Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/unity_build/src/libraries/core/command/Shell.cc @ 8524

Last change on this file since 8524 was 8524, checked in by rgrieder, 13 years ago

Fixed a serious problem with the debug levels for the Shells by introducing DevModeListener and moving the debug levels to the Shell again..

  • Property svn:eol-style set to native
File size: 21.5 KB
RevLine 
[1505]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:
[6105]25 *      Reto Grieder
[1505]26 *
27 */
28
[7401]29/**
30    @file
31    @brief Implementation of the Shell class.
32*/
33
[1505]34#include "Shell.h"
[3196]35
36#include "util/OutputHandler.h"
[6105]37#include "util/StringUtils.h"
38#include "util/SubString.h"
[7203]39#include "core/CoreIncludes.h"
40#include "core/ConfigFileManager.h"
41#include "core/ConfigValueIncludes.h"
[8524]42#include "core/PathConfig.h"
[1505]43#include "CommandExecutor.h"
44#include "ConsoleCommand.h"
45
46namespace orxonox
47{
[7236]48    SetConsoleCommand("log",     OutputHandler::log    );
[8079]49    SetConsoleCommand("error",   OutputHandler::error  ).hide();
50    SetConsoleCommand("warning", OutputHandler::warning).hide();
51    SetConsoleCommand("info",    OutputHandler::info   ).hide();
52    SetConsoleCommand("debug",   OutputHandler::debug  ).hide();
[1747]53
[7229]54    unsigned int Shell::cacheSize_s;
55
[7401]56    /**
57        @brief Constructor: Initializes the values and registers itself at OutputHandler.
58        @param consoleName The name of the shell - used to define the name of the soft-debug-level config-value
59        @param bScrollable If true, the user is allowed to scroll through the output-lines
60    */
[6417]61    Shell::Shell(const std::string& consoleName, bool bScrollable)
[6105]62        : OutputListener(consoleName)
63        , inputBuffer_(new InputBuffer())
64        , consoleName_(consoleName)
65        , bScrollable_(bScrollable)
[1505]66    {
67        RegisterRootObject(Shell);
68
69        this->scrollPosition_ = 0;
70        this->maxHistoryLength_ = 100;
71        this->historyPosition_ = 0;
72        this->historyOffset_ = 0;
[6105]73        this->bFinishedLastLine_ = true;
[1505]74
[6105]75        this->clearOutput();
[1755]76        this->configureInputBuffer();
[1505]77
[6417]78        // Specify file for the command history
79        ConfigFileManager::getInstance().setFilename(ConfigFileType::CommandHistory, "commandHistory.ini");
[3280]80
[6417]81        // Use a stringstream object to buffer the output
[6105]82        this->outputStream_ = &this->outputBuffer_;
83
[1505]84        this->setConfigValues();
[1792]85
[6105]86        // Get the previous output and add it to the Shell
[8515]87        OutputHandler::OutputVector::const_iterator it = OutputHandler::getInstance().getOutput().begin();
88        for (;it != OutputHandler::getInstance().getOutput().end(); ++it)
[6105]89        {
[8524]90            if (it->first <= debugLevel_)
[6105]91            {
92                this->outputBuffer_ << it->second;
93                this->outputChanged(it->first);
94            }
95        }
96
97        // Register the shell as output listener
98        OutputHandler::getInstance().registerOutputListener(this);
[8524]99        OutputHandler::getInstance().setSoftDebugLevel(consoleName_, debugLevel_);
[1505]100    }
101
[7401]102    /**
103        @brief Destructor: Unregisters the shell from OutputHandler.
104    */
[1755]105    Shell::~Shell()
106    {
[6105]107        OutputHandler::getInstance().unregisterOutputListener(this);
108        this->inputBuffer_->destroy();
[1755]109    }
110
[8524]111    namespace DefaultLogLevel
112    {
113        const OutputLevel::Value Dev  = OutputLevel::Info;
114        const OutputLevel::Value User = OutputLevel::Error;
115    }
116
[7401]117    /**
118        @brief Defines the config values.
119    */
[1505]120    void Shell::setConfigValues()
121    {
[6105]122        SetConfigValue(maxHistoryLength_, 100)
[3280]123            .callback(this, &Shell::commandHistoryLengthChanged);
[6105]124        SetConfigValue(historyOffset_, 0)
[3280]125            .callback(this, &Shell::commandHistoryOffsetChanged);
[6417]126        setConfigValueGeneric(this, &commandHistory_, ConfigFileType::CommandHistory, "Shell", "commandHistory_", std::vector<std::string>());
[7229]127        SetConfigValue(cacheSize_s, 32);
[8524]128
129        // Choose the default level according to the path Orxonox was started (build directory or not)
130        OutputLevel::Value defaultDebugLevel = (PathConfig::buildDirectoryRun() ? DefaultLogLevel::Dev : DefaultLogLevel::User);
131        SetConfigValueExternal(debugLevel_, "OutputHandler", "debugLevel" + consoleName_, defaultDebugLevel)
132            .description("The maximum level of debug output shown in the " + consoleName_);
133        OutputHandler::getInstance().setSoftDebugLevel(consoleName_, debugLevel_);
[1747]134    }
[1505]135
[7401]136    /**
137        @brief Config-value callback: Called when the history offset has changed in the config-file.
138    */
[1747]139    void Shell::commandHistoryOffsetChanged()
140    {
[1505]141        if (this->historyOffset_ >= this->maxHistoryLength_)
142            this->historyOffset_ = 0;
[1747]143    }
[1505]144
[7401]145    /**
146        @brief Config-value callback: Called when the length of the command history has changed in the config-file.
147    */
[1747]148    void Shell::commandHistoryLengthChanged()
149    {
150        this->commandHistoryOffsetChanged();
151
[1505]152        while (this->commandHistory_.size() > this->maxHistoryLength_)
153        {
154            unsigned int index = this->commandHistory_.size() - 1;
155            this->commandHistory_.erase(this->commandHistory_.begin() + index);
156            ModifyConfigValue(commandHistory_, remove, index);
157        }
158    }
159
[8524]160    /** Called upon changes in the development mode (by Core)
161        Behaviour details see Core::devModeChanged.
162    */
163    void Shell::devModeChanged(bool value)
164    {
165        bool isNormal = (value == PathConfig::buildDirectoryRun());
166        if (isNormal)
167        {
168            ModifyConfigValueExternal(debugLevel_, "debugLevel" + consoleName_, update);
169        }
170        else
171        {
172            OutputLevel::Value level = (value ? DefaultLogLevel::Dev : DefaultLogLevel::User);
173            ModifyConfigValueExternal(debugLevel_, "debugLevel" + consoleName_, tset, level);
174        }
175    }
176
[7401]177    /**
178        @brief Registers this object as listener for different key-events at the input buffer.
179    */
[1755]180    void Shell::configureInputBuffer()
[1505]181    {
182        this->inputBuffer_->registerListener(this, &Shell::inputChanged, true);
[6105]183        this->inputBuffer_->registerListener(this, &Shell::execute,         '\r',   false);
184        this->inputBuffer_->registerListener(this, &Shell::execute,         '\n',   false);
185        this->inputBuffer_->registerListener(this, &Shell::hintAndComplete, '\t',   true);
186        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\b',   true);
187        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\177', true);
188        this->inputBuffer_->registerListener(this, &Shell::exit,            '\033', true); // escape
189        this->inputBuffer_->registerListener(this, &Shell::deleteChar,      KeyCode::Delete);
190        this->inputBuffer_->registerListener(this, &Shell::cursorRight,     KeyCode::Right);
191        this->inputBuffer_->registerListener(this, &Shell::cursorLeft,      KeyCode::Left);
192        this->inputBuffer_->registerListener(this, &Shell::cursorEnd,       KeyCode::End);
193        this->inputBuffer_->registerListener(this, &Shell::cursorHome,      KeyCode::Home);
194        this->inputBuffer_->registerListener(this, &Shell::historyUp,       KeyCode::Up);
195        this->inputBuffer_->registerListener(this, &Shell::historyDown,     KeyCode::Down);
196        if (this->bScrollable_)
197        {
198            this->inputBuffer_->registerListener(this, &Shell::scrollUp,    KeyCode::PageUp);
199            this->inputBuffer_->registerListener(this, &Shell::scrollDown,  KeyCode::PageDown);
200        }
201        else
202        {
203            this->inputBuffer_->registerListener(this, &Shell::historySearchUp,   KeyCode::PageUp);
204            this->inputBuffer_->registerListener(this, &Shell::historySearchDown, KeyCode::PageDown);
205        }
[1505]206    }
207
[7401]208    /**
209        @brief Registers a shell listener which listens for changes in this shell.
[6105]210    */
[1505]211    void Shell::registerListener(ShellListener* listener)
212    {
[6105]213        this->listeners_.push_back(listener);
[1505]214    }
215
[7401]216    /**
217        @brief Unregisters a shell listener.
218    */
[1505]219    void Shell::unregisterListener(ShellListener* listener)
220    {
221        for (std::list<ShellListener*>::iterator it = this->listeners_.begin(); it != this->listeners_.end(); )
222        {
223            if ((*it) == listener)
[6105]224                it = this->listeners_.erase(it);
[1505]225            else
226                ++it;
227        }
228    }
229
[7401]230    /**
231        @brief Changes the position of the cursor in the input buffer.
232    */
[1505]233    void Shell::setCursorPosition(unsigned int cursor)
234    {
235        this->inputBuffer_->setCursorPosition(cursor);
[6105]236        this->updateListeners<&ShellListener::cursorChanged>();
[1505]237    }
238
[7401]239    /**
240        @brief Sends output to the internal output buffer.
241    */
[6417]242    void Shell::addOutput(const std::string& text, LineType type)
[1505]243    {
[6417]244        this->outputBuffer_ << text;
245        this->outputChanged(type);
[1505]246    }
247
[7401]248    /**
249        @brief Clears the list of output-lines.
250    */
[6105]251    void Shell::clearOutput()
[1505]252    {
[6105]253        this->outputLines_.clear();
254        this->scrollIterator_ = this->outputLines_.begin();
[1505]255
256        this->scrollPosition_ = 0;
[6105]257        this->bFinishedLastLine_ = true;
[1505]258
[6105]259        this->updateListeners<&ShellListener::linesChanged>();
[1505]260    }
261
[7401]262    /**
263        @brief Returns an iterator to the newest line of output (except if the user is currently scrolling through the output).
264    */
[6417]265    Shell::LineList::const_iterator Shell::getNewestLineIterator() const
[1505]266    {
267        if (this->scrollPosition_)
268            return this->scrollIterator_;
269        else
[6105]270            return this->outputLines_.begin();
[1505]271    }
272
[7401]273    /**
274        @brief Returns the end() iterator of the list of output-lines.
275    */
[6417]276    Shell::LineList::const_iterator Shell::getEndIterator() const
[1505]277    {
[6105]278        return this->outputLines_.end();
[1505]279    }
280
[7401]281    /**
282        @brief Adds a command to the history of entered commands and writes it to the config-file.
283    */
[1505]284    void Shell::addToHistory(const std::string& command)
285    {
[7191]286        if (command == "")
287            return;
288
289        size_t previous_offset = mod(this->historyOffset_ - 1, this->maxHistoryLength_);
290        if (previous_offset < this->commandHistory_.size() && command == this->commandHistory_[previous_offset])
291            return;
292
[1505]293        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
294        this->historyPosition_ = 0;
295        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
296    }
297
[7401]298    /**
299        @brief Returns a command from the history of entered commands (usually the most recent history entry, but the user can scroll through the history).
300    */
[6417]301    const std::string& Shell::getFromHistory() const
[1505]302    {
[3301]303        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
[1505]304        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
305            return this->commandHistory_[index];
306        else
[6417]307            return BLANKSTRING;
[1505]308    }
309
[7401]310    /**
311        @brief Called by OutputHandler or internally whenever output was sent to the output buffer. Reads from the buffer and writes the new output-lines to the list.
312    */
[6417]313    void Shell::outputChanged(int lineType)
[1505]314    {
[6105]315        bool newline = false;
[1505]316        do
317        {
[7401]318            // get the first line from the buffer
[6105]319            std::string output;
320            std::getline(this->outputBuffer_, output);
[1505]321
[7401]322            // check the state of the buffer
[6105]323            bool eof = this->outputBuffer_.eof();
324            bool fail = this->outputBuffer_.fail();
325            if (eof)
[7401]326                this->outputBuffer_.flush(); // check if more output was received in the meantime
[6105]327            if (eof || fail)
[7401]328                this->outputBuffer_.clear(); // clear the error flags
329
330            // the line is terminated with a line-break if neither an error occurred nor the end of the file was reached
[6105]331            newline = (!eof && !fail);
332
[7401]333            // no output retrieved - break the loop
[6417]334            if (!newline && output.empty())
[1505]335                break;
336
[7401]337            // check if the last line was terminated with a line-break
[6105]338            if (this->bFinishedLastLine_)
[1505]339            {
[7401]340                // yes it was - push the new line to the list
[6417]341                this->outputLines_.push_front(std::make_pair(output, static_cast<LineType>(lineType)));
[1505]342
[7401]343                // adjust the scroll position if needed
[1505]344                if (this->scrollPosition_)
345                    this->scrollPosition_++;
346                else
[6105]347                    this->scrollIterator_ = this->outputLines_.begin();
[1505]348
349                if (!this->scrollPosition_)
[6105]350                    this->updateListeners<&ShellListener::lineAdded>();
[1505]351            }
352            else
353            {
[7401]354                // no it wasn't - add the new output to the last line
[6417]355                this->outputLines_.front().first += output;
[6105]356                this->updateListeners<&ShellListener::onlyLastLineChanged>();
[1505]357            }
[7401]358
359            // remember if the last line was terminated with a line-break
[6417]360            this->bFinishedLastLine_ = newline;
[1505]361
[7401]362        } while (newline); // loop as long as more lines are in the buffer
[1505]363    }
364
[7401]365    /**
366        @brief Clears the text in the input buffer.
367    */
[6105]368    void Shell::clearInput()
369    {
370        this->inputBuffer_->clear();
371        this->historyPosition_ = 0;
372        this->updateListeners<&ShellListener::inputChanged>();
373        this->updateListeners<&ShellListener::cursorChanged>();
374    }
375
376
377    // ##########################################
378    // ###   InputBuffer callback functions   ###
379    // ##########################################
380
[7401]381    /// InputBuffer callback: Called if the input changes.
[1505]382    void Shell::inputChanged()
383    {
[6105]384        this->updateListeners<&ShellListener::inputChanged>();
385        this->updateListeners<&ShellListener::cursorChanged>();
[1505]386    }
387
[7401]388    /// InputBuffer callback: Called if a key was pressed that executes a command (usually [return]).
[1505]389    void Shell::execute()
390    {
391        this->addToHistory(this->inputBuffer_->get());
[6105]392        this->updateListeners<&ShellListener::executed>();
[1505]393
[7228]394        int error;
395        const std::string& result = CommandExecutor::query(this->inputBuffer_->get(), &error);
396        if (error)
[6417]397        {
[7228]398            switch (error)
399            {
400                case CommandExecutor::Error:       this->outputBuffer_ << "Error: Can't execute \"" << this->inputBuffer_->get() << "\", command doesn't exist. (S)" << std::endl; break;
401                case CommandExecutor::Incomplete:  this->outputBuffer_ << "Error: Can't execute \"" << this->inputBuffer_->get() << "\", not enough arguments given. (S)" << std::endl; break;
402                case CommandExecutor::Deactivated: this->outputBuffer_ << "Error: Can't execute \"" << this->inputBuffer_->get() << "\", command is not active. (S)" << std::endl; break;
403                case CommandExecutor::Denied:      this->outputBuffer_ << "Error: Can't execute \"" << this->inputBuffer_->get() << "\", access denied. (S)" << std::endl; break;
404            }
[6417]405            this->outputChanged(Error);
406        }
[7189]407        else if (result != "")
408        {
409            this->outputBuffer_ << result << std::endl;
410            this->outputChanged(Command);
411        }
[1505]412
[6105]413        this->clearInput();
[1505]414    }
415
[7401]416    /// InputBuffer callback: Called if a key was pressed that shows hints and completes a command (usually [tab]).
[6105]417    void Shell::hintAndComplete()
[1505]418    {
[7228]419        this->inputBuffer_->set(CommandExecutor::evaluate(this->inputBuffer_->get()).complete());
420        this->outputBuffer_ << CommandExecutor::evaluate(this->inputBuffer_->get()).hint() << std::endl;
[6417]421        this->outputChanged(Hint);
[1505]422
423        this->inputChanged();
424    }
425
[7401]426    /// InputBuffer callback: Called if a key was pressed that deletes the character before the cursor (usually [backspace]).
[1505]427    void Shell::backspace()
428    {
429        this->inputBuffer_->removeBehindCursor();
[6105]430        this->updateListeners<&ShellListener::inputChanged>();
431        this->updateListeners<&ShellListener::cursorChanged>();
[1505]432    }
433
[7401]434    /// InputBuffer callback: Called if a key was pressed that deletes the character after the cursor (usually [delete]).
[6105]435    void Shell::deleteChar()
[1505]436    {
[6105]437        this->inputBuffer_->removeAtCursor();
438        this->updateListeners<&ShellListener::inputChanged>();
[1505]439    }
440
[7401]441    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the right (usually [arrow right]).
[6105]442    void Shell::cursorRight()
[1505]443    {
444        this->inputBuffer_->increaseCursor();
[6105]445        this->updateListeners<&ShellListener::cursorChanged>();
[1505]446    }
447
[7401]448    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the left (usually [arrow left]).
[6105]449    void Shell::cursorLeft()
[1505]450    {
451        this->inputBuffer_->decreaseCursor();
[6105]452        this->updateListeners<&ShellListener::cursorChanged>();
[1505]453    }
454
[7401]455    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the end of the input line (usually [end]).
[6105]456    void Shell::cursorEnd()
[1505]457    {
458        this->inputBuffer_->setCursorToEnd();
[6105]459        this->updateListeners<&ShellListener::cursorChanged>();
[1505]460    }
461
[7401]462    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the beginning of the input line (usually [home]).
[6105]463    void Shell::cursorHome()
[1505]464    {
465        this->inputBuffer_->setCursorToBegin();
[6105]466        this->updateListeners<&ShellListener::cursorChanged>();
[1505]467    }
468
[7401]469    /// InputBuffer callback: Called if a key was pressed that scrolls upwards through the history of entered commands (usually [arrow up]).
[6105]470    void Shell::historyUp()
[1505]471    {
472        if (this->historyPosition_ < this->commandHistory_.size())
473        {
474            this->historyPosition_++;
475            this->inputBuffer_->set(this->getFromHistory());
476        }
477    }
478
[7401]479    /// InputBuffer callback: Called if a key was pressed that scrolls downwards through the history of entered commands (usually [arrow down]).
[6105]480    void Shell::historyDown()
[1505]481    {
482        if (this->historyPosition_ > 0)
483        {
484            this->historyPosition_--;
485            this->inputBuffer_->set(this->getFromHistory());
486        }
487    }
488
[7401]489    /// InputBuffer callback: Called if a key was pressed that searches upwards through the history for a command stat starts like the one the user is currently typing (usually [page up]). Only if the shell is not scrollable.
[6105]490    void Shell::historySearchUp()
[1505]491    {
[6105]492        if (this->historyPosition_ == this->historyOffset_)
493            return;
494        unsigned int cursorPosition = this->getCursorPosition();
[6417]495        const std::string& input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning of the inputline until the cursor position
[6105]496        for (unsigned int newPos = this->historyPosition_ + 1; newPos <= this->historyOffset_; newPos++)
[1505]497        {
[6105]498            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // search case insensitive
499            {
500                this->historyPosition_ = newPos;
501                this->inputBuffer_->set(this->getFromHistory());
502                this->setCursorPosition(cursorPosition);
503                return;
504            }
505        }
506    }
[1505]507
[7401]508    /// InputBuffer callback: Called if a key was pressed that searches downwards through the history for a command stat starts like the one the user is currently typing (usually [page down]). Only if the shell is not scrollable.
[6105]509    void Shell::historySearchDown()
510    {
511        if (this->historyPosition_ == 0)
512            return;
513        unsigned int cursorPosition = this->getCursorPosition();
[6417]514        const std::string& input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning
[6105]515        for (unsigned int newPos = this->historyPosition_ - 1; newPos > 0; newPos--)
516        {
517            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // sear$
518            {
519                this->historyPosition_ = newPos;
520                this->inputBuffer_->set(this->getFromHistory());
521                this->setCursorPosition(cursorPosition);
522                return;
523            }
[1505]524        }
525    }
526
[7401]527    /// InputBuffer callback: Called if a key was pressed that scrolls upwards through the output history (usually [page up]). Only if the shell is scrollable.
[6105]528    void Shell::scrollUp()
[1505]529    {
[6105]530        if (this->scrollIterator_ != this->outputLines_.end())
[1505]531        {
[6105]532            ++this->scrollIterator_;
533            ++this->scrollPosition_;
[1505]534
[6105]535            this->updateListeners<&ShellListener::linesChanged>();
[1505]536        }
537    }
538
[7401]539    /// InputBuffer callback: Called if a key was pressed that scrolls downwards through the output history (usually [page down]). Only if the shell is scrollable.
[6105]540    void Shell::scrollDown()
[1505]541    {
[6105]542        if (this->scrollIterator_ != this->outputLines_.begin())
[1505]543        {
[6105]544            --this->scrollIterator_;
545            --this->scrollPosition_;
546
547            this->updateListeners<&ShellListener::linesChanged>();
[1505]548        }
549    }
[7401]550
551    /// InputBuffer callback: Called if a key was pressed that clears the text in the input buffer or closes the shell (usually [esc]).
552    void Shell::exit()
553    {
554        if (this->inputBuffer_->getSize() > 0)
555        {
556            this->clearInput();
557            return;
558        }
559
560        this->clearInput();
561        this->scrollPosition_ = 0;
562        this->scrollIterator_ = this->outputLines_.begin();
563
564        this->updateListeners<&ShellListener::exit>();
565    }
[1505]566}
Note: See TracBrowser for help on using the repository browser.