2 // Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "supertux/console.hpp"
21 #include "physfs/physfs_stream.hpp"
22 #include "scripting/squirrel_util.hpp"
23 #include "supertux/gameconfig.hpp"
24 #include "supertux/main.hpp"
25 #include "video/drawing_context.hpp"
27 /// speed (pixels/s) the console closes
28 static const float FADE_SPEED = 1;
31 history_position(history.end()),
46 sq_release(Scripting::global_vm, &vm_object);
51 Console::init_graphics()
53 font.reset(new Font(Font::FIXED,"fonts/andale12.stf",1));
54 fontheight = font->get_height();
55 background.reset(new Surface("images/engine/console.png"));
56 background2.reset(new Surface("images/engine/console2.png"));
60 Console::flush(ConsoleStreamBuffer* buffer)
62 if (buffer == &outputBuffer) {
63 std::string s = outputBuffer.str();
64 if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) {
65 while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1);
67 outputBuffer.str(std::string());
76 vm = Scripting::global_vm;
77 HSQUIRRELVM new_vm = sq_newthread(vm, 16);
79 throw Scripting::SquirrelError(vm, "Couldn't create new VM thread for console");
81 // store reference to thread
82 sq_resetobject(&vm_object);
83 if(SQ_FAILED(sq_getstackobj(vm, -1, &vm_object)))
84 throw Scripting::SquirrelError(vm, "Couldn't get vm object for console");
85 sq_addref(vm, &vm_object);
88 // create new roottable for thread
90 sq_pushroottable(new_vm);
91 if(SQ_FAILED(sq_setdelegate(new_vm, -2)))
92 throw Scripting::SquirrelError(new_vm, "Couldn't set console_table delegate");
94 sq_setroottable(new_vm);
99 std::string filename = "scripts/console.nut";
100 IFileStream stream(filename);
101 Scripting::compile_and_run(vm, stream, filename);
102 } catch(std::exception& e) {
103 log_warning << "Couldn't load console.nut: " << e.what() << std::endl;
109 Console::execute_script(const std::string& command)
111 using namespace Scripting;
115 SQInteger oldtop = sq_gettop(vm);
117 if(SQ_FAILED(sq_compilebuffer(vm, command.c_str(), command.length(),
119 throw SquirrelError(vm, "Couldn't compile command");
121 sq_pushroottable(vm);
122 if(SQ_FAILED(sq_call(vm, 1, SQTrue, SQTrue)))
123 throw SquirrelError(vm, "Problem while executing command");
125 if(sq_gettype(vm, -1) != OT_NULL)
126 addLines(squirrel2string(vm, -1));
127 } catch(std::exception& e) {
130 SQInteger newtop = sq_gettop(vm);
131 if(newtop < oldtop) {
132 log_fatal << "Script destroyed squirrel stack..." << std::endl;
134 sq_settop(vm, oldtop);
139 Console::input(char c)
141 inputBuffer.insert(inputBufferPosition, 1, c);
142 inputBufferPosition++;
148 if ((inputBufferPosition > 0) && (inputBuffer.length() > 0)) {
149 inputBuffer.erase(inputBufferPosition-1, 1);
150 inputBufferPosition--;
157 if (inputBufferPosition < (int)inputBuffer.length()) {
158 inputBuffer.erase(inputBufferPosition, 1);
165 addLines("> "+inputBuffer);
168 inputBufferPosition = 0;
172 Console::scroll(int numLines)
175 if (offset > 0) offset = 0;
179 Console::show_history(int offset)
181 while ((offset > 0) && (history_position != history.end())) {
185 while ((offset < 0) && (history_position != history.begin())) {
189 if (history_position == history.end()) {
191 inputBufferPosition = 0;
193 inputBuffer = *history_position;
194 inputBufferPosition = inputBuffer.length();
199 Console::move_cursor(int offset)
201 if (offset == -65535) inputBufferPosition = 0;
202 if (offset == +65535) inputBufferPosition = inputBuffer.length();
203 inputBufferPosition+=offset;
204 if (inputBufferPosition < 0) inputBufferPosition = 0;
205 if (inputBufferPosition > (int)inputBuffer.length()) inputBufferPosition = inputBuffer.length();
208 // Helper functions for Console::autocomplete
209 // TODO: Fix rough documentation
212 void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix);
215 * Acts upon key,value on top of stack:
216 * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
217 * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
220 sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
222 const SQChar* key_chars;
223 if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
224 std::string key_string = table_prefix + key_chars;
226 switch (sq_gettype(vm, -1)) {
229 if (search_prefix.substr(0, key_string.length()) == key_string) {
231 sq_insert_commands(cmds, vm, key_string, search_prefix);
238 if (search_prefix.substr(0, key_string.length()) == key_string) {
239 sq_insert_commands(cmds, vm, key_string, search_prefix);
243 case OT_NATIVECLOSURE:
250 if (key_string.substr(0, search_prefix.length()) == search_prefix) {
251 cmds.push_back(key_string);
257 * calls sq_insert_command for all entries of table/class on top of stack
260 sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
262 sq_pushnull(vm); // push iterator
263 while (SQ_SUCCEEDED(sq_next(vm,-2))) {
264 sq_insert_command(cmds, vm, table_prefix, search_prefix);
265 sq_pop(vm, 2); // pop key, val
267 sq_pop(vm, 1); // pop iterator
271 // End of Console::autocomplete helper functions
274 Console::autocomplete()
276 //int autocompleteFrom = inputBuffer.find_last_of(" ();+", inputBufferPosition);
277 int autocompleteFrom = inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", inputBufferPosition);
278 if (autocompleteFrom != (int)std::string::npos) {
279 autocompleteFrom += 1;
281 autocompleteFrom = 0;
283 std::string prefix = inputBuffer.substr(autocompleteFrom, inputBufferPosition - autocompleteFrom);
284 addLines("> "+prefix);
286 std::list<std::string> cmds;
290 // append all keys of the current root table to list
291 sq_pushroottable(vm); // push root table
293 // check all keys (and their children) for matches
294 sq_insert_commands(cmds, vm, "", prefix);
296 // cycle through parent(delegate) table
297 SQInteger oldtop = sq_gettop(vm);
298 if(SQ_FAILED(sq_getdelegate(vm, -1)) || oldtop == sq_gettop(vm)) {
301 sq_remove(vm, -2); // remove old table
303 sq_pop(vm, 1); // remove table
305 // depending on number of hits, show matches or autocomplete
306 if (cmds.size() == 0) addLines("No known command starts with \""+prefix+"\"");
307 if (cmds.size() == 1) {
308 // one match: just replace input buffer with full command
309 std::string replaceWith = cmds.front();
310 inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
311 inputBufferPosition += (replaceWith.length() - prefix.length());
313 if (cmds.size() > 1) {
314 // multiple matches: show all matches and set input buffer to longest common prefix
315 std::string commonPrefix = cmds.front();
316 while (cmds.begin() != cmds.end()) {
317 std::string cmd = cmds.front();
320 for (int n = commonPrefix.length(); n >= 1; n--) {
321 if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
324 std::string replaceWith = commonPrefix;
325 inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
326 inputBufferPosition += (replaceWith.length() - prefix.length());
331 Console::addLines(std::string s)
333 std::istringstream iss(s);
335 while (std::getline(iss, line, '\n')) addLine(line);
339 Console::addLine(std::string s)
341 // output line to stderr
342 std::cerr << s << std::endl;
345 std::string overflow;
346 unsigned int line_count = 0;
348 lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
351 } while (s.length() > 0);
353 // trim scrollback buffer
354 while (lines.size() >= 1000)
357 // increase console height if necessary
361 height += fontheight * line_count;
364 // reset console to full opacity
367 // increase time that console stays open
373 Console::parse(std::string s)
375 // make sure we actually have something to parse
376 if (s.length() == 0) return;
378 // add line to history
379 history.push_back(s);
380 history_position = history.end();
382 // split line into list of args
383 std::vector<std::string> args;
387 start = s.find_first_not_of(" ,", end);
388 end = s.find_first_of(" ,", start);
389 if (start == s.npos) break;
390 args.push_back(s.substr(start, end-start));
393 // command is args[0]
394 if (args.size() == 0) return;
395 std::string command = args.front();
396 args.erase(args.begin());
398 // ignore if it's an internal command
399 if (consoleCommand(command,args)) return;
403 } catch(std::exception& e) {
410 Console::consoleCommand(std::string /*command*/, std::vector<std::string> /*arguments*/)
424 if(!g_config->console_enabled)
430 SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
440 // clear input buffer
442 inputBufferPosition = 0;
443 SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
449 if (Console::hasFocus()) {
458 Console::update(float elapsed_time)
461 stayOpen -= elapsed_time;
464 } else if(!focused && height > 0) {
465 alpha -= elapsed_time * FADE_SPEED;
474 Console::draw(DrawingContext& context)
479 int layer = LAYER_GUI + 1;
481 context.push_transform();
482 context.set_alpha(alpha);
483 context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), layer);
484 context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), layer);
485 for (int x = (SCREEN_WIDTH/2 - background->get_width()/2 - (static_cast<int>(ceilf((float)SCREEN_WIDTH / (float)background->get_width()) - 1) * background->get_width())); x < SCREEN_WIDTH; x+=background->get_width()) {
486 context.draw_surface(background.get(), Vector(x, height - background->get_height()), layer);
488 backgroundOffset+=10;
489 if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width();
495 float py = height-4-1 * font->get_height();
496 context.draw_text(font.get(), "> "+inputBuffer, Vector(4, py), ALIGN_LEFT, layer);
497 if (SDL_GetTicks() % 1000 < 750) {
498 int cursor_px = 2 + inputBufferPosition;
499 context.draw_text(font.get(), "_", Vector(4 + (cursor_px * font->get_text_width("X")), py), ALIGN_LEFT, layer);
503 int skipLines = -offset;
504 for (std::list<std::string>::iterator i = lines.begin(); i != lines.end(); i++) {
505 if (skipLines-- > 0) continue;
507 float py = height - 4 - lineNo*font->get_height();
508 if (py < -font->get_height()) break;
509 context.draw_text(font.get(), *i, Vector(4, py), ALIGN_LEFT, layer);
511 context.pop_transform();
514 Console* Console::instance = NULL;
515 int Console::inputBufferPosition = 0;
516 std::string Console::inputBuffer;
517 ConsoleStreamBuffer Console::outputBuffer;
518 std::ostream Console::output(&Console::outputBuffer);