1 /*
2 * See Licensing and Copyright notice in naev.h
3 */
4
5 /**
6 * @file console.c
7 *
8 * @brief Handles the Lua console.
9 */
10
11 #include "console.h"
12
13 #include "naev.h"
14
15 #include <stdlib.h>
16 #include <ctype.h>
17 #include "nstring.h"
18
19 #define lua_c
20 #include <lua.h>
21 #include <lauxlib.h>
22 #include <lualib.h>
23
24 #include "log.h"
25 #include "naev.h"
26 #include "nlua.h"
27 #include "nlua_cli.h"
28 #include "nlua_tk.h"
29 #include "nlua_tex.h"
30 #include "nlua_col.h"
31 #include "nlua_bkg.h"
32 #include "nlua_camera.h"
33 #include "nlua_music.h"
34 #include "font.h"
35 #include "toolkit.h"
36 #include "nfile.h"
37 #include "menu.h"
38 #include "conf.h"
39 #include "array.h"
40
41
42 #define BUTTON_WIDTH 50 /**< Button width. */
43 #define BUTTON_HEIGHT 20 /**< Button height. */
44
45
46 /*
47 * Global stuff.
48 */
49 static nlua_env cli_env = LUA_NOREF; /**< Lua CLI env. */
50 static glFont *cli_font = NULL; /**< CLI font to use. */
51
52 /*
53 * Buffers.
54 */
55 #define CLI_MAX_INPUT 1024 /** Maximum characters typed into console. */
56 #define CLI_WIDTH (SCREEN_W - 100) /**< Console width. */
57 #define CLI_HEIGHT (SCREEN_H - 100) /**< Console height. */
58 /** Height of console box */
59 #define CLI_CONSOLE_HEIGHT (CLI_HEIGHT-80-BUTTON_HEIGHT)
60 /** Number of lines displayed at once */
61 #define CLI_MAX_LINES (CLI_CONSOLE_HEIGHT/(cli_font->h+5))
62 static char **cli_buffer; /**< CLI buffer. */
63 static int cli_history = 0; /**< Position in history. */
64 static int cli_scroll_pos = -1; /**< Pistion in scrolling through output */
65 static int cli_firstOpen = 1; /**< First time opening. */
66
67
68 /*
69 * Input handling.
70 */
71 static int cli_firstline = 1; /**< Is this the first line? */
72
73
74 /*
75 * CLI stuff.
76 */
77 static int cli_script( lua_State *L );
78 static int cli_printOnly( lua_State *L );
79 static const luaL_Reg cli_methods[] = {
80 { "print", cli_printOnly },
81 { "script", cli_script },
82 { "warn", cli_warn },
83 {NULL, NULL}
84 }; /**< Console only functions. */
85
86
87
88 /*
89 * Prototypes.
90 */
91 static int cli_keyhandler( unsigned int wid, SDLKey key, SDLMod mod );
92 static void cli_render( double bx, double by, double w, double h, void *data );
93 static int cli_printCore( lua_State *L, int cli_only );
94 void cli_tabComplete( unsigned int wid );
95
96
97 /**
98 * @brief Back end for the Lua print functionality.
99 */
cli_printCore(lua_State * L,int cli_only)100 static int cli_printCore( lua_State *L, int cli_only )
101 {
102 int n; /* number of arguments */
103 int i;
104 char buf[CLI_MAX_INPUT];
105 int p;
106 const char *s;
107 char *line, *nextline, *tmp;
108
109 n = lua_gettop(L);
110 p = 0;
111
112 lua_getglobal(L, "tostring");
113 for (i=1; i<=n; i++) {
114 lua_pushvalue(L, -1); /* function to be called */
115 lua_pushvalue(L, i); /* value to print */
116 lua_call(L, 1, 1);
117 s = lua_tostring(L, -1); /* get result */
118 if (s == NULL)
119 return luaL_error(L, LUA_QL("tostring") " must return a string to "
120 LUA_QL("print"));
121 if (!cli_only)
122 LOG( "%s", s );
123
124 /* Add to console. */
125 tmp = strdup(s);
126 line = strtok(tmp, "\n");
127 while (line != NULL) {
128 nextline = strtok(NULL, "\n");
129 p += nsnprintf( &buf[p], CLI_MAX_INPUT-p, "%s%s",
130 (i>1) ? " " : "", line );
131 if ((p >= CLI_MAX_INPUT) || (nextline != NULL)) {
132 cli_addMessage(buf);
133 p = 0;
134 }
135 line = nextline;
136 }
137 free(tmp);
138 lua_pop(L, 1); /* pop result */
139 }
140
141 /* Add last line if needed. */
142 if (n > 0)
143 cli_addMessage(buf);
144
145 return 0;
146 }
147
148
149 /**
150 * @brief Barebones warn implementation for Lua, allowing scripts to print warnings to stderr.
151 *
152 * @luafunc warn()
153 */
cli_warn(lua_State * L)154 int cli_warn( lua_State *L )
155 {
156 const char *msg;
157
158 msg = luaL_checkstring(L,1);
159 logprintf( stderr, "Warning: %s\n", msg );
160
161 return 0;
162 }
163
164
165 /**
166 * @brief Replacement for the internal Lua print to print to both the console and the terminal.
167 */
cli_print(lua_State * L)168 int cli_print( lua_State *L )
169 {
170 return cli_printCore( L, 0 );
171 }
172
173
174 /**
175 * @brief Replacement for the internal Lua print to print to console instead of terminal.
176 */
cli_printOnly(lua_State * L)177 static int cli_printOnly( lua_State *L )
178 {
179 return cli_printCore( L, 1 );
180 }
181
182
183 /**
184 * @brief Would be like "dofile" from the base Lua lib.
185 */
cli_script(lua_State * L)186 static int cli_script( lua_State *L )
187 {
188 const char *fname;
189 char buf[PATH_MAX], *bbuf;
190 int n;
191
192 /* Handle parameters. */
193 fname = luaL_optstring(L, 1, NULL);
194 n = lua_gettop(L);
195
196 /* Try to find the file if it exists. */
197 if (nfile_fileExists(fname))
198 nsnprintf( buf, sizeof(buf), "%s", fname );
199 else {
200 bbuf = strdup( naev_binary() );
201 nsnprintf( buf, sizeof(buf), "%s/%s", nfile_dirname( bbuf ), fname );
202 free(bbuf);
203 }
204
205 /* Do the file. */
206 if (luaL_loadfile(L, buf) != 0)
207 lua_error(L);
208
209 /* Return the stuff. */
210 nlua_pushenv(cli_env);
211 lua_setfenv(L, -2);
212 lua_call(L, 0, LUA_MULTRET);
213 return lua_gettop(L) - n;
214 }
215
216
217 /**
218 * @brief Adds a message to the buffer.
219 *
220 * @param msg Message to add.
221 */
cli_addMessage(const char * msg)222 void cli_addMessage( const char *msg )
223 {
224 /* Not initialized. */
225 if (cli_env == LUA_NOREF)
226 return;
227
228 array_grow(&cli_buffer) = strdup((msg != NULL) ? msg : "");
229
230 cli_history = array_size(cli_buffer) - 1;
231 }
232
233
234 /**
235 * @brief Render function for the custom widget.
236 */
cli_render(double bx,double by,double w,double h,void * data)237 static void cli_render( double bx, double by, double w, double h, void *data )
238 {
239 (void) data;
240 int i, start;
241
242 if (cli_scroll_pos == -1)
243 start = MAX(0, array_size(cli_buffer) - CLI_MAX_LINES);
244 else
245 start = cli_scroll_pos;
246
247 for (i=start; i<array_size(cli_buffer); i++)
248 gl_printMaxRaw( cli_font, w, bx,
249 by + h - (i+1-start)*(cli_font->h+5),
250 &cBlack, cli_buffer[i] );
251 }
252
253
254 /**
255 * @brief Key handler for the console window.
256 */
cli_keyhandler(unsigned int wid,SDLKey key,SDLMod mod)257 static int cli_keyhandler( unsigned int wid, SDLKey key, SDLMod mod )
258 {
259 (void) mod;
260 int i, pos;
261 char *str;
262
263 switch (key) {
264
265 /* Go up in history. */
266 case SDLK_UP:
267 for (i=cli_history; i>=0; i--) {
268 if (strncmp(cli_buffer[i], "\eD>", 3) == 0) {
269 /* Strip escape codes from beginning and end */
270 str = nstrndup(cli_buffer[i]+5, strlen(cli_buffer[i])-7);
271 if (i == cli_history &&
272 strcmp(window_getInput(wid, "inpInput"), str) == 0) {
273 free(str);
274 continue;
275 }
276 window_setInput( wid, "inpInput", str );
277 free(str);
278 cli_history = i;
279 return 1;
280 }
281 }
282 return 1;
283
284 /* Go down in history. */
285 case SDLK_DOWN:
286 /* Clears buffer. */
287 if (cli_history >= array_size(cli_buffer)-1) {
288 window_setInput( wid, "inpInput", NULL );
289 return 1;
290 }
291
292 /* Find next buffer. */
293 for (i=cli_history+1; i<array_size(cli_buffer); i++) {
294 if (strncmp(cli_buffer[i], "\eD>", 3) == 0) {
295 str = nstrndup(cli_buffer[i]+5, strlen(cli_buffer[i])-7);
296 window_setInput( wid, "inpInput", str );
297 free(str);
298 cli_history = i;
299 return 1;
300 }
301 }
302 cli_history = i-1;
303 window_setInput( wid, "inpInput", NULL );
304 return 1;
305
306 /* Scroll up */
307 case SDLK_PAGEUP:
308 pos = cli_scroll_pos;
309 if (pos == -1)
310 pos = MAX(0, array_size(cli_buffer) - CLI_MAX_LINES);
311 cli_scroll_pos = MAX(0, pos - CLI_MAX_LINES);
312 return 1;
313
314 /* Scroll down */
315 case SDLK_PAGEDOWN:
316 if (cli_scroll_pos != -1) {
317 cli_scroll_pos = cli_scroll_pos + CLI_MAX_LINES;
318 if (cli_scroll_pos > (array_size(cli_buffer) - CLI_MAX_LINES))
319 cli_scroll_pos = -1;
320 }
321 return 1;
322
323 /* Tab completion */
324 case SDLK_TAB:
325 cli_tabComplete(wid);
326 return 1;
327
328 default:
329 break;
330 }
331
332 return 0;
333 }
334
335
336 /**
337 * @brief Basic tab completion for console.
338 */
cli_tabComplete(unsigned int wid)339 void cli_tabComplete( unsigned int wid ) {
340 int i;
341 const char *match, *old;
342 char *str, *cur, *new;
343
344 old = window_getInput( wid, "inpInput" );
345 if (old == NULL)
346 return;
347 str = strdup(old);
348
349 nlua_pushenv(cli_env);
350 cur = str;
351 for (i=0; str[i] != '\0'; i++) {
352 if (str[i] == '.' || str[i] == ':') {
353 str[i] = '\0';
354 lua_getfield(naevL, -1, cur);
355
356 /* If not indexable, replace with blank table */
357 if (!lua_istable(naevL, -1)) {
358 if (luaL_getmetafield(naevL, -1, "__index")) {
359 if (lua_istable(naevL, -1)) {
360 /* Handles the metatables used by Naev's userdatas */
361 lua_remove(naevL, -2);
362 } else {
363 lua_pop(naevL, 2);
364 lua_newtable(naevL);
365 }
366 } else {
367 lua_pop(naevL, 1);
368 lua_newtable(naevL);
369 }
370 }
371
372 lua_remove(naevL, -2);
373 cur = str + i + 1;
374 /* Start over on other non-identifier character */
375 } else if (!isalnum(str[i]) && str[i] != '_') {
376 lua_pop(naevL, 1);
377 nlua_pushenv(cli_env);
378 cur = str + i + 1;
379 }
380 }
381
382 if (strlen(cur) > 0) {
383 lua_pushnil(naevL);
384 while (lua_next(naevL, -2) != 0) {
385 if (lua_isstring(naevL, -2)) {
386 match = lua_tostring(naevL, -2);
387 if (strncmp(cur, match, strlen(cur)) == 0) {
388 new = malloc(strlen(old) + strlen(match) - strlen(cur) + 1);
389 strcpy(new, old);
390 strcat(new, match + strlen(cur));
391 window_setInput( wid, "inpInput", new);
392 free(new);
393 lua_pop(naevL, 2);
394 break;
395 }
396 }
397 lua_pop(naevL, 1);
398 }
399 }
400
401 free(str);
402 lua_pop(naevL, 1);
403 }
404
405
406 /**
407 * @brief Initializes the CLI environment.
408 */
cli_init(void)409 int cli_init (void)
410 {
411 /* Already loaded. */
412 if (cli_env != LUA_NOREF)
413 return 0;
414
415 /* Create the state. */
416 cli_env = nlua_newEnv(1);
417 nlua_loadStandard( cli_env );
418 nlua_loadTex( cli_env );
419 nlua_loadCol( cli_env );
420 nlua_loadBackground( cli_env );
421 nlua_loadCLI( cli_env );
422 nlua_loadCamera( cli_env );
423 nlua_loadMusic( cli_env );
424 nlua_loadTk( cli_env );
425
426 /* Mark as console. */
427 lua_pushboolean( naevL, 1 );
428 nlua_setenv( cli_env, "__cli" );
429
430 nlua_pushenv(cli_env);
431 luaL_register( naevL, NULL, cli_methods );
432 lua_settop( naevL, 0 );
433
434 /* Set the font. */
435 cli_font = malloc( sizeof(glFont) );
436 gl_fontInit( cli_font, "dat/mono.ttf", conf.font_size_console );
437
438 /* Allocate the buffer. */
439 cli_buffer = array_create(char*);
440
441 return 0;
442 }
443
444
445 /**
446 * @brief Destroys the CLI environment.
447 */
cli_exit(void)448 void cli_exit (void)
449 {
450 int i;
451
452 /* Destroy the state. */
453 if (cli_env != LUA_NOREF) {
454 nlua_freeEnv( cli_env );
455 cli_env = LUA_NOREF;
456 }
457
458 /* Free the buffer. */
459 for (i=0; i<array_size(cli_buffer); i++)
460 free(cli_buffer[i]);
461 array_free(cli_buffer);
462 cli_buffer = NULL;
463 }
464
465
466 /**
467 * @brief Handles the CLI input.
468 *
469 * @param wid Window receiving the input.
470 * @param unused Unused.
471 */
cli_input(unsigned int wid,char * unused)472 static void cli_input( unsigned int wid, char *unused )
473 {
474 (void) unused;
475 int status;
476 char *str;
477 char buf[CLI_MAX_INPUT+7];
478
479 /* Get the input. */
480 str = window_getInput( wid, "inpInput" );
481
482 /* Ignore useless stuff. */
483 if (str == NULL)
484 return;
485
486 /* Put the message in the console. */
487 nsnprintf( buf, CLI_MAX_INPUT+7, "\eD%s %s\e0",
488 cli_firstline ? "> " : ">>", str );
489 cli_addMessage( buf );
490
491 /* Set up for concat. */
492 if (!cli_firstline) /* o */
493 lua_pushliteral(naevL, "\n"); /* o \n */
494
495 /* Load the string. */
496 lua_pushstring( naevL, str ); /* s */
497
498 /* Concat. */
499 if (!cli_firstline) /* o \n s */
500 lua_concat(naevL, 3); /* s */
501
502 status = luaL_loadbuffer( naevL, lua_tostring(naevL,-1), lua_strlen(naevL,-1), "=cli" );
503
504 /* String isn't proper Lua yet. */
505 if (status == LUA_ERRSYNTAX) {
506 size_t lmsg;
507 const char *msg = lua_tolstring(naevL, -1, &lmsg);
508 const char *tp = msg + lmsg - (sizeof(LUA_QL("<eof>")) - 1);
509 if (strstr(msg, LUA_QL("<eof>")) == tp) {
510 /* Pop the loaded buffer. */
511 lua_pop(naevL, 1);
512 cli_firstline = 0;
513 }
514 else {
515 /* Real error, spew message and break. */
516 cli_addMessage( lua_tostring(naevL, -1) );
517 lua_settop(naevL, 0);
518 cli_firstline = 1;
519 }
520 }
521
522 /* Print results - all went well. */
523 else if (status == 0) {
524 lua_remove(naevL,1);
525
526 nlua_pushenv(cli_env);
527 lua_setfenv(naevL, -2);
528
529 if (nlua_pcall(cli_env, 0, LUA_MULTRET)) {
530 cli_addMessage( lua_tostring(naevL, -1) );
531 lua_pop(naevL, 1);
532 }
533
534 if (lua_gettop(naevL) > 0) {
535 nlua_getenv(cli_env, "print");
536 lua_insert(naevL, 1);
537 if (lua_pcall(naevL, lua_gettop(naevL)-1, 0, 0) != 0)
538 cli_addMessage( "Error printing results." );
539 }
540
541 /* Clear stack. */
542 lua_settop(naevL, 0);
543 cli_firstline = 1;
544 }
545
546 /* Clear the box now. */
547 window_setInput( wid, "inpInput", NULL );
548
549 /* Scroll to bottom */
550 cli_scroll_pos = -1;
551 }
552
553
554 /**
555 * @brief Opens the console.
556 */
cli_open(void)557 void cli_open (void)
558 {
559 unsigned int wid;
560
561 /* Lazy loading. */
562 if (cli_env == LUA_NOREF)
563 if (cli_init())
564 return;
565
566 /* Make sure main menu isn't open. */
567 if (menu_isOpen(MENU_MAIN))
568 return;
569
570 /* Must not be already open. */
571 if (window_exists( "Lua Console" ))
572 return;
573
574 /* Put a friendly message at first. */
575 if (cli_firstOpen) {
576 char buf[256];
577 cli_addMessage( "" );
578 cli_addMessage( "\egWelcome to the Lua console!" );
579 nsnprintf( buf, sizeof(buf), "\eg "APPNAME" v%s", naev_version(0) );
580 cli_addMessage( buf );
581 cli_addMessage( "" );
582 cli_firstOpen = 0;
583 }
584
585 /* Create the window. */
586 wid = window_create( "Lua Console", -1, -1, CLI_WIDTH, CLI_HEIGHT );
587
588 /* Window settings. */
589 window_setAccept( wid, cli_input );
590 window_setCancel( wid, window_close );
591 window_handleKeys( wid, cli_keyhandler );
592
593 /* Input box. */
594 window_addInput( wid, 20, 20,
595 CLI_WIDTH-60-BUTTON_WIDTH, BUTTON_HEIGHT,
596 "inpInput", CLI_MAX_INPUT, 1, cli_font );
597
598 /* Buttons. */
599 window_addButton( wid, -20, 20, BUTTON_WIDTH, BUTTON_HEIGHT,
600 "btnClose", "Close", window_close );
601
602 /* Custom console widget. */
603 window_addCust( wid, 20, -40,
604 CLI_WIDTH-40, CLI_CONSOLE_HEIGHT,
605 "cstConsole", 0, cli_render, NULL, NULL );
606 }
607
608
609