Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/Shell.cc @ 8729

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

Merged unity_build branch back to trunk.

Features:

  • Implemented fully automatic build units to speed up compilation if requested
  • Added DOUT macro for quick debug output
  • Activated text colouring in the POSIX IOConsole
  • DeclareToluaInterface is not necessary anymore

Improvements:

  • Output levels now change appropriately when switch back and forth from dev mode
  • Log level for the file output is now also correct during startup
  • Removed some header file dependencies in core and tools to speed up compilation

no more file for command line options

  • Improved util::tribool by adapting some concepts from boost::tribool

Regressions:

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