console.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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. #include "supertux/console.hpp"
  17. #include "math/sizef.hpp"
  18. #include "physfs/ifile_stream.hpp"
  19. #include "squirrel/squirrel_util.hpp"
  20. #include "squirrel/squirrel_virtual_machine.hpp"
  21. #include "supertux/gameconfig.hpp"
  22. #include "supertux/globals.hpp"
  23. #include "supertux/resources.hpp"
  24. #include "util/log.hpp"
  25. #include "video/drawing_context.hpp"
  26. #include "video/surface.hpp"
  27. /// speed (pixels/s) the console closes
  28. static const float FADE_SPEED = 1;
  29. ConsoleBuffer::ConsoleBuffer() :
  30. m_lines(),
  31. m_console(nullptr)
  32. {
  33. }
  34. void
  35. ConsoleBuffer::addLines(const std::string& s)
  36. {
  37. std::istringstream iss(s);
  38. std::string line;
  39. while (std::getline(iss, line, '\n'))
  40. {
  41. addLine(line);
  42. }
  43. }
  44. void
  45. ConsoleBuffer::addLine(const std::string& s_)
  46. {
  47. std::string s = s_;
  48. // Output line to stderr.
  49. get_logging_instance(false) << s << std::endl;
  50. // Wrap long lines.
  51. std::string overflow;
  52. int line_count = 0;
  53. do {
  54. m_lines.push_front(Font::wrap_to_chars(s, 99, &overflow));
  55. line_count += 1;
  56. s = overflow;
  57. } while (s.length() > 0);
  58. // Trim scrollback buffer.
  59. while (m_lines.size() >= 1000)
  60. {
  61. m_lines.pop_back();
  62. }
  63. if (m_console)
  64. {
  65. m_console->on_buffer_change(line_count);
  66. }
  67. }
  68. void
  69. ConsoleBuffer::flush(ConsoleStreamBuffer& buffer)
  70. {
  71. if (&buffer == &s_outputBuffer)
  72. {
  73. std::string s = s_outputBuffer.str();
  74. if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')))
  75. {
  76. while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))
  77. {
  78. s.erase(s.length()-1);
  79. }
  80. addLines(s);
  81. s_outputBuffer.str("");
  82. }
  83. }
  84. }
  85. Console::Console(ConsoleBuffer& buffer) :
  86. m_buffer(buffer),
  87. m_inputBuffer(),
  88. m_inputBufferPosition(0),
  89. m_history(),
  90. m_history_position(m_history.end()),
  91. m_background(Surface::from_file("images/engine/console.png")),
  92. m_background2(Surface::from_file("images/engine/console2.png")),
  93. m_vm(),
  94. m_backgroundOffset(0),
  95. m_height(0),
  96. m_alpha(1.0),
  97. m_offset(0),
  98. m_focused(false),
  99. m_font(Resources::console_font),
  100. m_stayOpen(0)
  101. {
  102. m_buffer.set_console(this);
  103. }
  104. Console::~Console()
  105. {
  106. m_vm.reset();
  107. m_buffer.set_console(nullptr);
  108. }
  109. void
  110. Console::on_buffer_change(int line_count)
  111. {
  112. // Increase console height if necessary.
  113. if (m_stayOpen > 0 && m_height < 64)
  114. {
  115. if (m_height < 4)
  116. {
  117. m_height = 4;
  118. }
  119. m_height += m_font->get_height() * static_cast<float>(line_count);
  120. }
  121. // Reset console to full opacity.
  122. m_alpha = 1.0;
  123. }
  124. void
  125. Console::ready_vm()
  126. {
  127. if (!m_vm.isEmpty()) return;
  128. m_vm = SquirrelVirtualMachine::current()->get_vm().newThread(16);
  129. // Create new root table for the thread.
  130. ssq::Table root = m_vm.newTable();
  131. root.setDelegate(m_vm); // Set the root table as delegate.
  132. m_vm.setRootTable(root);
  133. try
  134. {
  135. IFileStream stream("scripts/console.nut");
  136. m_vm.run(m_vm.compileSource(stream, "scripts/console.nut"));
  137. }
  138. catch (const std::exception& e)
  139. {
  140. log_warning << "Couldn't load 'console.nut': " << e.what() << std::endl;
  141. }
  142. }
  143. void
  144. Console::execute_script(const std::string& command)
  145. {
  146. ready_vm();
  147. const SQInteger old_top = m_vm.getTop();
  148. try
  149. {
  150. ssq::Object ret = m_vm.runAndReturn(m_vm.compileSource(command.c_str()));
  151. if (ret.getType() != ssq::Type::NULLPTR)
  152. m_buffer.addLines(squirrel_to_string(ret));
  153. }
  154. catch (const std::exception& e)
  155. {
  156. m_buffer.addLines(e.what());
  157. }
  158. if (m_vm.getTop() < old_top)
  159. {
  160. log_fatal << "Script destroyed Squirrel stack..." << std::endl;
  161. }
  162. else
  163. {
  164. sq_settop(m_vm.getHandle(), old_top);
  165. }
  166. }
  167. void
  168. Console::input(char c)
  169. {
  170. m_inputBuffer.insert(m_inputBufferPosition, 1, c);
  171. m_inputBufferPosition++;
  172. }
  173. void
  174. Console::backspace()
  175. {
  176. if ((m_inputBufferPosition > 0) && (m_inputBuffer.length() > 0)) {
  177. m_inputBuffer.erase(m_inputBufferPosition-1, 1);
  178. m_inputBufferPosition--;
  179. }
  180. }
  181. void
  182. Console::eraseChar()
  183. {
  184. if (m_inputBufferPosition < static_cast<int>(m_inputBuffer.length())) {
  185. m_inputBuffer.erase(m_inputBufferPosition, 1);
  186. }
  187. }
  188. void
  189. Console::enter()
  190. {
  191. m_buffer.addLines("> " + m_inputBuffer);
  192. parse(m_inputBuffer);
  193. m_inputBuffer = "";
  194. m_inputBufferPosition = 0;
  195. }
  196. void
  197. Console::scroll(int numLines)
  198. {
  199. m_offset += numLines;
  200. if (m_offset > 0) m_offset = 0;
  201. }
  202. void
  203. Console::show_history(int offset_)
  204. {
  205. while ((offset_ > 0) && (m_history_position != m_history.end())) {
  206. ++m_history_position;
  207. offset_--;
  208. }
  209. while ((offset_ < 0) && (m_history_position != m_history.begin())) {
  210. --m_history_position;
  211. offset_++;
  212. }
  213. if (m_history_position == m_history.end()) {
  214. m_inputBuffer = "";
  215. m_inputBufferPosition = 0;
  216. } else {
  217. m_inputBuffer = *m_history_position;
  218. m_inputBufferPosition = static_cast<int>(m_inputBuffer.length());
  219. }
  220. }
  221. void
  222. Console::move_cursor(int offset_)
  223. {
  224. if (offset_ == -65535) m_inputBufferPosition = 0;
  225. if (offset_ == +65535) m_inputBufferPosition = static_cast<int>(m_inputBuffer.length());
  226. m_inputBufferPosition+=offset_;
  227. if (m_inputBufferPosition < 0) m_inputBufferPosition = 0;
  228. if (m_inputBufferPosition > static_cast<int>(m_inputBuffer.length())) m_inputBufferPosition = static_cast<int>(m_inputBuffer.length());
  229. }
  230. // Helper functions for Console::autocomplete.
  231. // TODO: Fix rough documentation.
  232. namespace {
  233. void sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, const std::string& table_prefix, const std::string& search_prefix);
  234. /**
  235. * Acts upon key,value on top of stack:
  236. * Appends key (plus type-dependent suffix) to cmds if table_prefix+key starts with search_prefix;
  237. * Calls sq_insert_commands if search_prefix starts with table_prefix+key (and value is a table/class/instance);
  238. */
  239. void
  240. sq_insert_command(std::list<std::string>& cmds, HSQUIRRELVM vm, const std::string& table_prefix, const std::string& search_prefix)
  241. {
  242. const SQChar* key_chars;
  243. if (SQ_FAILED(sq_getstring(vm, -2, &key_chars))) return;
  244. std::string key_string = table_prefix + key_chars;
  245. switch (sq_gettype(vm, -1)) {
  246. case OT_INSTANCE:
  247. key_string+=".";
  248. if (search_prefix.substr(0, key_string.length()) == key_string) {
  249. sq_getclass(vm, -1);
  250. sq_insert_commands(cmds, vm, key_string, search_prefix);
  251. sq_pop(vm, 1);
  252. }
  253. break;
  254. case OT_TABLE:
  255. case OT_CLASS:
  256. key_string+=".";
  257. if (search_prefix.substr(0, key_string.length()) == key_string) {
  258. sq_insert_commands(cmds, vm, key_string, search_prefix);
  259. }
  260. break;
  261. case OT_CLOSURE:
  262. case OT_NATIVECLOSURE:
  263. key_string+="()";
  264. break;
  265. default:
  266. break;
  267. }
  268. if (key_string.substr(0, search_prefix.length()) == search_prefix) {
  269. cmds.push_back(key_string);
  270. }
  271. }
  272. /**
  273. * Calls sq_insert_command for all entries of table/class on top of stack.
  274. */
  275. void
  276. sq_insert_commands(std::list<std::string>& cmds, HSQUIRRELVM vm, const std::string& table_prefix, const std::string& search_prefix)
  277. {
  278. sq_pushnull(vm); // push iterator.
  279. while (SQ_SUCCEEDED(sq_next(vm,-2))) {
  280. sq_insert_command(cmds, vm, table_prefix, search_prefix);
  281. sq_pop(vm, 2); // pop key, val.
  282. }
  283. sq_pop(vm, 1); // pop iterator.
  284. }
  285. }
  286. // End of Console::autocomplete helper functions.
  287. void
  288. Console::autocomplete()
  289. {
  290. // int autocompleteFrom = m_inputBuffer.find_last_of(" ();+", m_inputBufferPosition);
  291. int autocompleteFrom = static_cast<int>(m_inputBuffer.find_last_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_->.", m_inputBufferPosition));
  292. if (autocompleteFrom != static_cast<int>(std::string::npos)) {
  293. autocompleteFrom += 1;
  294. } else {
  295. autocompleteFrom = 0;
  296. }
  297. std::string prefix = m_inputBuffer.substr(autocompleteFrom, m_inputBufferPosition - autocompleteFrom);
  298. m_buffer.addLines("> " + prefix);
  299. std::list<std::string> cmds;
  300. ready_vm();
  301. // Append all keys of the current root table to list.
  302. sq_pushroottable(m_vm.getHandle()); // push root table.
  303. while (true)
  304. {
  305. // Check all keys (and their children) for matches.
  306. sq_insert_commands(cmds, m_vm.getHandle(), "", prefix);
  307. // Cycle through parent(delegate) table.
  308. const SQInteger old_top = m_vm.getTop();
  309. if (SQ_FAILED(sq_getdelegate(m_vm.getHandle(), -1)) || old_top == m_vm.getTop())
  310. break;
  311. sq_remove(m_vm.getHandle(), -2); // Remove old table.
  312. }
  313. sq_pop(m_vm.getHandle(), 1); // Remove table.
  314. // Depending on number of hits, show matches or autocomplete.
  315. if (cmds.empty())
  316. {
  317. m_buffer.addLines("No known command starts with \"" + prefix + "\"");
  318. }
  319. if (cmds.size() == 1)
  320. {
  321. // One match: just replace input buffer with full command.
  322. std::string replaceWith = cmds.front();
  323. m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
  324. m_inputBufferPosition += static_cast<int>(replaceWith.length() - prefix.length());
  325. }
  326. if (cmds.size() > 1)
  327. {
  328. // Multiple matches: show all matches and set input buffer to longest common prefix.
  329. std::string commonPrefix = cmds.front();
  330. while (cmds.begin() != cmds.end()) {
  331. std::string cmd = cmds.front();
  332. cmds.pop_front();
  333. m_buffer.addLines(cmd);
  334. for (int n = static_cast<int>(commonPrefix.length()); n >= 1; n--) {
  335. if (cmd.compare(0, n, commonPrefix) != 0) commonPrefix.resize(n-1); else break;
  336. }
  337. }
  338. std::string replaceWith = commonPrefix;
  339. m_inputBuffer.replace(autocompleteFrom, prefix.length(), replaceWith);
  340. m_inputBufferPosition += static_cast<int>(replaceWith.length() - prefix.length());
  341. }
  342. }
  343. void
  344. Console::parse(const std::string& s)
  345. {
  346. // Make sure we actually have something to parse.
  347. if (s.length() == 0) return;
  348. // Add line to history.
  349. m_history.push_back(s);
  350. m_history_position = m_history.end();
  351. // Split line into list of args.
  352. std::vector<std::string> args;
  353. size_t end = 0;
  354. while (1) {
  355. size_t start = s.find_first_not_of(" ,", end);
  356. end = s.find_first_of(" ,", start);
  357. if (start == s.npos) break;
  358. args.push_back(s.substr(start, end-start));
  359. }
  360. // Command is args[0].
  361. if (args.size() == 0) return;
  362. std::string command = args.front();
  363. args.erase(args.begin());
  364. // Ignore if it's an internal command.
  365. if (consoleCommand(command,args)) return;
  366. try {
  367. execute_script(s);
  368. } catch(std::exception& e) {
  369. m_buffer.addLines(e.what());
  370. }
  371. }
  372. bool
  373. Console::consoleCommand(const std::string& /*command*/, const std::vector<std::string>& /*arguments*/)
  374. {
  375. return false;
  376. }
  377. bool
  378. Console::hasFocus() const
  379. {
  380. return m_focused;
  381. }
  382. void
  383. Console::show()
  384. {
  385. if (!g_config->developer_mode)
  386. return;
  387. m_focused = true;
  388. m_height = 256;
  389. m_alpha = 1.0;
  390. }
  391. void
  392. Console::open()
  393. {
  394. if (m_stayOpen < 2)
  395. m_stayOpen += 1.5f;
  396. }
  397. void
  398. Console::hide()
  399. {
  400. m_focused = false;
  401. m_height = 0;
  402. m_stayOpen = 0;
  403. // Clear the input buffer.
  404. }
  405. void
  406. Console::toggle()
  407. {
  408. if (Console::hasFocus()) {
  409. Console::hide();
  410. }
  411. else {
  412. Console::show();
  413. }
  414. }
  415. void
  416. Console::update(float dt_sec)
  417. {
  418. if (m_stayOpen > 0) {
  419. m_stayOpen -= dt_sec;
  420. if (m_stayOpen < 0)
  421. m_stayOpen = 0;
  422. } else if (!m_focused && m_height > 0) {
  423. m_alpha -= dt_sec * FADE_SPEED;
  424. if (m_alpha < 0) {
  425. m_alpha = 0;
  426. m_height = 0;
  427. }
  428. }
  429. m_backgroundOffset += static_cast<int>(600.0f * dt_sec);
  430. if (m_backgroundOffset > static_cast<int>(m_background->get_width())) m_backgroundOffset -= static_cast<int>(m_background->get_width());
  431. }
  432. void
  433. Console::draw(DrawingContext& context) const
  434. {
  435. if (m_height == 0)
  436. return;
  437. const int layer = LAYER_GUI + 1;
  438. const float context_center_x = context.get_width() / 2;
  439. const int background_center_x = m_background->get_width() / 2;
  440. context.push_transform();
  441. context.set_alpha(m_alpha);
  442. context.color().draw_surface(m_background2,
  443. Vector(context_center_x - static_cast<float>(background_center_x + m_background->get_width() - m_backgroundOffset),
  444. m_height - static_cast<float>(m_background->get_height())),
  445. layer);
  446. context.color().draw_surface(m_background2,
  447. Vector(context_center_x - static_cast<float>(background_center_x - m_backgroundOffset),
  448. m_height - static_cast<float>(m_background->get_height())),
  449. layer);
  450. for (int x = (static_cast<int>(context_center_x) - background_center_x
  451. - (static_cast<int>(ceilf(context.get_width() /
  452. static_cast<float>(m_background->get_width())) - 1) * m_background->get_width()));
  453. x < static_cast<int>(context.get_width());
  454. x += m_background->get_width())
  455. {
  456. context.color().draw_surface(m_background, Vector(static_cast<float>(x),
  457. m_height - static_cast<float>(m_background->get_height())),
  458. layer);
  459. }
  460. int lineNo = 0;
  461. if (m_focused) {
  462. lineNo++;
  463. float py = m_height-4-1 * m_font->get_height();
  464. std::string line = "> " + m_inputBuffer;
  465. context.color().draw_text(m_font, line, Vector(4, py), ALIGN_LEFT, layer);
  466. if (SDL_GetTicks() % 500 < 250) {
  467. std::string::size_type p = 2 + m_inputBufferPosition;
  468. float cursor_x;
  469. if (p >= line.size())
  470. {
  471. cursor_x = m_font->get_text_width(line);
  472. }
  473. else
  474. {
  475. cursor_x = m_font->get_text_width(line.substr(0, p));
  476. }
  477. context.color().draw_filled_rect(Rectf(Vector(3 + cursor_x, py),
  478. Sizef(2.0f, m_font->get_height() - 2)),
  479. Color(1.0f, 1.0f, 1.0f, 0.75f), layer);
  480. }
  481. }
  482. int skipLines = -m_offset;
  483. for (std::list<std::string>::iterator i = m_buffer.m_lines.begin(); i != m_buffer.m_lines.end(); ++i)
  484. {
  485. if (skipLines-- > 0) continue;
  486. lineNo++;
  487. float py = static_cast<float>(m_height - 4.0f - static_cast<float>(lineNo) * m_font->get_height());
  488. if (py < -m_font->get_height()) break;
  489. context.color().draw_text(m_font, *i, Vector(4.0f, py), ALIGN_LEFT, layer);
  490. }
  491. context.pop_transform();
  492. }
  493. ConsoleStreamBuffer ConsoleBuffer::s_outputBuffer;
  494. std::ostream ConsoleBuffer::output(&ConsoleBuffer::s_outputBuffer);
  495. /* EOF */