Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 7401 was 7401, checked in by landauf, 14 years ago

merged doc branch back to trunk

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