Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

Sorted out log level handling on startup and transferred its control back to Core.

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