Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/Shell.cc

Last change on this file was 11071, checked in by landauf, 8 years ago

merged branch cpp11_v3 back to trunk

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