9f1726764a00da76b72cee87ecbe799c191de419
[supertux.git] / src / supertux / console.cpp
1 //  SuperTux - Console
2 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
3 //
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.
8 //
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.
13 //
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/>.
16
17 #include "supertux/console.hpp"
18
19 #include <math.h>
20 #include <iostream>
21
22 #include "physfs/ifile_stream.hpp"
23 #include "scripting/scripting.hpp"
24 #include "scripting/squirrel_util.hpp"
25 #include "supertux/gameconfig.hpp"
26 #include "supertux/globals.hpp"
27 #include "video/drawing_context.hpp"
28
29 /// speed (pixels/s) the console closes
30 static const float FADE_SPEED = 1;
31
32 ConsoleBuffer::ConsoleBuffer() :
33   m_lines()
34 {
35 }
36
37 void
38 ConsoleBuffer::addLines(const std::string& s)
39 {
40   std::istringstream iss(s);
41   std::string line;
42   while (std::getline(iss, line, '\n'))
43   {
44     addLine(line);
45   }
46 }
47
48 void
49 ConsoleBuffer::addLine(const std::string& s_)
50 {
51   std::string s = s_;
52
53   // output line to stderr
54   std::cerr << s << std::endl;
55
56   // wrap long lines
57   std::string overflow;
58   int line_count = 0;
59   do {
60     m_lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
61     line_count += 1;
62     s = overflow;
63   } while (s.length() > 0);
64
65   // trim scrollback buffer
66   while (m_lines.size() >= 1000)
67   {
68     m_lines.pop_back();
69   }
70
71   if (Console::current())
72   {
73     Console::current()->on_buffer_change(line_count);
74   }
75 }
76
77 void
78 ConsoleBuffer::flush(ConsoleStreamBuffer& buffer)
79 {
80   if (&buffer == &s_outputBuffer)
81   {
82     std::string s = s_outputBuffer.str();
83     if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')))
84     {
85       while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))
86       {
87         s.erase(s.length()-1);
88       }
89       addLines(s);
90       s_outputBuffer.str(std::string());
91     }
92   }
93 }
94
95 Console::Console(ConsoleBuffer& buffer) :
96   m_buffer(buffer),
97   m_inputBuffer(),
98   m_inputBufferPosition(0),
99   m_history(),
100   m_history_position(m_history.end()),
101   m_background(Surface::create("images/engine/console.png")),
102   m_background2(Surface::create("images/engine/console2.png")),
103   m_vm(NULL),
104   m_vm_object(),
105   m_backgroundOffset(0),
106   m_height(0),
107   m_alpha(1.0),
108   m_offset(0),
109   m_focused(false),
110   m_font(new Font(Font::FIXED, "fonts/andale12.stf", 1)),
111   m_stayOpen(0)
112 {
113 }
114
115 Console::~Console()
116 {
117   if (m_vm != NULL)
118   {
119     sq_release(scripting::global_vm, &m_vm_object);
120   }
121 }
122
123 void
124 Console::on_buffer_change(int line_count)
125 {
126   // increase console height if necessary
127   if (m_stayOpen > 0 && m_height < 64)
128   {
129     if(m_height < 4)
130     {
131       m_height = 4;
132     }
133     m_height += m_font->get_height() * line_count;
134   }
135
136   // reset console to full opacity
137   m_alpha = 1.0;
138 }
139
140 void
141 Console::ready_vm()
142 {
143   if(m_vm == NULL) {
144     m_vm = scripting::global_vm;
145     HSQUIRRELVM new_vm = sq_newthread(m_vm, 16);
146     if(new_vm == NULL)
147       throw scripting::SquirrelError(m_vm, "Couldn't create new VM thread for console");
148
149     // store reference to thread
150     sq_resetobject(&m_vm_object);
151     if(SQ_FAILED(sq_getstackobj(m_vm, -1, &m_vm_object)))
152       throw scripting::SquirrelError(m_vm, "Couldn't get vm object for console");
153     sq_addref(m_vm, &m_vm_object);
154     sq_pop(m_vm, 1);
155
156     // create new roottable for thread
157     sq_newtable(new_vm);
158     sq_pushroottable(new_vm);
159     if(SQ_FAILED(sq_setdelegate(new_vm, -2)))
160       throw scripting::SquirrelError(new_vm, "Couldn't set console_table delegate");
161
162     sq_setroottable(new_vm);
163
164     m_vm = new_vm;
165
166     try {
167       std::string filename = "scripts/console.nut";
168       IFileStream stream(filename);
169       scripting::compile_and_run(m_vm, stream, filename);
170     } catch(std::exception& e) {
171       log_warning << "Couldn't load console.nut: " << e.what() << std::endl;
172     }
173   }
174 }
175
176 void
177 Console::execute_script(const std::string& command)
178 {
179   using namespace scripting;
180
181   ready_vm();
182
183   SQInteger oldtop = sq_gettop(m_vm);
184   try {
185     if(SQ_FAILED(sq_compilebuffer(m_vm, command.c_str(), command.length(),
186                                   "", SQTrue)))
187       throw SquirrelError(m_vm, "Couldn't compile command");
188
189     sq_pushroottable(m_vm);
190     if(SQ_FAILED(sq_call(m_vm, 1, SQTrue, SQTrue)))
191       throw SquirrelError(m_vm, "Problem while executing command");
192
193     if(sq_gettype(m_vm, -1) != OT_NULL)
194       m_buffer.addLines(squirrel2string(m_vm, -1));
195   } catch(std::exception& e) {
196     m_buffer.addLines(e.what());
197   }
198   SQInteger newtop = sq_gettop(m_vm);
199   if(newtop < oldtop) {
200     log_fatal << "Script destroyed squirrel stack..." << std::endl;
201   } else {
202     sq_settop(m_vm, oldtop);
203   }
204 }
205
206 void
207 Console::input(char c)
208 {
209   m_inputBuffer.insert(m_inputBufferPosition, 1, c);
210   m_inputBufferPosition++;
211 }
212
213 void
214 Console::backspace()
215 {
216   if ((m_inputBufferPosition > 0) && (m_inputBuffer.length() > 0)) {
217     m_inputBuffer.erase(m_inputBufferPosition-1, 1);
218     m_inputBufferPosition--;
219   }
220 }
221
222 void
223 Console::eraseChar()
224 {
225   if (m_inputBufferPosition < (int)m_inputBuffer.length()) {
226     m_inputBuffer.erase(m_inputBufferPosition, 1);
227   }
228 }
229
230 void
231 Console::enter()
232 {
233   m_buffer.addLines("> " + m_inputBuffer);
234   parse(m_inputBuffer);
235   m_inputBuffer = "";
236   m_inputBufferPosition = 0;
237 }
238
239 void
240 Console::scroll(int numLines)
241 {
242   m_offset += numLines;
243   if (m_offset > 0) m_offset = 0;
244 }
245
246 void
247 Console::show_history(int offset_)
248 {
249   while ((offset_ > 0) && (m_history_position != m_history.end())) {
250     m_history_position++;
251     offset_--;
252   }
253   while ((offset_ < 0) && (m_history_position != m_history.begin())) {
254     m_history_position--;
255     offset_++;
256   }
257   if (m_history_position == m_history.end()) {
258     m_inputBuffer = "";
259     m_inputBufferPosition = 0;
260   } else {
261     m_inputBuffer = *m_history_position;
262     m_inputBufferPosition = m_inputBuffer.length();
263   }
264 }
265
266 void
267 Console::move_cursor(int offset_)
268 {
269   if (offset_ == -65535) m_inputBufferPosition = 0;
270   if (offset_ == +65535) m_inputBufferPosition = m_inputBuffer.length();
271   m_inputBufferPosition+=offset_;
272   if (m_inputBufferPosition < 0) m_inputBufferPosition = 0;
273   if (m_inputBufferPosition > (int)m_inputBuffer.length()) m_inputBufferPosition = m_inputBuffer.length();
274 }
275
276 // Helper functions for Console::autocomplete
277 // TODO: Fix rough documentation
278 namespace {
279
280 void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix);
281
282 /**
283  * Acts upon key,value on top of stack:
284  * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
285  * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
286  */
287 void
288 sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
289 {
290   const SQChar* key_chars;
291   if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
292   std::string key_string = table_prefix + key_chars;
293
294   switch (sq_gettype(vm, -1)) {
295     case OT_INSTANCE:
296       key_string+=".";
297       if (search_prefix.substr(0, key_string.length()) == key_string) {
298         sq_getclass(vm, -1);
299         sq_insert_commands(cmds, vm, key_string, search_prefix);
300         sq_pop(vm, 1);
301       }
302       break;
303     case OT_TABLE:
304     case OT_CLASS:
305       key_string+=".";
306       if (search_prefix.substr(0, key_string.length()) == key_string) {
307         sq_insert_commands(cmds, vm, key_string, search_prefix);
308       }
309       break;
310     case OT_CLOSURE:
311     case OT_NATIVECLOSURE:
312       key_string+="()";
313       break;
314     default:
315       break;
316   }
317
318   if (key_string.substr(0, search_prefix.length()) == search_prefix) {
319     cmds.push_back(key_string);
320   }
321
322 }
323
324 /**
325  * calls sq_insert_command for all entries of table/class on top of stack
326  */
327 void
328 sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
329 {
330   sq_pushnull(vm); // push iterator
331   while (SQ_SUCCEEDED(sq_next(vm,-2))) {
332     sq_insert_command(cmds, vm, table_prefix, search_prefix);
333     sq_pop(vm, 2); // pop key, val
334   }
335   sq_pop(vm, 1); // pop iterator
336 }
337
338 }
339 // End of Console::autocomplete helper functions
340
341 void
342 Console::autocomplete()
343 {
344   //int autocompleteFrom = m_inputBuffer.find_last_of(" ();+", m_inputBufferPosition);
345   int autocompleteFrom = m_inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", m_inputBufferPosition);
346   if (autocompleteFrom != (int)std::string::npos) {
347     autocompleteFrom += 1;
348   } else {
349     autocompleteFrom = 0;
350   }
351   std::string prefix = m_inputBuffer.substr(autocompleteFrom, m_inputBufferPosition - autocompleteFrom);
352   m_buffer.addLines("> " + prefix);
353
354   std::list<std::string> cmds;
355
356   ready_vm();
357
358   // append all keys of the current root table to list
359   sq_pushroottable(m_vm); // push root table
360   while(true) {
361     // check all keys (and their children) for matches
362     sq_insert_commands(cmds, m_vm, "", prefix);
363
364     // cycle through parent(delegate) table
365     SQInteger oldtop = sq_gettop(m_vm);
366     if(SQ_FAILED(sq_getdelegate(m_vm, -1)) || oldtop == sq_gettop(m_vm)) {
367       break;
368     }
369     sq_remove(m_vm, -2); // remove old table
370   }
371   sq_pop(m_vm, 1); // remove table
372
373   // depending on number of hits, show matches or autocomplete
374   if (cmds.empty())
375   {
376     m_buffer.addLines("No known command starts with \"" + prefix + "\"");
377   }
378
379   if (cmds.size() == 1)
380   {
381     // one match: just replace input buffer with full command
382     std::string replaceWith = cmds.front();
383     m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
384     m_inputBufferPosition += (replaceWith.length() - prefix.length());
385   }
386
387   if (cmds.size() > 1)
388   {
389     // multiple matches: show all matches and set input buffer to longest common prefix
390     std::string commonPrefix = cmds.front();
391     while (cmds.begin() != cmds.end()) {
392       std::string cmd = cmds.front();
393       cmds.pop_front();
394       m_buffer.addLines(cmd);
395       for (int n = commonPrefix.length(); n >= 1; n--) {
396         if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
397       }
398     }
399     std::string replaceWith = commonPrefix;
400     m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
401     m_inputBufferPosition += (replaceWith.length() - prefix.length());
402   }
403 }
404
405 void
406 Console::parse(std::string s)
407 {
408   // make sure we actually have something to parse
409   if (s.length() == 0) return;
410
411   // add line to history
412   m_history.push_back(s);
413   m_history_position = m_history.end();
414
415   // split line into list of args
416   std::vector<std::string> args;
417   size_t start = 0;
418   size_t end = 0;
419   while (1) {
420     start = s.find_first_not_of(" ,", end);
421     end = s.find_first_of(" ,", start);
422     if (start == s.npos) break;
423     args.push_back(s.substr(start, end-start));
424   }
425
426   // command is args[0]
427   if (args.size() == 0) return;
428   std::string command = args.front();
429   args.erase(args.begin());
430
431   // ignore if it's an internal command
432   if (consoleCommand(command,args)) return;
433
434   try {
435     execute_script(s);
436   } catch(std::exception& e) {
437     m_buffer.addLines(e.what());
438   }
439 }
440
441 bool
442 Console::consoleCommand(std::string /*command*/, std::vector<std::string> /*arguments*/)
443 {
444   return false;
445 }
446
447 bool
448 Console::hasFocus()
449 {
450   return m_focused;
451 }
452
453 void
454 Console::show()
455 {
456   if(!g_config->console_enabled)
457     return;
458
459   m_focused = true;
460   m_height = 256;
461   m_alpha = 1.0;
462 //  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // Useless in SDL2 :  if you want to disable repeat, then you need to check if the key was repeated and ignore it.
463 }
464
465 void
466 Console::open()
467 {
468   if(m_stayOpen < 2)
469     m_stayOpen += 1.5;
470 }
471
472 void
473 Console::hide()
474 {
475   m_focused = false;
476   m_height = 0;
477   m_stayOpen = 0;
478
479   // clear input buffer
480   m_inputBuffer = "";
481   m_inputBufferPosition = 0;
482  // SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
483 }
484
485 void
486 Console::toggle()
487 {
488   if (Console::hasFocus()) {
489     Console::hide();
490   }
491   else {
492     Console::show();
493   }
494 }
495
496 void
497 Console::update(float elapsed_time)
498 {
499   if(m_stayOpen > 0) {
500     m_stayOpen -= elapsed_time;
501     if(m_stayOpen < 0)
502       m_stayOpen = 0;
503   } else if(!m_focused && m_height > 0) {
504     m_alpha -= elapsed_time * FADE_SPEED;
505     if(m_alpha < 0) {
506       m_alpha = 0;
507       m_height = 0;
508     }
509   }
510 }
511
512 void
513 Console::draw(DrawingContext& context)
514 {
515   if (m_height == 0)
516     return;
517
518   int layer = LAYER_GUI + 1;
519
520   context.push_transform();
521   context.set_alpha(m_alpha);
522   context.draw_surface(m_background2,
523                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 - m_background->get_width() + m_backgroundOffset,
524                               m_height - m_background->get_height()),
525                        layer);
526   context.draw_surface(m_background2,
527                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 + m_backgroundOffset,
528                               m_height - m_background->get_height()),
529                        layer);
530   for (int x = (SCREEN_WIDTH/2 - m_background->get_width()/2
531                 - (static_cast<int>(ceilf((float)SCREEN_WIDTH /
532                                           (float)m_background->get_width()) - 1) * m_background->get_width()));
533        x < SCREEN_WIDTH;
534        x += m_background->get_width())
535   {
536     context.draw_surface(m_background, Vector(x, m_height - m_background->get_height()), layer);
537   }
538   m_backgroundOffset+=10;
539   if (m_backgroundOffset > (int)m_background->get_width()) m_backgroundOffset -= (int)m_background->get_width();
540
541   int lineNo = 0;
542
543   if (m_focused) {
544     lineNo++;
545     float py = m_height-4-1 * m_font->get_height();
546     context.draw_text(m_font, "> "+m_inputBuffer, Vector(4, py), ALIGN_LEFT, layer);
547     if (SDL_GetTicks() % 1000 < 750) {
548       int cursor_px = 2 + m_inputBufferPosition;
549       context.draw_text(m_font, "_", Vector(4 + (cursor_px * m_font->get_text_width("X")), py), ALIGN_LEFT, layer);
550     }
551   }
552
553   int skipLines = -m_offset;
554   for (std::list<std::string>::iterator i = m_buffer.m_lines.begin(); i != m_buffer.m_lines.end(); i++)
555   {
556     if (skipLines-- > 0) continue;
557     lineNo++;
558     float py = m_height - 4 - lineNo * m_font->get_height();
559     if (py < -m_font->get_height()) break;
560     context.draw_text(m_font, *i, Vector(4, py), ALIGN_LEFT, layer);
561   }
562   context.pop_transform();
563 }
564
565 ConsoleStreamBuffer ConsoleBuffer::s_outputBuffer;
566 std::ostream ConsoleBuffer::output(&ConsoleBuffer::s_outputBuffer);
567
568 /* EOF */