Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/src/libraries/core/command/Shell.cc @ 10233

Last change on this file since 10233 was 8030, checked in by landauf, 14 years ago
  • TclBind: consistent definitions of query and execute binds
  • TclBind: fixed wrong binding of crossexecute
  • removed redundant console output of the "tcl" command
  • removed tclquery and tclexecute console commands (they were just shortcuts for TclThreadManager query/execute)
  • the following tcl helper commands are now hidden in the console: error, warning, info, debug, bgerror
  • removed old console commands which shadow functions of Tcl or are unnecessary with Tcl: puts, source, read, write, append
  • 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(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.