Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/presentation2/src/libraries/core/Shell.cc @ 6180

Last change on this file since 6180 was 6180, checked in by rgrieder, 14 years ago

Extended Shell line colouring in order to distinguish output from COUT, entered commands and hints.

  • Property svn:eol-style set to native
File size: 15.2 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      Reto Grieder
26 *
27 */
28
29#include "Shell.h"
30
31#include "util/OutputHandler.h"
32#include "util/StringUtils.h"
33#include "util/SubString.h"
34#include "CommandExecutor.h"
35#include "CoreIncludes.h"
36#include "ConfigValueIncludes.h"
37#include "ConsoleCommand.h"
38
39namespace orxonox
40{
41    SetConsoleCommandShortcut(OutputHandler, log);
42    SetConsoleCommandShortcut(OutputHandler, error);
43    SetConsoleCommandShortcut(OutputHandler, warning);
44    SetConsoleCommandShortcut(OutputHandler, info);
45    SetConsoleCommandShortcut(OutputHandler, debug);
46
47    Shell::Shell(const std::string& consoleName, bool bScrollable)
48        : OutputListener(consoleName)
49        , inputBuffer_(new InputBuffer())
50        , consoleName_(consoleName)
51        , bScrollable_(bScrollable)
52    {
53        RegisterRootObject(Shell);
54
55        this->scrollPosition_ = 0;
56        this->maxHistoryLength_ = 100;
57        this->historyPosition_ = 0;
58        this->historyOffset_ = 0;
59        this->bFinishedLastLine_ = true;
60
61        this->clearOutput();
62        this->configureInputBuffer();
63
64        // Get a config file for the command history
65        this->commandHistoryConfigFileType_ = ConfigFileManager::getInstance().getNewConfigFileType();
66        ConfigFileManager::getInstance().setFilename(this->commandHistoryConfigFileType_, "commandHistory.ini");
67
68        // Use a stringstream object to buffer the output and get it line by line in update()
69        this->outputStream_ = &this->outputBuffer_;
70
71        this->setConfigValues();
72
73        // Get the previous output and add it to the Shell
74        for (OutputHandler::OutputVectorIterator it = OutputHandler::getInstance().getOutputVectorBegin();
75            it != OutputHandler::getInstance().getOutputVectorEnd(); ++it)
76        {
77            if (it->first <= this->getSoftDebugLevel())
78            {
79                this->outputBuffer_ << it->second;
80                this->outputChanged(it->first);
81            }
82        }
83
84        // Register the shell as output listener
85        OutputHandler::getInstance().registerOutputListener(this);
86    }
87
88    Shell::~Shell()
89    {
90        OutputHandler::getInstance().unregisterOutputListener(this);
91        this->inputBuffer_->destroy();
92    }
93
94    void Shell::setConfigValues()
95    {
96        SetConfigValue(maxHistoryLength_, 100)
97            .callback(this, &Shell::commandHistoryLengthChanged);
98        SetConfigValue(historyOffset_, 0)
99            .callback(this, &Shell::commandHistoryOffsetChanged);
100        SetConfigValueVectorGeneric(commandHistoryConfigFileType_, commandHistory_, std::vector<std::string>());
101
102#ifdef ORXONOX_RELEASE
103        const unsigned int defaultLevel = 1;
104#else
105        const unsigned int defaultLevel = 3;
106#endif
107        SetConfigValueGeneric(ConfigFileType::Settings, softDebugLevel_, "softDebugLevel" + this->consoleName_, "OutputHandler", defaultLevel)
108            .description("The maximal level of debug output shown in the Shell");
109        this->setSoftDebugLevel(this->softDebugLevel_);
110    }
111
112    void Shell::commandHistoryOffsetChanged()
113    {
114        if (this->historyOffset_ >= this->maxHistoryLength_)
115            this->historyOffset_ = 0;
116    }
117
118    void Shell::commandHistoryLengthChanged()
119    {
120        this->commandHistoryOffsetChanged();
121
122        while (this->commandHistory_.size() > this->maxHistoryLength_)
123        {
124            unsigned int index = this->commandHistory_.size() - 1;
125            this->commandHistory_.erase(this->commandHistory_.begin() + index);
126            ModifyConfigValue(commandHistory_, remove, index);
127        }
128    }
129
130    void Shell::configureInputBuffer()
131    {
132        this->inputBuffer_->registerListener(this, &Shell::inputChanged, true);
133        this->inputBuffer_->registerListener(this, &Shell::execute,         '\r',   false);
134        this->inputBuffer_->registerListener(this, &Shell::execute,         '\n',   false);
135        this->inputBuffer_->registerListener(this, &Shell::hintAndComplete, '\t',   true);
136        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\b',   true);
137        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\177', true);
138        this->inputBuffer_->registerListener(this, &Shell::exit,            '\033', true); // escape
139        this->inputBuffer_->registerListener(this, &Shell::deleteChar,      KeyCode::Delete);
140        this->inputBuffer_->registerListener(this, &Shell::cursorRight,     KeyCode::Right);
141        this->inputBuffer_->registerListener(this, &Shell::cursorLeft,      KeyCode::Left);
142        this->inputBuffer_->registerListener(this, &Shell::cursorEnd,       KeyCode::End);
143        this->inputBuffer_->registerListener(this, &Shell::cursorHome,      KeyCode::Home);
144        this->inputBuffer_->registerListener(this, &Shell::historyUp,       KeyCode::Up);
145        this->inputBuffer_->registerListener(this, &Shell::historyDown,     KeyCode::Down);
146        if (this->bScrollable_)
147        {
148            this->inputBuffer_->registerListener(this, &Shell::scrollUp,    KeyCode::PageUp);
149            this->inputBuffer_->registerListener(this, &Shell::scrollDown,  KeyCode::PageDown);
150        }
151        else
152        {
153            this->inputBuffer_->registerListener(this, &Shell::historySearchUp,   KeyCode::PageUp);
154            this->inputBuffer_->registerListener(this, &Shell::historySearchDown, KeyCode::PageDown);
155        }
156    }
157
158    /*
159    void Shell::history()
160    {
161        Shell& instance = Shell::getInstance();
162
163        for (unsigned int i = instance.historyOffset_; i < instance.commandHistory_.size(); ++i)
164            instance.addOutputLine(instance.commandHistory_[i], -1);
165        for (unsigned int i =  0; i < instance.historyOffset_; ++i)
166            instance.addOutputLine(instance.commandHistory_[i], -1);
167    }
168    */
169
170    void Shell::registerListener(ShellListener* listener)
171    {
172        this->listeners_.push_back(listener);
173    }
174
175    void Shell::unregisterListener(ShellListener* listener)
176    {
177        for (std::list<ShellListener*>::iterator it = this->listeners_.begin(); it != this->listeners_.end(); )
178        {
179            if ((*it) == listener)
180                it = this->listeners_.erase(it);
181            else
182                ++it;
183        }
184    }
185
186    void Shell::setCursorPosition(unsigned int cursor)
187    {
188        this->inputBuffer_->setCursorPosition(cursor);
189        this->updateListeners<&ShellListener::cursorChanged>();
190    }
191
192    void Shell::addOutputLine(const std::string& line, LineType type)
193    {
194        // Make sure we really only have one line per line (no new lines!)
195        SubString lines(line, '\n');
196        for (unsigned i = 0; i < lines.size(); ++i)
197        {
198            this->outputLines_.push_front(std::make_pair(lines[i], type));
199            this->updateListeners<&ShellListener::lineAdded>();
200        }
201    }
202
203    void Shell::clearOutput()
204    {
205        this->outputLines_.clear();
206        this->scrollIterator_ = this->outputLines_.begin();
207
208        this->scrollPosition_ = 0;
209        this->bFinishedLastLine_ = true;
210
211        this->updateListeners<&ShellListener::linesChanged>();
212    }
213
214    Shell::LineList::const_iterator Shell::getNewestLineIterator() const
215    {
216        if (this->scrollPosition_)
217            return this->scrollIterator_;
218        else
219            return this->outputLines_.begin();
220    }
221
222    Shell::LineList::const_iterator Shell::getEndIterator() const
223    {
224        return this->outputLines_.end();
225    }
226
227    void Shell::addToHistory(const std::string& command)
228    {
229        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
230        this->historyPosition_ = 0;
231        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
232    }
233
234    std::string Shell::getFromHistory() const
235    {
236        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
237        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
238            return this->commandHistory_[index];
239        else
240            return "";
241    }
242
243    void Shell::outputChanged(int level)
244    {
245        bool newline = false;
246        do
247        {
248            std::string output;
249            std::getline(this->outputBuffer_, output);
250
251            bool eof = this->outputBuffer_.eof();
252            bool fail = this->outputBuffer_.fail();
253            if (eof)
254                this->outputBuffer_.flush();
255            if (eof || fail)
256                this->outputBuffer_.clear();
257            newline = (!eof && !fail);
258
259            if (!newline && output == "")
260                break;
261
262            if (this->bFinishedLastLine_)
263            {
264                this->outputLines_.push_front(std::make_pair(output, static_cast<LineType>(level)));
265
266                if (this->scrollPosition_)
267                    this->scrollPosition_++;
268                else
269                    this->scrollIterator_ = this->outputLines_.begin();
270
271                this->bFinishedLastLine_ = newline;
272
273                if (!this->scrollPosition_)
274                {
275                    this->updateListeners<&ShellListener::lineAdded>();
276                }
277            }
278            else
279            {
280                this->outputLines_.front().first += output;
281                this->bFinishedLastLine_ = newline;
282                this->updateListeners<&ShellListener::onlyLastLineChanged>();
283            }
284
285        } while (newline);
286    }
287
288    void Shell::clearInput()
289    {
290        this->inputBuffer_->clear();
291        this->historyPosition_ = 0;
292        this->updateListeners<&ShellListener::inputChanged>();
293        this->updateListeners<&ShellListener::cursorChanged>();
294    }
295
296    void Shell::setPromptPrefix(const std::string& str)
297    {
298    }
299
300
301    // ##########################################
302    // ###   InputBuffer callback functions   ###
303    // ##########################################
304
305    void Shell::inputChanged()
306    {
307        this->updateListeners<&ShellListener::inputChanged>();
308        this->updateListeners<&ShellListener::cursorChanged>();
309    }
310
311    void Shell::execute()
312    {
313        this->addToHistory(this->inputBuffer_->get());
314        this->updateListeners<&ShellListener::executed>();
315
316        if (!CommandExecutor::execute(this->inputBuffer_->get()))
317            this->addOutputLine("Error: Can't execute \"" + this->inputBuffer_->get() + "\".", Error);
318
319        this->clearInput();
320    }
321
322    void Shell::hintAndComplete()
323    {
324        this->inputBuffer_->set(CommandExecutor::complete(this->inputBuffer_->get()));
325        this->addOutputLine(CommandExecutor::hint(this->inputBuffer_->get()), Hint);
326
327        this->inputChanged();
328    }
329
330    void Shell::backspace()
331    {
332        this->inputBuffer_->removeBehindCursor();
333        this->updateListeners<&ShellListener::inputChanged>();
334        this->updateListeners<&ShellListener::cursorChanged>();
335    }
336
337    void Shell::exit()
338    {
339        if (this->inputBuffer_->getSize() > 0)
340        {
341            this->clearInput();
342            return;
343        }
344
345        this->clearInput();
346        this->scrollPosition_ = 0;
347        this->scrollIterator_ = this->outputLines_.begin();
348
349        this->updateListeners<&ShellListener::exit>();
350    }
351
352    void Shell::deleteChar()
353    {
354        this->inputBuffer_->removeAtCursor();
355        this->updateListeners<&ShellListener::inputChanged>();
356    }
357
358    void Shell::cursorRight()
359    {
360        this->inputBuffer_->increaseCursor();
361        this->updateListeners<&ShellListener::cursorChanged>();
362    }
363
364    void Shell::cursorLeft()
365    {
366        this->inputBuffer_->decreaseCursor();
367        this->updateListeners<&ShellListener::cursorChanged>();
368    }
369
370    void Shell::cursorEnd()
371    {
372        this->inputBuffer_->setCursorToEnd();
373        this->updateListeners<&ShellListener::cursorChanged>();
374    }
375
376    void Shell::cursorHome()
377    {
378        this->inputBuffer_->setCursorToBegin();
379        this->updateListeners<&ShellListener::cursorChanged>();
380    }
381
382    void Shell::historyUp()
383    {
384        if (this->historyPosition_ < this->commandHistory_.size())
385        {
386            this->historyPosition_++;
387            this->inputBuffer_->set(this->getFromHistory());
388        }
389    }
390
391    void Shell::historyDown()
392    {
393        if (this->historyPosition_ > 0)
394        {
395            this->historyPosition_--;
396            this->inputBuffer_->set(this->getFromHistory());
397        }
398    }
399
400    void Shell::historySearchUp()
401    {
402        if (this->historyPosition_ == this->historyOffset_)
403            return;
404        unsigned int cursorPosition = this->getCursorPosition();
405        std::string input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning of the inputline until the cursor position
406        for (unsigned int newPos = this->historyPosition_ + 1; newPos <= this->historyOffset_; newPos++)
407        {
408            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // search case insensitive
409            {
410                this->historyPosition_ = newPos;
411                this->inputBuffer_->set(this->getFromHistory());
412                this->setCursorPosition(cursorPosition);
413                return;
414            }
415        }
416    }
417
418    void Shell::historySearchDown()
419    {
420        if (this->historyPosition_ == 0)
421            return;
422        unsigned int cursorPosition = this->getCursorPosition();
423        std::string input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning
424        for (unsigned int newPos = this->historyPosition_ - 1; newPos > 0; newPos--)
425        {
426            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // sear$
427            {
428                this->historyPosition_ = newPos;
429                this->inputBuffer_->set(this->getFromHistory());
430                this->setCursorPosition(cursorPosition);
431                return;
432            }
433        }
434    }
435
436    void Shell::scrollUp()
437    {
438        if (this->scrollIterator_ != this->outputLines_.end())
439        {
440            ++this->scrollIterator_;
441            ++this->scrollPosition_;
442
443            this->updateListeners<&ShellListener::linesChanged>();
444        }
445    }
446
447    void Shell::scrollDown()
448    {
449        if (this->scrollIterator_ != this->outputLines_.begin())
450        {
451            --this->scrollIterator_;
452            --this->scrollPosition_;
453
454            this->updateListeners<&ShellListener::linesChanged>();
455        }
456    }
457}
Note: See TracBrowser for help on using the repository browser.