Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6536 was 6536, checked in by rgrieder, 14 years ago

Merged revisions 6430-6440 from the gamestate branch to the trunk.
This adds keybindings merging functionality.

(from log of r6437)
When running development builds, the keybinder will merge the local file and the one from the data folder.
Catch: if you want to remove a binding, you'll have to write "NoBinding" (not case sensitive) to override the default command

The keybind command already does that for you though.

  • Property svn:eol-style set to native
File size: 15.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#include "Shell.h"
30
31#include "util/OutputHandler.h"
32#include "util/StringUtils.h"
33#include "util/SubString.h"
34#include "CommandExecutor.h"
35#include "CoreIncludes.h"
36#include "ConfigFileManager.h"
37#include "ConfigValueIncludes.h"
38#include "ConsoleCommand.h"
39
40namespace orxonox
41{
42    SetConsoleCommandShortcut(OutputHandler, log);
43    SetConsoleCommandShortcut(OutputHandler, error);
44    SetConsoleCommandShortcut(OutputHandler, warning);
45    SetConsoleCommandShortcut(OutputHandler, info);
46    SetConsoleCommandShortcut(OutputHandler, debug);
47
48    Shell::Shell(const std::string& consoleName, bool bScrollable)
49        : OutputListener(consoleName)
50        , inputBuffer_(new InputBuffer())
51        , consoleName_(consoleName)
52        , bScrollable_(bScrollable)
53    {
54        RegisterRootObject(Shell);
55
56        this->scrollPosition_ = 0;
57        this->maxHistoryLength_ = 100;
58        this->historyPosition_ = 0;
59        this->historyOffset_ = 0;
60        this->bFinishedLastLine_ = true;
61
62        this->clearOutput();
63        this->configureInputBuffer();
64
65        // Specify file for the command history
66        ConfigFileManager::getInstance().setFilename(ConfigFileType::CommandHistory, "commandHistory.ini");
67
68        // Use a stringstream object to buffer the output
69        this->outputStream_ = &this->outputBuffer_;
70
71        this->setConfigValues();
72
73        // Get the previous output and add it to the Shell
74        for (OutputHandler::OutputVectorIterator it = OutputHandler::getInstance().getOutputVectorBegin();
75            it != OutputHandler::getInstance().getOutputVectorEnd(); ++it)
76        {
77            if (it->first <= this->getSoftDebugLevel())
78            {
79                this->outputBuffer_ << it->second;
80                this->outputChanged(it->first);
81            }
82        }
83
84        // Register the shell as output listener
85        OutputHandler::getInstance().registerOutputListener(this);
86    }
87
88    Shell::~Shell()
89    {
90        OutputHandler::getInstance().unregisterOutputListener(this);
91        this->inputBuffer_->destroy();
92    }
93
94    void Shell::setConfigValues()
95    {
96        SetConfigValue(maxHistoryLength_, 100)
97            .callback(this, &Shell::commandHistoryLengthChanged);
98        SetConfigValue(historyOffset_, 0)
99            .callback(this, &Shell::commandHistoryOffsetChanged);
100        setConfigValueGeneric(this, &commandHistory_, ConfigFileType::CommandHistory, "Shell", "commandHistory_", std::vector<std::string>());
101
102#ifdef ORXONOX_RELEASE
103        const unsigned int defaultLevel = 1;
104#else
105        const unsigned int defaultLevel = 3;
106#endif
107        setConfigValueGeneric(this, &softDebugLevel_, ConfigFileType::Settings, "OutputHandler", "softDebugLevel" + this->consoleName_, defaultLevel)
108            .description("The maximal level of debug output shown in the Shell");
109        this->setSoftDebugLevel(this->softDebugLevel_);
110    }
111
112    void Shell::commandHistoryOffsetChanged()
113    {
114        if (this->historyOffset_ >= this->maxHistoryLength_)
115            this->historyOffset_ = 0;
116    }
117
118    void Shell::commandHistoryLengthChanged()
119    {
120        this->commandHistoryOffsetChanged();
121
122        while (this->commandHistory_.size() > this->maxHistoryLength_)
123        {
124            unsigned int index = this->commandHistory_.size() - 1;
125            this->commandHistory_.erase(this->commandHistory_.begin() + index);
126            ModifyConfigValue(commandHistory_, remove, index);
127        }
128    }
129
130    void Shell::configureInputBuffer()
131    {
132        this->inputBuffer_->registerListener(this, &Shell::inputChanged, true);
133        this->inputBuffer_->registerListener(this, &Shell::execute,         '\r',   false);
134        this->inputBuffer_->registerListener(this, &Shell::execute,         '\n',   false);
135        this->inputBuffer_->registerListener(this, &Shell::hintAndComplete, '\t',   true);
136        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\b',   true);
137        this->inputBuffer_->registerListener(this, &Shell::backspace,       '\177', true);
138        this->inputBuffer_->registerListener(this, &Shell::exit,            '\033', true); // escape
139        this->inputBuffer_->registerListener(this, &Shell::deleteChar,      KeyCode::Delete);
140        this->inputBuffer_->registerListener(this, &Shell::cursorRight,     KeyCode::Right);
141        this->inputBuffer_->registerListener(this, &Shell::cursorLeft,      KeyCode::Left);
142        this->inputBuffer_->registerListener(this, &Shell::cursorEnd,       KeyCode::End);
143        this->inputBuffer_->registerListener(this, &Shell::cursorHome,      KeyCode::Home);
144        this->inputBuffer_->registerListener(this, &Shell::historyUp,       KeyCode::Up);
145        this->inputBuffer_->registerListener(this, &Shell::historyDown,     KeyCode::Down);
146        if (this->bScrollable_)
147        {
148            this->inputBuffer_->registerListener(this, &Shell::scrollUp,    KeyCode::PageUp);
149            this->inputBuffer_->registerListener(this, &Shell::scrollDown,  KeyCode::PageDown);
150        }
151        else
152        {
153            this->inputBuffer_->registerListener(this, &Shell::historySearchUp,   KeyCode::PageUp);
154            this->inputBuffer_->registerListener(this, &Shell::historySearchDown, KeyCode::PageDown);
155        }
156    }
157
158    /*
159    void Shell::history()
160    {
161        Shell& instance = Shell::getInstance();
162
163        for (unsigned int i = instance.historyOffset_; i < instance.commandHistory_.size(); ++i)
164            instance.addOutput(instance.commandHistory_[i] + '\n', -1);
165        for (unsigned int i =  0; i < instance.historyOffset_; ++i)
166            instance.addOutput(instance.commandHistory_[i] + '\n', -1);
167    }
168    */
169
170    void Shell::registerListener(ShellListener* listener)
171    {
172        this->listeners_.push_back(listener);
173    }
174
175    void Shell::unregisterListener(ShellListener* listener)
176    {
177        for (std::list<ShellListener*>::iterator it = this->listeners_.begin(); it != this->listeners_.end(); )
178        {
179            if ((*it) == listener)
180                it = this->listeners_.erase(it);
181            else
182                ++it;
183        }
184    }
185
186    void Shell::setCursorPosition(unsigned int cursor)
187    {
188        this->inputBuffer_->setCursorPosition(cursor);
189        this->updateListeners<&ShellListener::cursorChanged>();
190    }
191
192    void Shell::addOutput(const std::string& text, LineType type)
193    {
194        this->outputBuffer_ << text;
195        this->outputChanged(type);
196    }
197
198    void Shell::clearOutput()
199    {
200        this->outputLines_.clear();
201        this->scrollIterator_ = this->outputLines_.begin();
202
203        this->scrollPosition_ = 0;
204        this->bFinishedLastLine_ = true;
205
206        this->updateListeners<&ShellListener::linesChanged>();
207    }
208
209    Shell::LineList::const_iterator Shell::getNewestLineIterator() const
210    {
211        if (this->scrollPosition_)
212            return this->scrollIterator_;
213        else
214            return this->outputLines_.begin();
215    }
216
217    Shell::LineList::const_iterator Shell::getEndIterator() const
218    {
219        return this->outputLines_.end();
220    }
221
222    void Shell::addToHistory(const std::string& command)
223    {
224        ModifyConfigValue(commandHistory_, set, this->historyOffset_, command);
225        this->historyPosition_ = 0;
226        ModifyConfigValue(historyOffset_, set, (this->historyOffset_ + 1) % this->maxHistoryLength_);
227    }
228
229    const std::string& Shell::getFromHistory() const
230    {
231        unsigned int index = mod(static_cast<int>(this->historyOffset_) - static_cast<int>(this->historyPosition_), this->maxHistoryLength_);
232        if (index < this->commandHistory_.size() && this->historyPosition_ != 0)
233            return this->commandHistory_[index];
234        else
235            return BLANKSTRING;
236    }
237
238    void Shell::outputChanged(int lineType)
239    {
240        bool newline = false;
241        do
242        {
243            std::string output;
244            std::getline(this->outputBuffer_, output);
245
246            bool eof = this->outputBuffer_.eof();
247            bool fail = this->outputBuffer_.fail();
248            if (eof)
249                this->outputBuffer_.flush();
250            if (eof || fail)
251                this->outputBuffer_.clear();
252            newline = (!eof && !fail);
253
254            if (!newline && output.empty())
255                break;
256
257            if (this->bFinishedLastLine_)
258            {
259                this->outputLines_.push_front(std::make_pair(output, static_cast<LineType>(lineType)));
260
261                if (this->scrollPosition_)
262                    this->scrollPosition_++;
263                else
264                    this->scrollIterator_ = this->outputLines_.begin();
265
266                this->bFinishedLastLine_ = newline;
267
268                if (!this->scrollPosition_)
269                    this->updateListeners<&ShellListener::lineAdded>();
270            }
271            else
272            {
273                this->outputLines_.front().first += output;
274                this->bFinishedLastLine_ = newline;
275                this->updateListeners<&ShellListener::onlyLastLineChanged>();
276            }
277            this->bFinishedLastLine_ = newline;
278
279        } while (newline);
280    }
281
282    void Shell::clearInput()
283    {
284        this->inputBuffer_->clear();
285        this->historyPosition_ = 0;
286        this->updateListeners<&ShellListener::inputChanged>();
287        this->updateListeners<&ShellListener::cursorChanged>();
288    }
289
290    void Shell::setPromptPrefix(const std::string& str)
291    {
292    }
293
294
295    // ##########################################
296    // ###   InputBuffer callback functions   ###
297    // ##########################################
298
299    void Shell::inputChanged()
300    {
301        this->updateListeners<&ShellListener::inputChanged>();
302        this->updateListeners<&ShellListener::cursorChanged>();
303    }
304
305    void Shell::execute()
306    {
307        this->addToHistory(this->inputBuffer_->get());
308        this->updateListeners<&ShellListener::executed>();
309
310        if (!CommandExecutor::execute(this->inputBuffer_->get()))
311        {
312            this->outputBuffer_ << "Error: Can't execute \"" << this->inputBuffer_->get() << "\"." << std::endl;
313            this->outputChanged(Error);
314        }
315
316        this->clearInput();
317    }
318
319    void Shell::hintAndComplete()
320    {
321        this->inputBuffer_->set(CommandExecutor::complete(this->inputBuffer_->get()));
322        this->outputBuffer_ << CommandExecutor::hint(this->inputBuffer_->get()) << std::endl;
323        this->outputChanged(Hint);
324
325        this->inputChanged();
326    }
327
328    void Shell::backspace()
329    {
330        this->inputBuffer_->removeBehindCursor();
331        this->updateListeners<&ShellListener::inputChanged>();
332        this->updateListeners<&ShellListener::cursorChanged>();
333    }
334
335    void Shell::exit()
336    {
337        if (this->inputBuffer_->getSize() > 0)
338        {
339            this->clearInput();
340            return;
341        }
342
343        this->clearInput();
344        this->scrollPosition_ = 0;
345        this->scrollIterator_ = this->outputLines_.begin();
346
347        this->updateListeners<&ShellListener::exit>();
348    }
349
350    void Shell::deleteChar()
351    {
352        this->inputBuffer_->removeAtCursor();
353        this->updateListeners<&ShellListener::inputChanged>();
354    }
355
356    void Shell::cursorRight()
357    {
358        this->inputBuffer_->increaseCursor();
359        this->updateListeners<&ShellListener::cursorChanged>();
360    }
361
362    void Shell::cursorLeft()
363    {
364        this->inputBuffer_->decreaseCursor();
365        this->updateListeners<&ShellListener::cursorChanged>();
366    }
367
368    void Shell::cursorEnd()
369    {
370        this->inputBuffer_->setCursorToEnd();
371        this->updateListeners<&ShellListener::cursorChanged>();
372    }
373
374    void Shell::cursorHome()
375    {
376        this->inputBuffer_->setCursorToBegin();
377        this->updateListeners<&ShellListener::cursorChanged>();
378    }
379
380    void Shell::historyUp()
381    {
382        if (this->historyPosition_ < this->commandHistory_.size())
383        {
384            this->historyPosition_++;
385            this->inputBuffer_->set(this->getFromHistory());
386        }
387    }
388
389    void Shell::historyDown()
390    {
391        if (this->historyPosition_ > 0)
392        {
393            this->historyPosition_--;
394            this->inputBuffer_->set(this->getFromHistory());
395        }
396    }
397
398    void Shell::historySearchUp()
399    {
400        if (this->historyPosition_ == this->historyOffset_)
401            return;
402        unsigned int cursorPosition = this->getCursorPosition();
403        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
404        for (unsigned int newPos = this->historyPosition_ + 1; newPos <= this->historyOffset_; newPos++)
405        {
406            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // search case insensitive
407            {
408                this->historyPosition_ = newPos;
409                this->inputBuffer_->set(this->getFromHistory());
410                this->setCursorPosition(cursorPosition);
411                return;
412            }
413        }
414    }
415
416    void Shell::historySearchDown()
417    {
418        if (this->historyPosition_ == 0)
419            return;
420        unsigned int cursorPosition = this->getCursorPosition();
421        const std::string& input_str(this->getInput().substr(0, cursorPosition)); // only search for the expression from the beginning
422        for (unsigned int newPos = this->historyPosition_ - 1; newPos > 0; newPos--)
423        {
424            if (getLowercase(this->commandHistory_[this->historyOffset_ - newPos]).find(getLowercase(input_str)) == 0) // sear$
425            {
426                this->historyPosition_ = newPos;
427                this->inputBuffer_->set(this->getFromHistory());
428                this->setCursorPosition(cursorPosition);
429                return;
430            }
431        }
432    }
433
434    void Shell::scrollUp()
435    {
436        if (this->scrollIterator_ != this->outputLines_.end())
437        {
438            ++this->scrollIterator_;
439            ++this->scrollPosition_;
440
441            this->updateListeners<&ShellListener::linesChanged>();
442        }
443    }
444
445    void Shell::scrollDown()
446    {
447        if (this->scrollIterator_ != this->outputLines_.begin())
448        {
449            --this->scrollIterator_;
450            --this->scrollPosition_;
451
452            this->updateListeners<&ShellListener::linesChanged>();
453        }
454    }
455}
Note: See TracBrowser for help on using the repository browser.