Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

A context is now defined by a struct instead of only a mask.

Introduced sub-contexts. Sub-contexts of the same main-context share the same mask, but have a different ID.
Main-contexts are filtered using a bitmask which happens for every line of output and is very fast.
Sub-contexts are filtered using a set which is slow but happens only if a specific sub-context is enabled in the config file which is usually not the case.

The concept of filtering normal output + additional contexts was moved from BaseWriter directly to OutputListener and OutputManager which makes the whole system faster.
BaseWriter now calls registerContext() for each configured output context, which basically allows the usage of more than 64 contexts as long as these contexts are not used before loading the config file. Though by design it's not recommended.

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