Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

added flag to disable automatic registration of output listeners. avoids crash during creation of a Shell if some verbose context (object_list?) is activated.
different handling of sub-contexts whose main-context is also explicitly registered (even though that's not the intended way to use)

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