Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Removed debugLevel_ from Shell, using correct variable inherited from BaseWriter as config value.
Fixed OutputListener::setLevelMax() which ignored the new output level "message".
Fixed using a boolean called "verbose" instead of the output level with the same name in Loader.
Preventing further problems of this kind by defining OutputLevel as an enum.

  • 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::changedConfigurableLevels);
126        SetConfigValueExternal(this->configurableContextsMaxLevel_,
127                               this->getConfigurableSectionName(),
128                               this->getConfigurableContextsMaxLevelName(),
129                               this->configurableContextsMaxLevel_)
130            .description("The maximum level of output shown in the " + this->getName() + " for additional contexts")
131            .callback(static_cast<BaseWriter*>(this), &BaseWriter::changedConfigurableLevels);
132        SetConfigValueExternal(this->configurableContexts_,
133                               this->getConfigurableSectionName(),
134                               this->getConfigurableContextsName(),
135                               this->configurableContexts_)
136            .description("Additional output contexts shown in the " + this->getName())
137            .callback(static_cast<BaseWriter*>(this), &BaseWriter::changedConfigurableLevels);
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.