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