Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 9550 was 9550, checked in by landauf, 11 years ago

merged testing branch back to trunk. unbelievable it took me 13 months to finish this chore…

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