Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Shell and its derivatives are now based on the new output system

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