Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/cpp11_v2/src/libraries/core/command/Shell.cc @ 11007

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

made some enums in core library strongly typed. for other enums in core (especially in the input library) this isn't possible because enums are often used like flags (converted to int and compared with binary operators).

  • Property svn:eol-style set to native
File size: 20.3 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/config/ConfigFileManager.h"
43#include "core/config/ConfigValueIncludes.h"
44#include "core/ApplicationPaths.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    RegisterClassNoArgs(Shell);
59
60    /**
61        @brief Constructor: Initializes the values.
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    */
65    Shell::Shell(const std::string& consoleName, bool bScrollable)
66        : BaseWriter(consoleName, false)
67        , inputBuffer_(new InputBuffer())
68        , bScrollable_(bScrollable)
69    {
70        RegisterObject(Shell);
71
72        OutputManager::getInstance().registerListener(this);
73
74        this->scrollPosition_ = 0;
75        this->maxHistoryLength_ = 100;
76        this->historyPosition_ = 0;
77        this->historyOffset_ = 0;
78
79        this->clearOutput();
80        this->configureInputBuffer();
81
82        // Specify file for the command history
83        ConfigFileManager::getInstance().setFilename(ConfigFileType::CommandHistory, "commandHistory.ini");
84
85        // Choose the default level according to the path Orxonox was started (build directory or not)
86        OutputLevel defaultDebugLevel = (ApplicationPaths::buildDirectoryRun() ? DefaultLogLevel::Dev : DefaultLogLevel::User);
87        this->setLevelMax(defaultDebugLevel);
88
89        this->setConfigValues();
90
91        // Get the previous output and add it to the Shell
92        OutputManager::getInstance().getMemoryWriter()->resendOutput(this);
93    }
94
95    /**
96        @brief Destructor
97    */
98    Shell::~Shell()
99    {
100        delete this->inputBuffer_;
101
102        OutputManager::getInstance().unregisterListener(this);
103    }
104
105    /**
106        @brief Defines the config values.
107    */
108    void Shell::setConfigValues()
109    {
110        SetConfigValue(maxHistoryLength_, 100)
111            .callback(this, &Shell::commandHistoryLengthChanged);
112        SetConfigValue(historyOffset_, 0)
113            .callback(this, &Shell::commandHistoryOffsetChanged);
114        setConfigValueGeneric(this, &commandHistory_, ConfigFileType::CommandHistory, "Shell", "commandHistory_", std::vector<std::string>());
115        SetConfigValue(cacheSize_s, 32);
116
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);
135    }
136
137    /**
138        @brief Config-value callback: Called when the history offset has changed in the config-file.
139    */
140    void Shell::commandHistoryOffsetChanged()
141    {
142        if (this->historyOffset_ >= this->maxHistoryLength_)
143            this->historyOffset_ = 0;
144    }
145
146    /**
147        @brief Config-value callback: Called when the length of the command history has changed in the config-file.
148    */
149    void Shell::commandHistoryLengthChanged()
150    {
151        this->commandHistoryOffsetChanged();
152
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
161    /** Called upon changes in the development mode (by Core)
162        Behaviour details see Core::devModeChanged.
163    */
164    void Shell::devModeChanged(bool value)
165    {
166        bool isNormal = (value == ApplicationPaths::buildDirectoryRun());
167        if (isNormal)
168        {
169            ModifyConfigValueExternal(this->configurableMaxLevel_, this->getConfigurableMaxLevelName(), update);
170        }
171        else
172        {
173            OutputLevel level = (value ? DefaultLogLevel::Dev : DefaultLogLevel::User);
174            ModifyConfigValueExternal(this->configurableMaxLevel_, this->getConfigurableMaxLevelName(), tset, static_cast<int>(level));
175        }
176    }
177
178    /**
179        @brief Registers this object as listener for different key-events at the input buffer.
180    */
181    void Shell::configureInputBuffer()
182    {
183        this->inputBuffer_->registerListener(this, &Shell::inputChanged, true);
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        }
207    }
208
209    /**
210        @brief Registers a shell listener which listens for changes in this shell.
211    */
212    void Shell::registerListener(ShellListener* listener)
213    {
214        this->listeners_.push_back(listener);
215    }
216
217    /**
218        @brief Unregisters a shell listener.
219    */
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)
225                it = this->listeners_.erase(it);
226            else
227                ++it;
228        }
229    }
230
231    /**
232        @brief Changes the position of the cursor in the input buffer.
233    */
234    void Shell::setCursorPosition(unsigned int cursor)
235    {
236        this->inputBuffer_->setCursorPosition(cursor);
237        this->updateListeners<&ShellListener::cursorChanged>();
238    }
239
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
252    /**
253        @brief Adds multiple lines to the internal output buffer.
254    */
255    void Shell::addOutput(const std::string& text, LineType type)
256    {
257        std::vector<std::string> lines;
258        vectorize(text, '\n', &lines);
259
260        for (const std::string& line : lines)
261            this->addLine(line, type);
262    }
263
264    /**
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
270        this->outputLines_.push_front(std::make_pair(line, type));
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    /**
283        @brief Clears the list of output-lines.
284    */
285    void Shell::clearOutput()
286    {
287        this->outputLines_.clear();
288        this->scrollIterator_ = this->outputLines_.begin();
289
290        this->scrollPosition_ = 0;
291
292        this->updateListeners<&ShellListener::linesChanged>();
293    }
294
295    /**
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    /**
304        @brief Returns an iterator to the newest line of output (except if the user is currently scrolling through the output).
305    */
306    Shell::LineList::const_iterator Shell::getNewestLineIterator() const
307    {
308        if (this->scrollPosition_)
309            return this->scrollIterator_;
310        else
311            return this->outputLines_.begin();
312    }
313
314    /**
315        @brief Returns the end() iterator of the list of output-lines.
316    */
317    Shell::LineList::const_iterator Shell::getEndIterator() const
318    {
319        return this->outputLines_.end();
320    }
321
322    /**
323        @brief Adds a command to the history of entered commands and writes it to the config-file.
324    */
325    void Shell::addToHistory(const std::string& command)
326    {
327        if (command == "")
328            return;
329
330        size_t previous_offset = mod(static_cast<int>(this->historyOffset_) - 1, this->maxHistoryLength_);
331        if (previous_offset < this->commandHistory_.size() && command == this->commandHistory_[previous_offset])
332            return;
333
334        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
335        this->historyPosition_ = 0;
336        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
337    }
338
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    */
342    const std::string& Shell::getFromHistory() const
343    {
344        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
345        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
346            return this->commandHistory_[index];
347        else
348            return BLANKSTRING;
349    }
350
351    /**
352        @brief Clears the text in the input buffer.
353    */
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
367    /// InputBuffer callback: Called if the input changes.
368    void Shell::inputChanged()
369    {
370        this->updateListeners<&ShellListener::inputChanged>();
371        this->updateListeners<&ShellListener::cursorChanged>();
372    }
373
374    /// InputBuffer callback: Called if a key was pressed that executes a command (usually [return]).
375    void Shell::execute()
376    {
377        this->addToHistory(this->inputBuffer_->get());
378        this->updateListeners<&ShellListener::executed>();
379
380        int error;
381        const std::string& result = CommandExecutor::query(this->inputBuffer_->get(), &error);
382        if (error)
383            this->addOutput("Error: Can't execute \"" + this->inputBuffer_->get() + "\", " + CommandExecutor::getErrorDescription(error) + ". (Shell)", LineType::UserError);
384        else if (result != "")
385            this->addOutput(result, LineType::Result);
386
387        this->clearInput();
388    }
389
390    /// InputBuffer callback: Called if a key was pressed that shows hints and completes a command (usually [tab]).
391    void Shell::hintAndComplete()
392    {
393        this->inputBuffer_->set(CommandExecutor::evaluate(this->inputBuffer_->get()).complete());
394        this->addOutput(CommandExecutor::evaluate(this->inputBuffer_->get()).hint(), LineType::Hint);
395
396        this->inputChanged();
397    }
398
399    /// InputBuffer callback: Called if a key was pressed that deletes the character before the cursor (usually [backspace]).
400    void Shell::backspace()
401    {
402        this->inputBuffer_->removeBehindCursor();
403        this->updateListeners<&ShellListener::inputChanged>();
404        this->updateListeners<&ShellListener::cursorChanged>();
405    }
406
407    /// InputBuffer callback: Called if a key was pressed that deletes the character after the cursor (usually [delete]).
408    void Shell::deleteChar()
409    {
410        this->inputBuffer_->removeAtCursor();
411        this->updateListeners<&ShellListener::inputChanged>();
412    }
413
414    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the right (usually [arrow right]).
415    void Shell::cursorRight()
416    {
417        this->inputBuffer_->increaseCursor();
418        this->updateListeners<&ShellListener::cursorChanged>();
419    }
420
421    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the left (usually [arrow left]).
422    void Shell::cursorLeft()
423    {
424        this->inputBuffer_->decreaseCursor();
425        this->updateListeners<&ShellListener::cursorChanged>();
426    }
427
428    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the end of the input line (usually [end]).
429    void Shell::cursorEnd()
430    {
431        this->inputBuffer_->setCursorToEnd();
432        this->updateListeners<&ShellListener::cursorChanged>();
433    }
434
435    /// InputBuffer callback: Called if a key was pressed that moves the input cursor the beginning of the input line (usually [home]).
436    void Shell::cursorHome()
437    {
438        this->inputBuffer_->setCursorToBegin();
439        this->updateListeners<&ShellListener::cursorChanged>();
440    }
441
442    /// InputBuffer callback: Called if a key was pressed that scrolls upwards through the history of entered commands (usually [arrow up]).
443    void Shell::historyUp()
444    {
445        if (this->historyPosition_ < this->commandHistory_.size())
446        {
447            this->historyPosition_++;
448            this->inputBuffer_->set(this->getFromHistory());
449        }
450    }
451
452    /// InputBuffer callback: Called if a key was pressed that scrolls downwards through the history of entered commands (usually [arrow down]).
453    void Shell::historyDown()
454    {
455        if (this->historyPosition_ > 0)
456        {
457            this->historyPosition_--;
458            this->inputBuffer_->set(this->getFromHistory());
459        }
460    }
461
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.
463    void Shell::historySearchUp()
464    {
465        if (this->historyPosition_ == this->historyOffset_)
466            return;
467        unsigned int cursorPosition = this->getCursorPosition();
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
469        for (unsigned int newPos = this->historyPosition_ + 1; newPos <= this->historyOffset_; newPos++)
470        {
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    }
480
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.
482    void Shell::historySearchDown()
483    {
484        if (this->historyPosition_ == 0)
485            return;
486        unsigned int cursorPosition = this->getCursorPosition();
487        const std::string& input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning
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            }
497        }
498    }
499
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.
501    void Shell::scrollUp()
502    {
503        if (this->scrollIterator_ != this->outputLines_.end())
504        {
505            ++this->scrollIterator_;
506            ++this->scrollPosition_;
507
508            this->updateListeners<&ShellListener::linesChanged>();
509        }
510    }
511
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.
513    void Shell::scrollDown()
514    {
515        if (this->scrollIterator_ != this->outputLines_.begin())
516        {
517            --this->scrollIterator_;
518            --this->scrollPosition_;
519
520            this->updateListeners<&ShellListener::linesChanged>();
521        }
522    }
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    }
539}
Note: See TracBrowser for help on using the repository browser.