1d27bc87b201c16cf04f58dc59fca4b8ffcd7f55
[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   if (!m_font)
127   {
128     // FIXME: This is an ugly workaround for a crash at startup.
129     // Console::current() becomes valid before the Console constructor
130     // is finished and loading Surfaces and Fonts wants to write text
131     // to the Console, with Fonts that aren't yet loaded, thus
132     // crashing
133     return;
134   }
135
136   // increase console height if necessary
137   if (m_stayOpen > 0 && m_height < 64)
138   {
139     if(m_height < 4)
140     {
141       m_height = 4;
142     }
143     m_height += m_font->get_height() * line_count;
144   }
145
146   // reset console to full opacity
147   m_alpha = 1.0;
148 }
149
150 void
151 Console::ready_vm()
152 {
153   if(m_vm == NULL) {
154     m_vm = scripting::global_vm;
155     HSQUIRRELVM new_vm = sq_newthread(m_vm, 16);
156     if(new_vm == NULL)
157       throw scripting::SquirrelError(m_vm, "Couldn't create new VM thread for console");
158
159     // store reference to thread
160     sq_resetobject(&m_vm_object);
161     if(SQ_FAILED(sq_getstackobj(m_vm, -1, &m_vm_object)))
162       throw scripting::SquirrelError(m_vm, "Couldn't get vm object for console");
163     sq_addref(m_vm, &m_vm_object);
164     sq_pop(m_vm, 1);
165
166     // create new roottable for thread
167     sq_newtable(new_vm);
168     sq_pushroottable(new_vm);
169     if(SQ_FAILED(sq_setdelegate(new_vm, -2)))
170       throw scripting::SquirrelError(new_vm, "Couldn't set console_table delegate");
171
172     sq_setroottable(new_vm);
173
174     m_vm = new_vm;
175
176     try {
177       std::string filename = "scripts/console.nut";
178       IFileStream stream(filename);
179       scripting::compile_and_run(m_vm, stream, filename);
180     } catch(std::exception& e) {
181       log_warning << "Couldn't load console.nut: " << e.what() << std::endl;
182     }
183   }
184 }
185
186 void
187 Console::execute_script(const std::string& command)
188 {
189   using namespace scripting;
190
191   ready_vm();
192
193   SQInteger oldtop = sq_gettop(m_vm);
194   try {
195     if(SQ_FAILED(sq_compilebuffer(m_vm, command.c_str(), command.length(),
196                                   "", SQTrue)))
197       throw SquirrelError(m_vm, "Couldn't compile command");
198
199     sq_pushroottable(m_vm);
200     if(SQ_FAILED(sq_call(m_vm, 1, SQTrue, SQTrue)))
201       throw SquirrelError(m_vm, "Problem while executing command");
202
203     if(sq_gettype(m_vm, -1) != OT_NULL)
204       m_buffer.addLines(squirrel2string(m_vm, -1));
205   } catch(std::exception& e) {
206     m_buffer.addLines(e.what());
207   }
208   SQInteger newtop = sq_gettop(m_vm);
209   if(newtop < oldtop) {
210     log_fatal << "Script destroyed squirrel stack..." << std::endl;
211   } else {
212     sq_settop(m_vm, oldtop);
213   }
214 }
215
216 void
217 Console::input(char c)
218 {
219   m_inputBuffer.insert(m_inputBufferPosition, 1, c);
220   m_inputBufferPosition++;
221 }
222
223 void
224 Console::backspace()
225 {
226   if ((m_inputBufferPosition > 0) && (m_inputBuffer.length() > 0)) {
227     m_inputBuffer.erase(m_inputBufferPosition-1, 1);
228     m_inputBufferPosition--;
229   }
230 }
231
232 void
233 Console::eraseChar()
234 {
235   if (m_inputBufferPosition < (int)m_inputBuffer.length()) {
236     m_inputBuffer.erase(m_inputBufferPosition, 1);
237   }
238 }
239
240 void
241 Console::enter()
242 {
243   m_buffer.addLines("> " + m_inputBuffer);
244   parse(m_inputBuffer);
245   m_inputBuffer = "";
246   m_inputBufferPosition = 0;
247 }
248
249 void
250 Console::scroll(int numLines)
251 {
252   m_offset += numLines;
253   if (m_offset > 0) m_offset = 0;
254 }
255
256 void
257 Console::show_history(int offset_)
258 {
259   while ((offset_ > 0) && (m_history_position != m_history.end())) {
260     ++m_history_position;
261     offset_--;
262   }
263   while ((offset_ < 0) && (m_history_position != m_history.begin())) {
264     --m_history_position;
265     offset_++;
266   }
267   if (m_history_position == m_history.end()) {
268     m_inputBuffer = "";
269     m_inputBufferPosition = 0;
270   } else {
271     m_inputBuffer = *m_history_position;
272     m_inputBufferPosition = m_inputBuffer.length();
273   }
274 }
275
276 void
277 Console::move_cursor(int offset_)
278 {
279   if (offset_ == -65535) m_inputBufferPosition = 0;
280   if (offset_ == +65535) m_inputBufferPosition = m_inputBuffer.length();
281   m_inputBufferPosition+=offset_;
282   if (m_inputBufferPosition < 0) m_inputBufferPosition = 0;
283   if (m_inputBufferPosition > (int)m_inputBuffer.length()) m_inputBufferPosition = m_inputBuffer.length();
284 }
285
286 // Helper functions for Console::autocomplete
287 // TODO: Fix rough documentation
288 namespace {
289
290 void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix);
291
292 /**
293  * Acts upon key,value on top of stack:
294  * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
295  * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
296  */
297 void
298 sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
299 {
300   const SQChar* key_chars;
301   if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
302   std::string key_string = table_prefix + key_chars;
303
304   switch (sq_gettype(vm, -1)) {
305     case OT_INSTANCE:
306       key_string+=".";
307       if (search_prefix.substr(0, key_string.length()) == key_string) {
308         sq_getclass(vm, -1);
309         sq_insert_commands(cmds, vm, key_string, search_prefix);
310         sq_pop(vm, 1);
311       }
312       break;
313     case OT_TABLE:
314     case OT_CLASS:
315       key_string+=".";
316       if (search_prefix.substr(0, key_string.length()) == key_string) {
317         sq_insert_commands(cmds, vm, key_string, search_prefix);
318       }
319       break;
320     case OT_CLOSURE:
321     case OT_NATIVECLOSURE:
322       key_string+="()";
323       break;
324     default:
325       break;
326   }
327
328   if (key_string.substr(0, search_prefix.length()) == search_prefix) {
329     cmds.push_back(key_string);
330   }
331
332 }
333
334 /**
335  * calls sq_insert_command for all entries of table/class on top of stack
336  */
337 void
338 sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, std::string table_prefix, std::string search_prefix)
339 {
340   sq_pushnull(vm); // push iterator
341   while (SQ_SUCCEEDED(sq_next(vm,-2))) {
342     sq_insert_command(cmds, vm, table_prefix, search_prefix);
343     sq_pop(vm, 2); // pop key, val
344   }
345   sq_pop(vm, 1); // pop iterator
346 }
347
348 }
349 // End of Console::autocomplete helper functions
350
351 void
352 Console::autocomplete()
353 {
354   //int autocompleteFrom = m_inputBuffer.find_last_of(" ();+", m_inputBufferPosition);
355   int autocompleteFrom = m_inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", m_inputBufferPosition);
356   if (autocompleteFrom != (int)std::string::npos) {
357     autocompleteFrom += 1;
358   } else {
359     autocompleteFrom = 0;
360   }
361   std::string prefix = m_inputBuffer.substr(autocompleteFrom, m_inputBufferPosition - autocompleteFrom);
362   m_buffer.addLines("> " + prefix);
363
364   std::list<std::string> cmds;
365
366   ready_vm();
367
368   // append all keys of the current root table to list
369   sq_pushroottable(m_vm); // push root table
370   while(true) {
371     // check all keys (and their children) for matches
372     sq_insert_commands(cmds, m_vm, "", prefix);
373
374     // cycle through parent(delegate) table
375     SQInteger oldtop = sq_gettop(m_vm);
376     if(SQ_FAILED(sq_getdelegate(m_vm, -1)) || oldtop == sq_gettop(m_vm)) {
377       break;
378     }
379     sq_remove(m_vm, -2); // remove old table
380   }
381   sq_pop(m_vm, 1); // remove table
382
383   // depending on number of hits, show matches or autocomplete
384   if (cmds.empty())
385   {
386     m_buffer.addLines("No known command starts with \"" + prefix + "\"");
387   }
388
389   if (cmds.size() == 1)
390   {
391     // one match: just replace input buffer with full command
392     std::string replaceWith = cmds.front();
393     m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
394     m_inputBufferPosition += (replaceWith.length() - prefix.length());
395   }
396
397   if (cmds.size() > 1)
398   {
399     // multiple matches: show all matches and set input buffer to longest common prefix
400     std::string commonPrefix = cmds.front();
401     while (cmds.begin() != cmds.end()) {
402       std::string cmd = cmds.front();
403       cmds.pop_front();
404       m_buffer.addLines(cmd);
405       for (int n = commonPrefix.length(); n >= 1; n--) {
406         if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
407       }
408     }
409     std::string replaceWith = commonPrefix;
410     m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
411     m_inputBufferPosition += (replaceWith.length() - prefix.length());
412   }
413 }
414
415 void
416 Console::parse(std::string s)
417 {
418   // make sure we actually have something to parse
419   if (s.length() == 0) return;
420
421   // add line to history
422   m_history.push_back(s);
423   m_history_position = m_history.end();
424
425   // split line into list of args
426   std::vector<std::string> args;
427   size_t end = 0;
428   while (1) {
429     size_t start = s.find_first_not_of(" ,", end);
430     end = s.find_first_of(" ,", start);
431     if (start == s.npos) break;
432     args.push_back(s.substr(start, end-start));
433   }
434
435   // command is args[0]
436   if (args.size() == 0) return;
437   std::string command = args.front();
438   args.erase(args.begin());
439
440   // ignore if it's an internal command
441   if (consoleCommand(command,args)) return;
442
443   try {
444     execute_script(s);
445   } catch(std::exception& e) {
446     m_buffer.addLines(e.what());
447   }
448 }
449
450 bool
451 Console::consoleCommand(std::string /*command*/, std::vector<std::string> /*arguments*/)
452 {
453   return false;
454 }
455
456 bool
457 Console::hasFocus()
458 {
459   return m_focused;
460 }
461
462 void
463 Console::show()
464 {
465   if(!g_config->console_enabled)
466     return;
467
468   m_focused = true;
469   m_height = 256;
470   m_alpha = 1.0;
471 //  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.
472 }
473
474 void
475 Console::open()
476 {
477   if(m_stayOpen < 2)
478     m_stayOpen += 1.5;
479 }
480
481 void
482 Console::hide()
483 {
484   m_focused = false;
485   m_height = 0;
486   m_stayOpen = 0;
487
488   // clear input buffer
489   m_inputBuffer = "";
490   m_inputBufferPosition = 0;
491  // SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
492 }
493
494 void
495 Console::toggle()
496 {
497   if (Console::hasFocus()) {
498     Console::hide();
499   }
500   else {
501     Console::show();
502   }
503 }
504
505 void
506 Console::update(float elapsed_time)
507 {
508   if(m_stayOpen > 0) {
509     m_stayOpen -= elapsed_time;
510     if(m_stayOpen < 0)
511       m_stayOpen = 0;
512   } else if(!m_focused && m_height > 0) {
513     m_alpha -= elapsed_time * FADE_SPEED;
514     if(m_alpha < 0) {
515       m_alpha = 0;
516       m_height = 0;
517     }
518   }
519 }
520
521 void
522 Console::draw(DrawingContext& context)
523 {
524   if (m_height == 0)
525     return;
526
527   int layer = LAYER_GUI + 1;
528
529   context.push_transform();
530   context.set_alpha(m_alpha);
531   context.draw_surface(m_background2,
532                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 - m_background->get_width() + m_backgroundOffset,
533                               m_height - m_background->get_height()),
534                        layer);
535   context.draw_surface(m_background2,
536                        Vector(SCREEN_WIDTH/2 - m_background->get_width()/2 + m_backgroundOffset,
537                               m_height - m_background->get_height()),
538                        layer);
539   for (int x = (SCREEN_WIDTH/2 - m_background->get_width()/2
540                 - (static_cast<int>(ceilf((float)SCREEN_WIDTH /
541                                           (float)m_background->get_width()) - 1) * m_background->get_width()));
542        x < SCREEN_WIDTH;
543        x += m_background->get_width())
544   {
545     context.draw_surface(m_background, Vector(x, m_height - m_background->get_height()), layer);
546   }
547   m_backgroundOffset+=10;
548   if (m_backgroundOffset > (int)m_background->get_width()) m_backgroundOffset -= (int)m_background->get_width();
549
550   int lineNo = 0;
551
552   if (m_focused) {
553     lineNo++;
554     float py = m_height-4-1 * m_font->get_height();
555     context.draw_text(m_font, "> "+m_inputBuffer, Vector(4, py), ALIGN_LEFT, layer);
556     if (SDL_GetTicks() % 1000 < 750) {
557       int cursor_px = 2 + m_inputBufferPosition;
558       context.draw_text(m_font, "_", Vector(4 + (cursor_px * m_font->get_text_width("X")), py), ALIGN_LEFT, layer);
559     }
560   }
561
562   int skipLines = -m_offset;
563   for (std::list<std::string>::iterator i = m_buffer.m_lines.begin(); i != m_buffer.m_lines.end(); ++i)
564   {
565     if (skipLines-- > 0) continue;
566     lineNo++;
567     float py = m_height - 4 - lineNo * m_font->get_height();
568     if (py < -m_font->get_height()) break;
569     context.draw_text(m_font, *i, Vector(4, py), ALIGN_LEFT, layer);
570   }
571   context.pop_transform();
572 }
573
574 ConsoleStreamBuffer ConsoleBuffer::s_outputBuffer;
575 std::ostream ConsoleBuffer::output(&ConsoleBuffer::s_outputBuffer);
576
577 /* EOF */