Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/output/src/libraries/core/command/Shell.cc @ 8801

Last change on this file since 8801 was 8801, checked in by landauf, 13 years ago

fixed bug with multiple lines in shell.
adjusted colors in IOConsole and InGameConsole a bit

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