Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

added console command "orxout" (and also "orxout_context")
re-added the shortcut commands log, error, warning, status, info, and debug
adjusted init.tcl

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