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