1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 /* console.c for gretl */
21 
22 #include "gretl.h"
23 #include "console.h"
24 #include "menustate.h"
25 #include "dlgutils.h"
26 #include "gui_recode.h"
27 #include "textbuf.h"
28 #include "winstack.h"
29 
30 #ifdef G_OS_WIN32
31 # include "gretlwin32.h"
32 #endif
33 
34 #ifdef HAVE_GTKSV_COMPLETION
35 # include "completions.h"
36 #endif
37 
38 #include "libset.h"
39 #include "monte_carlo.h"
40 #include "gretl_func.h"
41 #include "uservar.h"
42 #include "cmd_private.h"
43 
44 #define CDEBUG 0
45 #define KDEBUG 0
46 
47 /* file-scope globals */
48 static char **cmd_history;
49 static int hpos, hlines;
50 static gchar *hist0;
51 static GtkWidget *console_main;
52 static int console_protected;
53 
54 static gint console_key_handler (GtkWidget *cview, GdkEvent *key,
55 				 gpointer p);
56 
protect_console(void)57 static void protect_console (void)
58 {
59     console_protected++;
60 }
61 
unprotect_console(void)62 static void unprotect_console (void)
63 {
64     if (console_protected > 0) {
65 	console_protected--;
66     }
67 }
68 
command_history_init(void)69 static void command_history_init (void)
70 {
71     hlines = hpos = 0;
72     hist0 = NULL;
73 }
74 
command_history_destroy(void)75 static void command_history_destroy (void)
76 {
77     if (cmd_history != NULL) {
78 	strings_array_free(cmd_history, hlines);
79 	cmd_history = NULL;
80     }
81 
82     g_free(hist0);
83     hist0 = NULL;
84 
85     hlines = hpos = 0;
86 }
87 
console_init(char * cbuf)88 static ExecState *console_init (char *cbuf)
89 {
90     ExecState *s;
91     PRN *prn;
92 
93     s = calloc(1, sizeof *s);
94     if (s == NULL) {
95 	return NULL;
96     }
97 
98     if (bufopen(&prn)) {
99 	free(s);
100 	return NULL;
101     }
102 
103     set_gretl_echo(1);
104 
105     /* note below: @model is a GUI global (maybe a bad
106        idea, but would be kinda complicated to unpick)
107     */
108     gretl_exec_state_init(s, CONSOLE_EXEC, cbuf,
109 			  get_lib_cmd(), model, prn);
110 
111     command_history_init();
112 
113     return s;
114 }
115 
push_history_line(const char * line)116 static void push_history_line (const char *line)
117 {
118     strings_array_add(&cmd_history, &hlines, line);
119     hpos = hlines;
120 }
121 
console_beep(void)122 static void console_beep (void)
123 {
124 #ifdef G_OS_WIN32
125     MessageBeep(MB_ICONEXCLAMATION);
126 #else
127     gdk_beep();
128 #endif
129 }
130 
fetch_history_line(int keyval)131 static const char *fetch_history_line (int keyval)
132 {
133     static int beep;
134     char *ret = NULL;
135 
136     if (keyval == GDK_Up) {
137 	if (hpos > 0) {
138 	    /* up is OK */
139 	    ret = cmd_history[--hpos];
140 	    beep = 0;
141 	} else {
142 	    /* can't go up */
143 	    console_beep();
144 	}
145     } else if (keyval == GDK_Down) {
146 	if (hlines == 0) {
147 	    /* no history yet */
148 	    hpos = 0;
149 	    console_beep();
150 	} else if (hpos < hlines - 1) {
151 	    /* down is OK */
152 	    ret = cmd_history[++hpos];
153 	    beep = 0;
154 	} else {
155 	    /* can't go down */
156 	    hpos = hlines;
157 	    if (beep) {
158 		console_beep();
159 	    } else {
160 		beep = 1;
161 	    }
162 	}
163     }
164 
165     return ret;
166 }
167 
console_scroll_to_end(GtkWidget * cview,GtkTextBuffer * buf,GtkTextIter * end)168 static void console_scroll_to_end (GtkWidget *cview,
169 				   GtkTextBuffer *buf,
170 				   GtkTextIter *end)
171 {
172     GtkTextMark *mark;
173 
174     mark = gtk_text_buffer_create_mark(buf, NULL, end, FALSE);
175     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(cview), mark);
176     gtk_text_buffer_delete_mark(buf, mark);
177 }
178 
on_last_line(GtkWidget * cview)179 static gint on_last_line (GtkWidget *cview)
180 {
181     GtkTextBuffer *buf;
182     GtkTextIter iter;
183 
184     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(cview));
185     gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_insert(buf));
186 
187     return (gtk_text_iter_get_line(&iter) ==
188 	    gtk_text_buffer_get_line_count(buf) - 1);
189 }
190 
console_mouse_handler(GtkWidget * cview,GdkEventButton * event,gpointer p)191 static gint console_mouse_handler (GtkWidget *cview, GdkEventButton *event,
192 				   gpointer p)
193 {
194     interactive_script_help(cview, event, p);
195     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(cview),
196 				     on_last_line(cview));
197     return FALSE;
198 }
199 
console_paste_text(GtkWidget * cview,GdkAtom atom)200 static gint console_paste_text (GtkWidget *cview, GdkAtom atom)
201 {
202     GtkClipboard *cb = gtk_clipboard_get(atom);
203     gchar *src = gtk_clipboard_wait_for_text(cb);
204 
205     if (src != NULL) {
206 	GtkTextBuffer *buf;
207 	GtkTextIter iter;
208 	char *p;
209 
210 	p = strchr(src, '\n');
211 	if (p != NULL) {
212 	    /* no newlines allowed! */
213 	    *p = '\0';
214 	}
215 
216 	buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(cview));
217 	gtk_text_buffer_get_end_iter(buf, &iter);
218 	gtk_text_buffer_insert(buf, &iter, src, -1);
219 
220 	g_free(src);
221     }
222 
223     return TRUE;
224 }
225 
console_paste_handler(GtkWidget * w,gpointer p)226 static void console_paste_handler (GtkWidget *w, gpointer p)
227 {
228     /* we don't accept pasted material other than via
229        the X selection */
230     return;
231 }
232 
233 /* paste from X selection onto the command line */
234 
console_click_handler(GtkWidget * w,GdkEventButton * event,gpointer p)235 static gint console_click_handler (GtkWidget *w,
236 				   GdkEventButton *event,
237 				   gpointer p)
238 {
239     if (event->button == 2) {
240 	return console_paste_text(w, GDK_SELECTION_PRIMARY);
241     }
242 
243     return FALSE;
244 }
245 
246 enum {
247     SAMPLE_RECORD,
248     SAMPLE_CHECK
249 };
250 
251 /* mechanism to check if a console action has altered the
252    current sample information */
253 
console_sample_handler(const DATASET * pdinfo,int code)254 static int console_sample_handler (const DATASET *pdinfo, int code)
255 {
256     static int pd, t1, t2, ts;
257     static double sd0;
258     int ret = 0;
259 
260     if (code == SAMPLE_RECORD) {
261 	pd = pdinfo->pd;
262 	t1 = pdinfo->t1;
263 	t2 = pdinfo->t2;
264 	ts = pdinfo->structure;
265 	sd0 = pdinfo->sd0;
266     } else if (code == SAMPLE_CHECK && pdinfo->v > 0) {
267 	if (pdinfo->pd != pd ||
268 	    pdinfo->t1 != t1 ||
269 	    pdinfo->t2 != t2 ||
270 	    pdinfo->structure != ts ||
271 	    pdinfo->sd0 != sd0) {
272 	    ret = 1;
273 	}
274     }
275 
276     return ret;
277 }
278 
279 /* the two functions below are public because they are
280    also used with the command 'minibuffer' */
281 
console_record_sample(const DATASET * pdinfo)282 void console_record_sample (const DATASET *pdinfo)
283 {
284     console_sample_handler(pdinfo, SAMPLE_RECORD);
285 }
286 
console_sample_changed(const DATASET * pdinfo)287 int console_sample_changed (const DATASET *pdinfo)
288 {
289     return console_sample_handler(pdinfo, SAMPLE_CHECK);
290 }
291 
print_result_to_console(GtkTextBuffer * buf,GtkTextIter * iter,ExecState * state)292 static void print_result_to_console (GtkTextBuffer *buf,
293 				     GtkTextIter *iter,
294 				     ExecState *state)
295 {
296     const char *prnbuf = gretl_print_get_buffer(state->prn);
297 
298     if (g_utf8_validate(prnbuf, -1, NULL)) {
299 	gtk_text_buffer_insert_with_tags_by_name(buf, iter, prnbuf, -1,
300 						 "output", NULL);
301     } else {
302 	gchar *trbuf = my_locale_to_utf8(prnbuf);
303 
304 	fprintf(stderr, "console text did not validate as utf8\n");
305 	if (trbuf != NULL) {
306 	    gtk_text_buffer_insert_with_tags_by_name(buf, iter, trbuf, -1,
307 						     "output", NULL);
308 	    g_free(trbuf);
309 	}
310     }
311 
312     gretl_print_reset_buffer(state->prn);
313 }
314 
console_insert_prompt(GtkTextBuffer * buf,GtkTextIter * iter,const char * prompt)315 static void console_insert_prompt (GtkTextBuffer *buf,
316 				   GtkTextIter *iter,
317 				   const char *prompt)
318 {
319     gtk_text_buffer_insert_with_tags_by_name(buf, iter, prompt, -1,
320 					     "prompt", NULL);
321     gtk_text_buffer_place_cursor(buf, iter);
322 }
323 
detect_quit(const char * s)324 static int detect_quit (const char *s)
325 {
326     s += strspn(s, " \t");
327     if (!strncmp(s, "quit", 4)) {
328 	int n = strlen(s);
329 
330 	if (n == 4 || (n > 4 && isspace(s[4]))) {
331 	    return 1;
332 	}
333     }
334 
335     return 0;
336 }
337 
maybe_exit_on_quit(void)338 static void maybe_exit_on_quit (void)
339 {
340     if (exit_check()) {
341 	return;
342     } else {
343 	const char *msg = N_("Really quit gretl?");
344 	int resp = no_yes_dialog(NULL, _(msg));
345 
346 	if (resp == GRETL_YES) {
347 	    gtk_main_quit();
348 	}
349     }
350 }
351 
real_console_exec(ExecState * state)352 static int real_console_exec (ExecState *state)
353 {
354     int err = 0;
355 
356 #if CDEBUG
357     fprintf(stderr, "*** real_console_exec: '%s'\n", state->line);
358 #endif
359 
360     if (swallow && detect_quit(state->line)) {
361 	maybe_exit_on_quit();
362 	return 0;
363     }
364 
365     push_history_line(state->line);
366 
367     state->flags = CONSOLE_EXEC;
368     err = gui_exec_line(state, dataset, console_main);
369 
370     while (!err && gretl_execute_loop()) {
371 	err = gretl_loop_exec(state, dataset, NULL);
372     }
373 
374 #if CDEBUG
375     fprintf(stderr, "*** real_console_exec returning %d\n", err);
376 #endif
377 
378     return err;
379 }
380 
console_prompt(ExecState * s)381 static const char *console_prompt (ExecState *s)
382 {
383     if (gretl_compiling_function() ||
384 	gretl_compiling_loop()) {
385 	return "> ";
386     } else {
387 	return "? ";
388     }
389 }
390 
391 /* called on receipt of a completed command line */
392 
update_console(ExecState * state,GtkWidget * cview)393 static void update_console (ExecState *state, GtkWidget *cview)
394 {
395     GtkTextBuffer *buf;
396     GtkTextIter iter;
397 
398     console_record_sample(dataset);
399 
400     protect_console();
401     real_console_exec(state);
402 
403     if (state->cmd->ci == QUIT) {
404 	*state->line = '\0';
405 	unprotect_console();
406 	return;
407     }
408 
409     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(cview));
410 
411     gtk_text_buffer_get_end_iter(buf, &iter);
412     gtk_text_buffer_insert(buf, &iter, "\n", 1);
413 
414     if (print_redirection_level(state->prn) == 0) {
415 	print_result_to_console(buf, &iter, state);
416     }
417 
418     /* set up prompt for next command and scroll to it */
419     console_insert_prompt(buf, &iter, console_prompt(state));
420     console_scroll_to_end(cview, buf, &iter);
421 
422     /* update variable listing in main window if needed */
423     if (check_dataset_is_changed(dataset)) {
424 	mark_dataset_as_modified();
425 	populate_varlist();
426     }
427 
428     /* update sample info if needed */
429     if (console_sample_changed(dataset)) {
430 	set_sample_label(dataset);
431     }
432 
433     /* clear command line for next entry */
434     *state->line = '\0';
435 
436 #ifdef G_OS_WIN32
437     gtk_window_present(GTK_WINDOW(gtk_widget_get_toplevel(cview)));
438     gtk_widget_grab_focus(cview);
439 #endif
440 
441     unprotect_console();
442 }
443 
console_is_busy(void)444 int console_is_busy (void)
445 {
446     if (console_main != NULL && GTK_IS_WINDOW(console_main)) {
447 	gtk_window_present(GTK_WINDOW(console_main));
448 	return 1;
449     } else {
450 	return 0;
451     }
452 }
453 
console_destroyed(GtkWidget * w,ExecState * state)454 static void console_destroyed (GtkWidget *w, ExecState *state)
455 {
456 #if CDEBUG
457     fprintf(stderr, "*** console_destroyed called\n");
458 #endif
459     command_history_destroy();
460     gretl_print_destroy(state->prn);
461     gretl_exec_state_destroy(state);
462     console_main = NULL;
463     if (!swallow) {
464 	/* exit the command loop */
465 	gtk_main_quit();
466     }
467 }
468 
console_destroy_check(void)469 static gboolean console_destroy_check (void)
470 {
471     return console_protected ? TRUE : FALSE;
472 }
473 
gretl_console(void)474 windata_t *gretl_console (void)
475 {
476     static char cbuf[MAXLINE];
477     windata_t *vwin;
478     GtkTextBuffer *buf;
479     GtkTextIter iter;
480     ExecState *state;
481     const gchar *intro =
482 	N_("gretl console: type 'help' for a list of commands");
483 
484     if (console_main != NULL) {
485 	if (GTK_IS_WINDOW(console_main)) {
486 	    gtk_window_present(GTK_WINDOW(console_main));
487 	} else {
488 	    vwin = g_object_get_data(G_OBJECT(console_main), "vwin");
489 	    gtk_widget_grab_focus(vwin->text);
490 	}
491 	return NULL;
492     }
493 
494     state = console_init(cbuf);
495     if (state == NULL) {
496 	return NULL;
497     }
498 
499     if (swallow) {
500 	preset_viewer_flag(VWIN_SWALLOW);
501     }
502     vwin = console_window(78, 450);
503     console_main = vwin->main;
504 
505     g_signal_connect(G_OBJECT(vwin->text), "paste-clipboard",
506 		     G_CALLBACK(console_paste_handler), NULL);
507     g_signal_connect(G_OBJECT(vwin->text), "button-press-event",
508 		     G_CALLBACK(console_click_handler), NULL);
509     g_signal_connect(G_OBJECT(vwin->text), "button-release-event",
510 		     G_CALLBACK(console_mouse_handler), vwin);
511     g_signal_connect(G_OBJECT(vwin->text), "key-press-event",
512 		     G_CALLBACK(console_key_handler), vwin);
513     g_signal_connect(G_OBJECT(vwin->main), "delete-event",
514 		     G_CALLBACK(console_destroy_check), NULL);
515     g_signal_connect(G_OBJECT(vwin->main), "destroy",
516 		     G_CALLBACK(console_destroyed), state);
517 
518     g_object_set_data(G_OBJECT(vwin->text), "ExecState", state); /* was NULL */
519 
520     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vwin->text));
521     gtk_text_buffer_get_start_iter(buf, &iter);
522 
523     if (swallow) {
524 	console_insert_prompt(buf, &iter, "? ");
525     } else {
526 	gtk_text_buffer_insert_with_tags_by_name(buf, &iter, _(intro), -1,
527 						 "output", NULL);
528 	console_insert_prompt(buf, &iter, "\n? ");
529     }
530 
531     gtk_widget_grab_focus(vwin->text);
532 
533     if (!swallow) {
534 	/* enter command loop */
535 	gtk_main();
536     }
537 
538 #if CDEBUG
539     fprintf(stderr, "gretl_console: returning\n");
540 #endif
541 
542     return vwin;
543 }
544 
545 /* handle backslash continuation of console command line */
546 
547 static int
command_continues(char * targ,const gchar * src,int * err)548 command_continues (char *targ, const gchar *src, int *err)
549 {
550     int contd = 0;
551 
552     if (strlen(targ) + strlen(src) + 1 > MAXLINE) {
553 	*err = E_TOOLONG;
554 	return 0;
555     } else {
556 	contd = ends_with_backslash(src);
557 	strcat(targ, src);
558 	if (contd) {
559 	    char *p = strrchr(targ, '\\');
560 
561 	    if (p - targ > 0 && !isspace(*(p - 1))) {
562 		*p = ' ';
563 	    } else {
564 		*p = '\0';
565 	    }
566 	}
567     }
568 
569     return contd;
570 }
571 
console_get_current_line(GtkTextBuffer * buf,GtkTextIter * iter)572 static gchar *console_get_current_line (GtkTextBuffer *buf,
573 					GtkTextIter *iter)
574 {
575     GtkTextIter start, end;
576 
577     start = end = *iter;
578     gtk_text_iter_set_line_index(&start, 2);
579     gtk_text_iter_forward_to_line_end(&end);
580 
581     return gtk_text_buffer_get_text(buf, &start, &end, FALSE);
582 }
583 
584 #define IS_BACKKEY(k) (k == GDK_BackSpace || k == GDK_Left)
585 
console_key_handler(GtkWidget * cview,GdkEvent * event,gpointer p)586 static gint console_key_handler (GtkWidget *cview,
587 				 GdkEvent *event,
588 				 gpointer p)
589 {
590     GdkEventKey *kevent = (GdkEventKey *) event;
591     guint keyval = kevent->keyval;
592     guint upkey = gdk_keyval_to_upper(keyval);
593     GtkTextIter ins, end;
594     GtkTextBuffer *buf;
595     GtkTextMark *mark;
596     gint ctrl = 0;
597 
598 #if KDEBUG
599     fprintf(stderr, "HERE console_key_handler (keyval %u, %s)\n",
600 	    keyval, gdk_keyval_name(keyval));
601 #endif
602 
603 #ifdef OS_OSX
604     if (cmd_key(kevent)) {
605 	if (upkey == GDK_C || upkey == GDK_X) {
606 	    /* allow regular copy/cut behavior */
607 	    return FALSE;
608 	}
609     }
610 #endif
611 
612     if (kevent->state & GDK_CONTROL_MASK) {
613 	if (keyval == GDK_Control_L || keyval == GDK_Control_R) {
614 	    return FALSE;
615 	} else if (upkey == GDK_C || upkey == GDK_X) {
616 	    /* allow regular copy/cut behavior */
617 	    return FALSE;
618 	} else if (swallow && (upkey == GDK_Page_Up || upkey == GDK_Tab)) {
619 	    gtk_widget_grab_focus(mdata->listbox);
620 	    return TRUE;
621 	} else {
622 	    ctrl = 1;
623 	}
624     }
625 
626     buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(cview));
627 
628     /* first find out where the insertion point and end are */
629     mark = gtk_text_buffer_get_insert(buf);
630     gtk_text_buffer_get_iter_at_mark(buf, &ins, mark);
631     gtk_text_buffer_get_end_iter(buf, &end);
632 
633     /* if the insertion point is not on the last line, move it */
634     if (gtk_text_iter_get_line(&ins) != gtk_text_iter_get_line(&end)) {
635 	gtk_text_buffer_place_cursor(buf, &end);
636 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(cview), TRUE);
637 	gtk_text_buffer_get_end_iter(buf, &ins);
638     }
639 
640     if (keyval == GDK_Home && (kevent->state & GDK_SHIFT_MASK)) {
641 	/* "select to start of line" */
642 	GtkTextIter start = ins;
643 
644 	gtk_text_iter_set_line_index(&start, 2);
645 	gtk_text_buffer_select_range(buf, &start, &ins);
646 	return TRUE;
647     }
648 
649     if (IS_BACKKEY(keyval)) {
650 	/* if we're at the start of the input line, block backspacing */
651 	if (gtk_text_iter_get_line_index(&ins) < 3) {
652 	    return TRUE;
653 	}
654     } else if (keyval == GDK_Home || (ctrl && upkey == GDK_A)) {
655 	/* go to start of typing area */
656 	gtk_text_iter_set_line_index(&ins, 2);
657 	gtk_text_buffer_place_cursor(buf, &ins);
658 	return TRUE;
659     }
660 
661     /* At this point 'ins' indicates the insertion point and
662        'end' points to the end of the current line of input,
663        These may or may not be the same thing.
664     */
665 
666     if (keyval == GDK_Return) {
667 	/* execute the command, unless backslash-continuation
668 	   is happening
669 	*/
670 	ExecState *state;
671 	gchar *thisline;
672 	int contd = 0, err = 0;
673 
674 	state = g_object_get_data(G_OBJECT(cview), "ExecState");
675 	thisline = console_get_current_line(buf, &ins);
676 
677 	if (thisline != NULL) {
678 	    g_strstrip(thisline);
679 	    contd = command_continues(state->line, thisline, &err);
680 	    g_free(thisline);
681 	}
682 
683 	if (err) {
684 	    gui_errmsg(err);
685 	} else if (contd) {
686 	    console_insert_prompt(buf, &end, "\n> ");
687 	    console_scroll_to_end(cview, buf, &end);
688 	} else {
689 	    /* request execution of the completed command */
690 	    update_console(state, cview);
691 	    if (state->cmd->ci == QUIT) {
692 		windata_t *vwin = (windata_t *) p;
693 
694 		gtk_widget_destroy(vwin->main);
695 	    }
696 	}
697 
698 	return TRUE; /* handled */
699     }
700 
701     if (keyval == GDK_Up || keyval == GDK_Down) {
702 	/* up/down arrows: navigate the command history */
703 	GtkTextIter start = ins;
704 	const char *histline;
705 
706 	if (hpos == hlines && keyval == GDK_Up) {
707 	    g_free(hist0);
708 	    hist0 = console_get_current_line(buf, &ins);
709 	}
710 
711 	histline = fetch_history_line(keyval);
712 
713 	if (histline != NULL || keyval == GDK_Down) {
714 	    gtk_text_iter_set_line_index(&start, 2);
715 	    gtk_text_buffer_delete(buf, &start, &end);
716 	    if (histline != NULL) {
717 		gtk_text_buffer_insert(buf, &start, histline, -1);
718 	    } else if (hpos == hlines && hist0 != NULL) {
719 		gtk_text_buffer_insert(buf, &start, hist0, -1);
720 	    }
721 	}
722 
723 	return TRUE;
724     }
725 
726 #ifdef HAVE_GTKSV_COMPLETION
727     if (keyval == GDK_Tab && console_completion == COMPLETE_USER &&
728 	maybe_try_completion((windata_t *) p)) {
729 	call_user_completion(cview);
730 	return TRUE;
731     }
732 #endif
733 
734     if (script_auto_bracket && lbracket(keyval)) {
735 	return script_bracket_handler((windata_t *) p, keyval);
736     }
737 
738     return FALSE;
739 }
740