"Fossies" - the Fresh Open Source Software Archive

Member "gretl-2020e/gui/console.c" (17 Aug 2020, 16778 Bytes) of package /linux/misc/gretl-2020e.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "console.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2020d_vs_2020e.

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