Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Code related to config values for output is now implemented in BaseWriter.
There's a config value for the normal output level, a vector with additional contexts, and a config value for the level of these additional contexts.
ioconsole and standard console use the same values.

  • Property svn:eol-style set to native
File size: 20.5 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/MemoryWriter.h"
40#include "core/CoreIncludes.h"
41#include "core/ConfigFileManager.h"
42#include "core/ConfigValueIncludes.h"
43#include "core/PathConfig.h"
44#include "core/input/InputBuffer.h"
45#include "CommandExecutor.h"
46#include "ConsoleCommand.h"
47
48namespace orxonox
49{
50//    SetConsoleCommand("log",     OutputHandler::log    );
51//    SetConsoleCommand("error",   OutputHandler::error  ).hide();
52//    SetConsoleCommand("warning", OutputHandler::warning).hide();
53//    SetConsoleCommand("info",    OutputHandler::info   ).hide();
54//    SetConsoleCommand("debug",   OutputHandler::debug  ).hide();
55
56    unsigned int Shell::cacheSize_s;
57
58    namespace DefaultLogLevel
59    {
60        const OutputLevel Dev  = level::internal_warning;
61        const OutputLevel User = level::user_info;
62    }
63
64    /**
65        @brief Constructor: Initializes the values.
66        @param consoleName The name of the shell - used to define the name of the soft-debug-level config-value
67        @param bScrollable If true, the user is allowed to scroll through the output-lines
68    */
69    Shell::Shell(const std::string& consoleName, bool bScrollable)
70        : BaseWriter(consoleName)
71        , inputBuffer_(new InputBuffer())
72        , bScrollable_(bScrollable)
73    {
74        RegisterRootObject(Shell);
75
76        this->scrollPosition_ = 0;
77        this->maxHistoryLength_ = 100;
78        this->historyPosition_ = 0;
79        this->historyOffset_ = 0;
80
81        this->clearOutput();
82        this->configureInputBuffer();
83
84        // Specify file for the command history
85        ConfigFileManager::getInstance().setFilename(ConfigFileType::CommandHistory, "commandHistory.ini");
86
87        // Choose the default level according to the path Orxonox was started (build directory or not)
88        OutputLevel defaultDebugLevel = (PathConfig::buildDirectoryRun() ? DefaultLogLevel::Dev : DefaultLogLevel::User);
89        this->setLevelMax(defaultDebugLevel);
90
91        this->setConfigValues();
92
93        // Get the previous output and add it to the Shell
94        MemoryWriter::getInstance().resendOutput(this);
95    }
96
97    /**
98        @brief Destructor
99    */
100    Shell::~Shell()
101    {
102        this->inputBuffer_->destroy();
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::changedConfigurableLevels);
123        SetConfigValueExternal(this->configurableContextsMaxLevel_,
124                               this->getConfigurableSectionName(),
125                               this->getConfigurableContextsMaxLevelName(),
126                               this->configurableContextsMaxLevel_)
127            .description("The maximum level of output shown in the " + this->getName() + " for additional contexts")
128            .callback(static_cast<BaseWriter*>(this), &BaseWriter::changedConfigurableLevels);
129        SetConfigValueExternal(this->configurableContexts_,
130                               this->getConfigurableSectionName(),
131                               this->getConfigurableContextsName(),
132                               this->configurableContexts_)
133            .description("Additional output contexts shown in the " + this->getName())
134            .callback(static_cast<BaseWriter*>(this), &BaseWriter::changedConfigurableLevels);
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 == PathConfig::buildDirectoryRun());
167        if (isNormal)
168        {
169            ModifyConfigValueExternal(debugLevel_, this->getConfigurableMaxLevelName(), update);
170        }
171        else
172        {
173            OutputLevel level = (value ? DefaultLogLevel::Dev : DefaultLogLevel::User);
174            ModifyConfigValueExternal(debugLevel_, this->getConfigurableMaxLevelName(), tset, 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 Sends output to the internal output buffer.
254    */
255    void Shell::addOutput(const std::string& text, LineType type)
256    {
257        // yes it was - push the new line to the list
258        this->outputLines_.push_front(std::make_pair(text, static_cast<LineType>(type)));
259
260        // adjust the scroll position if needed
261        if (this->scrollPosition_)
262            this->scrollPosition_++;
263        else
264            this->scrollIterator_ = this->outputLines_.begin();
265
266        if (!this->scrollPosition_)
267            this->updateListeners<&ShellListener::lineAdded>();
268    }
269
270    /**
271        @brief Clears the list of output-lines.
272    */
273    void Shell::clearOutput()
274    {
275        this->outputLines_.clear();
276        this->scrollIterator_ = this->outputLines_.begin();
277
278        this->scrollPosition_ = 0;
279
280        this->updateListeners<&ShellListener::linesChanged>();
281    }
282
283    /**
284        @brief Inherited from BaseWriter (LogListener), called if a new line of output was sent.
285    */
286    void Shell::printLine(const std::string& line, OutputLevel level)
287    {
288        this->addOutput(line, static_cast<LineType>(level));
289    }
290
291    /**
292        @brief Returns an iterator to the newest line of output (except if the user is currently scrolling through the output).
293    */
294    Shell::LineList::const_iterator Shell::getNewestLineIterator() const
295    {
296        if (this->scrollPosition_)
297            return this->scrollIterator_;
298        else
299            return this->outputLines_.begin();
300    }
301
302    /**
303        @brief Returns the end() iterator of the list of output-lines.
304    */
305    Shell::LineList::const_iterator Shell::getEndIterator() const
306    {
307        return this->outputLines_.end();
308    }
309
310    /**
311        @brief Adds a command to the history of entered commands and writes it to the config-file.
312    */
313    void Shell::addToHistory(const std::string& command)
314    {
315        if (command == "")
316            return;
317
318        size_t previous_offset = mod(static_cast<int>(this->historyOffset_) - 1, this->maxHistoryLength_);
319        if (previous_offset < this->commandHistory_.size() && command == this->commandHistory_[previous_offset])
320            return;
321
322        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
323        this->historyPosition_ = 0;
324        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
325    }
326
327    /**
328        @brief Returns a command from the history of entered commands (usually the most recent history entry, but the user can scroll through the history).
329    */
330    const std::string& Shell::getFromHistory() const
331    {
332        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
333        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
334            return this->commandHistory_[index];
335        else
336            return BLANKSTRING;
337    }
338
339    /**
340        @brief Clears the text in the input buffer.
341    */
342    void Shell::clearInput()
343    {
344        this->inputBuffer_->clear();
345        this->historyPosition_ = 0;
346        this->updateListeners<&ShellListener::inputChanged>();
347        this->updateListeners<&ShellListener::cursorChanged>();
348    }
349
350
351    // ##########################################
352    // ###   InputBuffer callback functions   ###
353    // ##########################################
354
355    /// InputBuffer callback: Called if the input changes.
356    void Shell::inputChanged()
357    {
358        this->updateListeners<&ShellListener::inputChanged>();
359        this->updateListeners<&ShellListener::cursorChanged>();
360    }
361
362    /// InputBuffer callback: Called if a key was pressed that executes a command (usually [return]).
363    void Shell::execute()
364    {
365        this->addToHistory(this->inputBuffer_->get());
366        this->updateListeners<&ShellListener::executed>();
367
368        int error;
369        const std::string& result = CommandExecutor::query(this->inputBuffer_->get(), &error);
370        if (error)
371        {
372            switch (error)
373            {
374                case CommandExecutor::Error:       this->addOutput("Error: Can't execute \"" + this->inputBuffer_->get() + "\", command doesn't exist. (S)", UserError); break;
375                case CommandExecutor::Incomplete:  this->addOutput("Error: Can't execute \"" + this->inputBuffer_->get() + "\", not enough arguments given. (S)", UserError); break;
376                case CommandExecutor::Deactivated: this->addOutput("Error: Can't execute \"" + this->inputBuffer_->get() + "\", command is not active. (S)", UserError); break;
377                case CommandExecutor::Denied:      this->addOutput("Error: Can't execute \"" + this->inputBuffer_->get() + "\", access denied. (S)", UserError); break;
378            }
379        }
380        else if (result != "")
381        {
382            this->addOutput(result, Command);
383        }
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.