Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/gamecontent/src/libraries/core/command/Shell.cc @ 9215

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

merged output branch back to trunk.

Changes:

  • you have to include util/Output.h instead of util/Debug.h
  • COUT(x) is now called orxout(level)
  • output levels are now defined by an enum instead of numbers. see util/Output.h for the definition
  • it's possible to use output contexts with orxout(level, context). see util/Output.h for some common contexts. you can define more contexts
  • you must use 'endl' at the end of an output message, '\n' does not flush the message

Output levels:

  • instead of COUT(0) use orxout()
  • instead of COUT(1) use orxout(user_error) or orxout(internal_error)
  • instead of COUT(2) use orxout(user_warning) or orxout(internal_warning)
  • instead of COUT(3) use orxout(user_status/user_info) or orxout(internal_status/internal_info)
  • instead of COUT(4) use orxout(verbose)
  • instead of COUT(5) use orxout(verbose_more)
  • instead of COUT(6) use orxout(verbose_ultra)

Guidelines:

  • user_* levels are for the user, visible in the console and the log-file
  • internal_* levels are for developers, visible in the log-file
  • verbose_* levels are for debugging, only visible if the context of the output is activated

Usage in C++:

  • orxout() << "message" << endl;
  • orxout(level) << "message" << endl;
  • orxout(level, context) << "message" << endl;

Usage in Lua:

  • orxout("message")
  • orxout(orxonox.level.levelname, "message")
  • orxout(orxonox.level.levelname, "context", "message")

Usage in Tcl (and in the in-game-console):

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