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