Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/unity_build/src/libraries/core/command/Shell.cc @ 8524

Last change on this file since 8524 was 8524, checked in by rgrieder, 13 years ago

Fixed a serious problem with the debug levels for the Shells by introducing DevModeListener and moving the debug levels to the Shell again..

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