Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/console/src/libraries/core/Shell.cc @ 6013

Last change on this file since 6013 was 6010, checked in by rgrieder, 15 years ago

Sorted out some small stuff in the Shell and hopefully fixed IOConsole problems (flickering and too much output).

  • Property svn:eol-style set to native
File size: 15.1 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 "CommandExecutor.h"
34#include "CoreIncludes.h"
35#include "ConfigValueIncludes.h"
36#include "ConsoleCommand.h"
37
38namespace orxonox
39{
40    SetConsoleCommandShortcut(OutputHandler, log);
41    SetConsoleCommandShortcut(OutputHandler, error);
42    SetConsoleCommandShortcut(OutputHandler, warning);
43    SetConsoleCommandShortcut(OutputHandler, info);
44    SetConsoleCommandShortcut(OutputHandler, debug);
45
46    Shell::Shell(const std::string& consoleName, bool bScrollable, bool bPrependOutputLevel)
47        : OutputListener(consoleName)
48        , inputBuffer_(new InputBuffer())
49        , consoleName_(consoleName)
50        , bPrependOutputLevel_(bPrependOutputLevel)
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, int level)
193    {
194        if (level <= this->softDebugLevel_)
195            this->outputLines_.push_front(line);
196        this->updateListeners<&ShellListener::lineAdded>();
197    }
198
199    void Shell::clearOutput()
200    {
201        this->outputLines_.clear();
202        this->scrollIterator_ = this->outputLines_.begin();
203
204        this->scrollPosition_ = 0;
205        this->bFinishedLastLine_ = true;
206
207        this->updateListeners<&ShellListener::linesChanged>();
208    }
209
210    std::list<std::string>::const_iterator Shell::getNewestLineIterator() const
211    {
212        if (this->scrollPosition_)
213            return this->scrollIterator_;
214        else
215            return this->outputLines_.begin();
216    }
217
218    std::list<std::string>::const_iterator Shell::getEndIterator() const
219    {
220        return this->outputLines_.end();
221    }
222
223    void Shell::addToHistory(const std::string& command)
224    {
225        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
226        this->historyPosition_ = 0;
227        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
228    }
229
230    std::string Shell::getFromHistory() const
231    {
232        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
233        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
234            return this->commandHistory_[index];
235        else
236            return "";
237    }
238
239    void Shell::outputChanged(int level)
240    {
241        bool newline = false;
242        do
243        {
244            std::string output;
245            std::getline(this->outputBuffer_, output);
246
247            bool eof = this->outputBuffer_.eof();
248            bool fail = this->outputBuffer_.fail();
249            if (eof)
250                this->outputBuffer_.flush();
251            if (eof || fail)
252                this->outputBuffer_.clear();
253            newline = (!eof && !fail);
254
255            if (!newline && output == "")
256                break;
257
258            if (this->bFinishedLastLine_)
259            {
260                if (this->bPrependOutputLevel_)
261                    output.insert(0, 1, static_cast<char>(level));
262
263                this->outputLines_.push_front(output);
264
265                if (this->scrollPosition_)
266                    this->scrollPosition_++;
267                else
268                    this->scrollIterator_ = this->outputLines_.begin();
269
270                this->bFinishedLastLine_ = newline;
271
272                if (!this->scrollPosition_)
273                {
274                    this->updateListeners<&ShellListener::lineAdded>();
275                }
276            }
277            else
278            {
279                (*this->outputLines_.begin()) += output;
280                this->bFinishedLastLine_ = newline;
281                this->updateListeners<&ShellListener::onlyLastLineChanged>();
282            }
283
284        } while (newline);
285    }
286
287    void Shell::clearInput()
288    {
289        this->inputBuffer_->clear();
290        this->historyPosition_ = 0;
291        this->updateListeners<&ShellListener::inputChanged>();
292        this->updateListeners<&ShellListener::cursorChanged>();
293    }
294
295    void Shell::setPromptPrefix(const std::string& str)
296    {
297    }
298
299
300    // ##########################################
301    // ###   InputBuffer callback functions   ###
302    // ##########################################
303
304    void Shell::inputChanged()
305    {
306        this->updateListeners<&ShellListener::inputChanged>();
307        this->updateListeners<&ShellListener::cursorChanged>();
308    }
309
310    void Shell::execute()
311    {
312        this->addToHistory(this->inputBuffer_->get());
313        this->updateListeners<&ShellListener::executed>();
314
315        if (!CommandExecutor::execute(this->inputBuffer_->get()))
316            this->addOutputLine("Error: Can't execute \"" + this->inputBuffer_->get() + "\".", 1);
317
318        this->clearInput();
319    }
320
321    void Shell::hintAndComplete()
322    {
323        this->inputBuffer_->set(CommandExecutor::complete(this->inputBuffer_->get()));
324        this->addOutputLine(CommandExecutor::hint(this->inputBuffer_->get()), -1);
325
326        this->inputChanged();
327    }
328
329    void Shell::backspace()
330    {
331        this->inputBuffer_->removeBehindCursor();
332        this->updateListeners<&ShellListener::inputChanged>();
333        this->updateListeners<&ShellListener::cursorChanged>();
334    }
335
336    void Shell::exit()
337    {
338        if (this->inputBuffer_->getSize() > 0)
339        {
340            this->clearInput();
341            return;
342        }
343
344        this->clearInput();
345        this->scrollPosition_ = 0;
346        this->scrollIterator_ = this->outputLines_.begin();
347
348        this->updateListeners<&ShellListener::exit>();
349    }
350
351    void Shell::deleteChar()
352    {
353        this->inputBuffer_->removeAtCursor();
354        this->updateListeners<&ShellListener::inputChanged>();
355    }
356
357    void Shell::cursorRight()
358    {
359        this->inputBuffer_->increaseCursor();
360        this->updateListeners<&ShellListener::cursorChanged>();
361    }
362
363    void Shell::cursorLeft()
364    {
365        this->inputBuffer_->decreaseCursor();
366        this->updateListeners<&ShellListener::cursorChanged>();
367    }
368
369    void Shell::cursorEnd()
370    {
371        this->inputBuffer_->setCursorToEnd();
372        this->updateListeners<&ShellListener::cursorChanged>();
373    }
374
375    void Shell::cursorHome()
376    {
377        this->inputBuffer_->setCursorToBegin();
378        this->updateListeners<&ShellListener::cursorChanged>();
379    }
380
381    void Shell::historyUp()
382    {
383        if (this->historyPosition_ < this->commandHistory_.size())
384        {
385            this->historyPosition_++;
386            this->inputBuffer_->set(this->getFromHistory());
387        }
388    }
389
390    void Shell::historyDown()
391    {
392        if (this->historyPosition_ > 0)
393        {
394            this->historyPosition_--;
395            this->inputBuffer_->set(this->getFromHistory());
396        }
397    }
398
399    void Shell::historySearchUp()
400    {
401        if (this->historyPosition_ == this->historyOffset_)
402            return;
403        unsigned int cursorPosition = this->getCursorPosition();
404        std::string input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning of the inputline until the cursor position
405        for (unsigned int newPos = this->historyPosition_ + 1; newPos <= this->historyOffset_; newPos++)
406        {
407            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // search case insensitive
408            {
409                this->historyPosition_ = newPos;
410                this->inputBuffer_->set(this->getFromHistory());
411                this->setCursorPosition(cursorPosition);
412                return;
413            }
414        }
415    }
416
417    void Shell::historySearchDown()
418    {
419        if (this->historyPosition_ == 0)
420            return;
421        unsigned int cursorPosition = this->getCursorPosition();
422        std::string input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning
423        for (unsigned int newPos = this->historyPosition_ - 1; newPos > 0; newPos--)
424        {
425            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // sear$
426            {
427                this->historyPosition_ = newPos;
428                this->inputBuffer_->set(this->getFromHistory());
429                this->setCursorPosition(cursorPosition);
430                return;
431            }
432        }
433    }
434
435    void Shell::scrollUp()
436    {
437        if (this->scrollIterator_ != this->outputLines_.end())
438        {
439            ++this->scrollIterator_;
440            ++this->scrollPosition_;
441
442            this->updateListeners<&ShellListener::linesChanged>();
443        }
444    }
445
446    void Shell::scrollDown()
447    {
448        if (this->scrollIterator_ != this->outputLines_.begin())
449        {
450            --this->scrollIterator_;
451            --this->scrollPosition_;
452
453            this->updateListeners<&ShellListener::linesChanged>();
454        }
455    }
456}
Note: See TracBrowser for help on using the repository browser.