Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/ai2/src/libraries/core/command/Shell.cc @ 8847

Last change on this file since 8847 was 8706, checked in by dafrick, 13 years ago

Merging presentation branch back into trunk.
There are many new features and also a lot of other changes and bugfixes, if you want to know, digg through the svn log.
Not everything is yet working as it should, but it should be fairly stable. If you habe any bug reports, just send me an email.

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