Ticket #4196: InstructionStation_v3.3.patch

File InstructionStation_v3.3.patch, 69.2 KB (added by psprint, 3 years ago)

Added open in … checkbox

  • lib/global.h

    From 2e3a98f5dba4097935c48df520e89a08ee3b9748 Mon Sep 17 00:00:00 2001
    From: Sebastian Gniazdowski <sgniazdowski@gmail.com>
    Date: Wed, 10 Feb 2021 13:41:35 -0600
    Subject: =?UTF-8?q?Instruction=20Station=20=E2=80=93=20a=20CLI=20window=20?=
     =?UTF-8?q?for=20MCEdit?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    ---
     lib/global.h               |   3 +
     lib/keybind.c              |   1 +
     lib/keybind.h              |   1 +
     lib/tty/key.c              |  10 +-
     lib/widget/wtools.c        |  40 +-
     lib/widget/wtools.h        |   4 +
     misc/mc.default.keymap     |   1 +
     misc/mc.emacs.keymap       |   1 +
     src/editor/Makefile.am     |   1 +
     src/editor/edit-impl.h     |  24 +-
     src/editor/edit.c          |  27 +-
     src/editor/editcmd.c       |  26 +-
     src/editor/editdraw.c      |   1 -
     src/editor/editmenu.c      |   3 +
     src/editor/editwidget.c    | 592 +++++++++++++-------------
     src/editor/editwidget.h    |   3 +
     src/editor/instr_station.c | 821 +++++++++++++++++++++++++++++++++++++
     src/editor/instr_station.h |  86 ++++
     src/keybind-defaults.c     |   1 +
     19 files changed, 1339 insertions(+), 307 deletions(-)
     create mode 100644 src/editor/instr_station.c
     create mode 100644 src/editor/instr_station.h
    
    diff --git a/lib/global.h b/lib/global.h
    index f1a3e702c..1f76061c6 100644
    a b  
    103103#include "lib/logging.h" 
    104104#endif 
    105105 
     106#define has_flag(x,y) (((x) & (y)) != 0) 
     107#define set_flag_in(x,y) ((x) |= (y)) 
     108 
    106109/* Just for keeping Your's brains from invention a proper size of the buffer :-) */ 
    107110#define BUF_10K 10240L 
    108111#define BUF_8K  8192L 
  • lib/keybind.c

    diff --git a/lib/keybind.c b/lib/keybind.c
    index 65ac5f15c..2f9d3cf29 100644
    a b static name_keymap_t command_names_start[] = { 
    300300    ADD_KEYMAP_NAME (EditMail), 
    301301    ADD_KEYMAP_NAME (ParagraphFormat), 
    302302    ADD_KEYMAP_NAME (MatchBracket), 
     303    ADD_KEYMAP_NAME (InstructionStation), 
    303304    ADD_KEYMAP_NAME (ExternalCommand), 
    304305    ADD_KEYMAP_NAME (MacroStartRecord), 
    305306    ADD_KEYMAP_NAME (MacroStopRecord), 
  • lib/keybind.h

    diff --git a/lib/keybind.h b/lib/keybind.h
    index 4e954ade2..18c7d95e4 100644
    a b enum 
    324324    CK_SyntaxOnOff, 
    325325    CK_SyntaxChoose, 
    326326    CK_InsertLiteral, 
     327    CK_InstructionStation, 
    327328    CK_ExternalCommand, 
    328329    CK_Date, 
    329330    CK_EditMail, 
  • lib/tty/key.c

    diff --git a/lib/tty/key.c b/lib/tty/key.c
    index 58a2f018b..b55fb8f07 100644
    a b tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block) 
    20312031            time_out.tv_sec = 0; 
    20322032            time_out.tv_usec = 0; 
    20332033        } 
    2034  
     2034        else if (time_addr == NULL) 
     2035        { 
     2036            /* Standard timeout to call GLib main loop */ 
     2037            time_addr = &time_out; 
     2038            time_addr->tv_sec = 0; 
     2039            time_addr->tv_usec = 200000; 
     2040        } 
    20352041        tty_enable_interrupt_key (); 
    20362042        flag = select (nfd, &select_set, NULL, NULL, time_addr); 
    20372043        tty_disable_interrupt_key (); 
    tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block) 
    20432049         */ 
    20442050        if (flag == 0) 
    20452051        { 
     2052            /* Provide CPU time to GLib main loop's default context */ 
     2053            g_main_context_iteration (NULL, FALSE); 
    20462054            if (redo_event) 
    20472055                return EV_MOUSE; 
    20482056            if (!block || tty_got_winch ()) 
  • lib/widget/wtools.c

    diff --git a/lib/widget/wtools.c b/lib/widget/wtools.c
    index 94ace3167..7d67d05ab 100644
    a b bg_message (int dummy, int *flags, char *title, const char *text) 
    193193static char * 
    194194fg_input_dialog_help (const char *header, const char *text, const char *help, 
    195195                      const char *history_name, const char *def_text, gboolean strip_password, 
    196                       input_complete_t completion_flags) 
     196                      input_complete_t completion_flags, quick_widget_t *add_w) 
    197197{ 
    198198    char *p_text; 
    199199    char histname[64] = "inp|"; 
    fg_input_dialog_help (const char *header, const char *text, const char *help, 
    218218    } 
    219219 
    220220    { 
    221         quick_widget_t quick_widgets[] = { 
     221        int i=1, norm_i=0, add_i=0; 
     222        quick_widget_t quick_widgets[15] = { 
    222223            /* *INDENT-OFF* */ 
    223224            QUICK_LABELED_INPUT (p_text, input_label_above, def_text, histname, &my_str, 
    224                                  NULL, is_passwd, strip_password, completion_flags), 
    225             QUICK_BUTTONS_OK_CANCEL, 
    226             QUICK_END 
     225                                 NULL, is_passwd, strip_password, completion_flags) 
    227226            /* *INDENT-ON* */ 
    228227        }; 
    229  
     228        quick_widget_t press_end[] = { QUICK_BUTTONS_OK_CANCEL, QUICK_END }; 
    230229        quick_dialog_t qdlg = { 
    231230            -1, -1, COLS / 2, header, 
    232231            help, quick_widgets, NULL, NULL 
    233232        }; 
    234233 
     234        /* Handling of additional widgets optionally passed via last argument `add_w` */ 
     235 
     236        /* Copy/append any extra widgets (max 10) */ 
     237        while (add_w != NULL && add_w[add_i].widget_type != quick_end && i < 15) 
     238            quick_widgets[i++] = add_w[add_i++]; 
     239 
     240        /* Copy normal buttons */ 
     241        while (norm_i<sizeof(press_end)/sizeof(quick_widget_t) && i<15) 
     242            quick_widgets[i++] = press_end[norm_i++]; 
     243 
    235244        ret = quick_dialog (&qdlg); 
    236245    } 
    237246 
    input_dialog_help (const char *header, const char *text, const char *help, 
    543552        { 
    544553            void *p; 
    545554            char *(*f) (const char *, const char *, const char *, const char *, const char *, 
    546                         gboolean, input_complete_t); 
     555                        gboolean, input_complete_t, quick_widget_t *add_w); 
    547556        } func; 
    548557        func.f = fg_input_dialog_help; 
    549         return wtools_parent_call_string (func.p, 7, 
     558        return wtools_parent_call_string (func.p, 8, 
    550559                                          strlen (header), header, strlen (text), 
    551560                                          text, strlen (help), help, 
    552561                                          strlen (history_name), history_name, 
    553562                                          strlen (def_text), def_text, 
    554563                                          sizeof (gboolean), strip_password, 
    555                                           sizeof (input_complete_t), completion_flags); 
     564                                          sizeof (input_complete_t), completion_flags, 
     565                                          sizeof (quick_widget_t*), NULL); 
    556566    } 
    557567    else 
    558568#endif /* ENABLE_BACKGROUND */ 
    559569        return fg_input_dialog_help (header, text, help, history_name, def_text, strip_password, 
    560                                      completion_flags); 
     570                                     completion_flags, NULL); 
    561571} 
    562572 
    563573/* --------------------------------------------------------------------------------------------- */ 
    input_dialog (const char *header, const char *text, const char *history_name, co 
    573583 
    574584/* --------------------------------------------------------------------------------------------- */ 
    575585 
     586char * 
     587input_dialog_ext (const char *header, const char *text, const char *history_name, const char *def_text, 
     588              input_complete_t completion_flags, quick_widget_t *add_w) 
     589{ 
     590    return fg_input_dialog_help (header, text, "[Input Line Keys]", history_name, def_text, FALSE, 
     591                                     completion_flags, add_w); 
     592} 
     593 
     594/* --------------------------------------------------------------------------------------------- */ 
     595 
    576596char * 
    577597input_expand_dialog (const char *header, const char *text, 
    578598                     const char *history_name, const char *def_text, 
  • lib/widget/wtools.h

    diff --git a/lib/widget/wtools.h b/lib/widget/wtools.h
    index b1f7a9b47..d5e7b9220 100644
    a b void display_postponed_messages (void); 
    7676char *input_dialog (const char *header, const char *text, 
    7777                    const char *history_name, const char *def_text, 
    7878                    input_complete_t completion_flags); 
     79/* The input dialogs */ 
     80char *input_dialog_ext (const char *header, const char *text, 
     81                    const char *history_name, const char *def_text, 
     82                    input_complete_t completion_flags, quick_widget_t *add_w); 
    7983char *input_dialog_help (const char *header, const char *text, const char *help, 
    8084                         const char *history_name, const char *def_text, gboolean strip_password, 
    8185                         input_complete_t completion_flags); 
  • misc/mc.default.keymap

    diff --git a/misc/mc.default.keymap b/misc/mc.default.keymap
    index 73b4c0eab..a7269d07f 100644
    a b Sort = alt-t 
    352352Mail = alt-m 
    353353ParagraphFormat = alt-p 
    354354MatchBracket = alt-b 
     355InstructionStation = alt-i 
    355356ExternalCommand = alt-u 
    356357UserMenu = f11 
    357358Menu = f9 
  • misc/mc.emacs.keymap

    diff --git a/misc/mc.emacs.keymap b/misc/mc.emacs.keymap
    index 955929444..98412b75f 100644
    a b Sort = alt-t 
    351351# Mail = 
    352352ParagraphFormat = alt-p 
    353353# MatchBracket = 
     354InstructionStation = alt-i 
    354355ExternalCommand = alt-u 
    355356UserMenu = f11 
    356357Menu = f9 
  • src/editor/Makefile.am

    diff --git a/src/editor/Makefile.am b/src/editor/Makefile.am
    index 235ed76af..0ac4b4af7 100644
    a b libedit_la_SOURCES = \ 
    1818        editmenu.c \ 
    1919        editoptions.c \ 
    2020        editwidget.c editwidget.h \ 
     21        instr_station.c instr_station.h \ 
    2122        etags.c etags.h \ 
    2223        format.c \ 
    2324        syntax.c 
  • src/editor/edit-impl.h

    diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h
    index 881ef549b..083c6a3ee 100644
    a b typedef enum 
    8888    LB_MAC 
    8989} LineBreaks; 
    9090 
     91typedef enum 
     92{ 
     93    EDIT_DO_INIT_BASE_CLASS = 1 << 0, 
     94 
     95} edit_init_flags_t; 
     96 
     97typedef enum instr_stn_flags 
     98{ 
     99    INSTR_STN_NO_FLAGS = 0, 
     100    INSTR_STN_NO_STDIN = 1<<17, 
     101    INSTR_STN_NO_PROMPT = 1<<18 
     102} instr_stn_flags_t; 
     103 
    91104typedef enum 
    92105{ 
    93106    EDIT_QUICK_SAVE = 0, 
    extern char *edit_window_state_char; 
    146159extern char *edit_window_close_char; 
    147160 
    148161/*** declarations of public functions ************************************************************/ 
     162cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 
     163cb_ret_t edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 
     164cb_ret_t edit_dialog_command_execute (WDialog * h, long command, void *data); 
     165 
     166void edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event); 
    149167 
    150168gboolean edit_add_window (WDialog * h, int y, int x, int lines, int cols, 
    151169                          const vfs_path_t * f, long fline); 
    void edit_move_to_prev_col (WEdit * edit, off_t p); 
    169187long edit_get_col (const WEdit * edit); 
    170188void edit_update_curs_row (WEdit * edit); 
    171189void edit_update_curs_col (WEdit * edit); 
     190gboolean edit_newline_end_check_only (const WEdit * edit); 
    172191void edit_find_bracket (WEdit * edit); 
    173192gboolean edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line); 
    174193void edit_set_codeset (WEdit * edit); 
    void edit_delete_line (WEdit * edit); 
    180199 
    181200int edit_delete (WEdit * edit, gboolean byte_delete); 
    182201int edit_backspace (WEdit * edit, gboolean byte_delete); 
     202void edit_insert_string (WEdit * edit, char *str_text, gsize len); 
    183203void edit_insert (WEdit * edit, int c); 
    184204void edit_insert_over (WEdit * edit); 
    185205void edit_cursor_move (WEdit * edit, off_t increment); 
    char *edit_get_write_filter (const vfs_path_t * write_name_vpath, 
    193213gboolean edit_save_confirm_cmd (WEdit * edit); 
    194214gboolean edit_save_as_cmd (WEdit * edit); 
    195215WEdit *edit_init (WEdit * edit, int y, int x, int lines, int cols, 
    196                   const vfs_path_t * filename_vpath, long line); 
     216                  const vfs_path_t * filename_vpath, long line, edit_init_flags_t flags); 
    197217gboolean edit_clean (WEdit * edit); 
    198218gboolean edit_ok_to_exit (WEdit * edit); 
    199219file_suitable_rank_t edit_check_file_suitable (const vfs_path_t * fs_path); 
    void edit_center_display (WEdit * e, long diff); 
    249269void edit_move_display (WEdit * e, long line); 
    250270void edit_word_wrap (WEdit * edit); 
    251271int edit_sort_cmd (WEdit * edit); 
     272gboolean edit_add_instr_stn_window (WDialog * h, instr_stn_flags_t flags, void *data); 
     273 
    252274int edit_ext_cmd (WEdit * edit); 
    253275 
    254276int edit_store_macro_cmd (WEdit * edit); 
  • src/editor/edit.c

    diff --git a/src/editor/edit.c b/src/editor/edit.c
    index 8477939aa..ea193a8de 100644
    a b edit_modification (WEdit * edit) 
    652652{ 
    653653    edit->caches_valid = FALSE; 
    654654 
    655     /* raise lock when file modified */ 
    656     if (!edit->modified && !edit->delete_file) 
     655    /* raise lock when file is about to be modified */ 
     656    if (edit->filename_vpath && !edit->modified && !edit->delete_file) 
    657657        edit->locked = lock_file (edit->filename_vpath); 
    658658    edit->modified = 1; 
    659659} 
    edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath) 
    21062106 
    21072107WEdit * 
    21082108edit_init (WEdit * edit, int y, int x, int lines, int cols, const vfs_path_t * filename_vpath, 
    2109            long line) 
     2109           long line, edit_init_flags_t flags) 
    21102110{ 
    21112111    gboolean to_free = FALSE; 
    21122112 
    edit_init (WEdit * edit, int y, int x, int lines, int cols, const vfs_path_t * f 
    21282128        edit->fullscreen = fullscreen; 
    21292129        edit->loc_prev = loc_prev; 
    21302130    } 
    2131     else 
     2131    if (has_flag (flags, EDIT_DO_INIT_BASE_CLASS) || edit == NULL) 
    21322132    { 
    21332133        Widget *w; 
    2134         edit = g_malloc0 (sizeof (WEdit)); 
    2135         to_free = TRUE; 
     2134        if (edit == NULL) 
     2135        { 
     2136            edit = g_malloc0 (sizeof (WEdit)); 
     2137            to_free = TRUE; 
     2138        } 
    21362139 
    21372140        w = WIDGET (edit); 
    21382141        widget_init (w, y, x, lines, cols, NULL, NULL); 
    edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line) 
    22712274    e->fullscreen = edit->fullscreen; 
    22722275    e->loc_prev = edit->loc_prev; 
    22732276 
    2274     if (edit_init (e, w->y, w->x, w->lines, w->cols, filename_vpath, line) == NULL) 
     2277    if (edit_init (e, w->y, w->x, w->lines, w->cols, filename_vpath, line, 0) == NULL) 
    22752278    { 
    22762279        g_free (e); 
    22772280        return FALSE; 
    edit_push_redo_action (WEdit * edit, long c) 
    25282531        edit->redo_stack_bottom = edit->redo_stack_pointer = 0; 
    25292532} 
    25302533 
     2534/* --------------------------------------------------------------------------------------------- */ 
     2535 
     2536void 
     2537edit_insert_string (WEdit * edit, char *str_text, gsize len) 
     2538{ 
     2539    size_t i; 
     2540    for (i = 0; i < len; i++) 
     2541        edit_insert (edit, str_text[i]); 
     2542} 
     2543 
    25312544/* --------------------------------------------------------------------------------------------- */ 
    25322545/** 
    25332546   Basic low level single character buffer alterations and movements at the cursor. 
  • src/editor/editcmd.c

    diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c
    index be58f3db7..3f72c097e 100644
    a b edit_complete_word_insert_recoded_completion (WEdit * edit, char *completion, gs 
    15731573/*** public functions ****************************************************************************/ 
    15741574/* --------------------------------------------------------------------------------------------- */ 
    15751575 
     1576gboolean 
     1577edit_newline_end_check_only (const WEdit * e) 
     1578{ 
     1579    const edit_buffer_t *buf = &e->buffer; 
     1580    return (buf->size > 0 && edit_buffer_get_byte (buf, buf->size - 1) == '\n'); 
     1581} 
     1582 
     1583/* --------------------------------------------------------------------------------------------- */ 
     1584 
    15761585void 
    15771586edit_refresh_cmd (void) 
    15781587{ 
    edit_ext_cmd (WEdit * edit) 
    32723281{ 
    32733282    char *exp, *tmp, *tmp_edit_temp_file; 
    32743283    int e; 
     3284    gboolean run_in_cli = FALSE; 
     3285 
     3286    quick_widget_t chbox[3] = { QUICK_SEPARATOR(TRUE), 
     3287                         QUICK_CHECKBOX("&Open in CLI window", &run_in_cli, NULL),  
     3288                         QUICK_END}; 
    32753289     
    32763290    exp = 
    3277         input_dialog (_("Paste output of external command"), 
     3291        input_dialog_ext (_("Paste output of external command"), 
    32783292                      _("Enter shell command(s):"), MC_HISTORY_EDIT_PASTE_EXTCMD, INPUT_LAST_TEXT, 
    32793293                      INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES 
    32803294                      | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS | 
    3281                       INPUT_COMPLETE_SHELL_ESC); 
     3295                      INPUT_COMPLETE_SHELL_ESC, chbox); 
    32823296 
    32833297    if (!exp) 
    32843298        return 1; 
    32853299 
     3300    /* Should start a CLI window with the command ? */ 
     3301    if (run_in_cli) 
     3302    { 
     3303        edit_add_instr_stn_window (DIALOG(WIDGET(edit)->owner), INSTR_STN_NO_STDIN | INSTR_STN_NO_PROMPT, exp); 
     3304        g_free(exp); 
     3305        return 0; 
     3306    } 
     3307 
    32863308    tmp_edit_temp_file = mc_config_get_full_path (EDIT_HOME_TEMP_FILE); 
    32873309    tmp = g_strconcat (exp, " > ", tmp_edit_temp_file, (char *) NULL); 
    32883310    g_free (tmp_edit_temp_file); 
  • src/editor/editdraw.c

    diff --git a/src/editor/editdraw.c b/src/editor/editdraw.c
    index 61bbdcbee..80495dcd0 100644
    a b edit_draw_this_line (WEdit * edit, off_t b, long row, long start_col, long end_c 
    820820    } 
    821821 
    822822    p->ch = 0; 
    823  
    824823    print_to_widget (edit, row, start_col, start_col_real, end_col, line, line_stat, book_mark); 
    825824} 
    826825 
  • src/editor/editmenu.c

    diff --git a/src/editor/editmenu.c b/src/editor/editmenu.c
    index c20e9fb7e..0c8e60801 100644
    a b create_window_menu (void) 
    238238    entries = g_list_prepend (entries, menu_entry_create (_("&Next"), CK_WindowNext)); 
    239239    entries = g_list_prepend (entries, menu_entry_create (_("&Previous"), CK_WindowPrev)); 
    240240    entries = g_list_prepend (entries, menu_entry_create (_("&List..."), CK_WindowList)); 
     241    entries = 
     242        g_list_prepend (entries, 
     243                        menu_entry_create (_("Instr&uction Station"), CK_InstructionStation)); 
    241244 
    242245    return g_list_reverse (entries); 
    243246} 
  • src/editor/editwidget.c

    diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c
    index e32541cd9..bfea60abb 100644
    a b  
    6868#ifdef HAVE_ASPELL 
    6969#include "spell.h" 
    7070#endif 
     71#include "src/editor/instr_station.h" 
    7172 
    7273/*** global variables ****************************************************************************/ 
    7374 
    static unsigned int edit_dlg_init_refcounter = 0; 
    8687 
    8788/*** file scope functions ************************************************************************/ 
    8889 
    89 static cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, 
    90                                       void *data); 
    91  
    9290/* --------------------------------------------------------------------------------------------- */ 
    9391 
    9492static char * 
    edit_get_title (const WDialog * h, size_t len) 
    634632    return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL); 
    635633} 
    636634 
    637 /* --------------------------------------------------------------------------------------------- */ 
    638  
    639 static cb_ret_t 
    640 edit_dialog_command_execute (WDialog * h, long command) 
    641 { 
    642     WGroup *g = GROUP (h); 
    643     Widget *wh = WIDGET (h); 
    644     cb_ret_t ret = MSG_HANDLED; 
    645  
    646     switch (command) 
    647     { 
    648     case CK_EditNew: 
    649         edit_add_window (h, wh->y + 1, wh->x, wh->lines - 2, wh->cols, NULL, 0); 
    650         break; 
    651     case CK_EditFile: 
    652         edit_load_cmd (h, NULL); 
    653         break; 
    654     case CK_OtherFile: 
    655         { 
    656             WEdit *e = (WEdit *) g->current->data; 
    657             gboolean retflag = FALSE; 
    658  
    659             if (e != NULL && edit_widget_is_editor (CONST_WIDGET (e))) 
    660             { 
    661                 retflag = edit_compute_other_file_vfs_path (e); 
    662                 if (retflag) 
    663                     retflag = edit_switch_to_file (h, e->otherfile_vpath); 
    664             } 
    665             if (!retflag) 
    666                 ret = MSG_NOT_HANDLED; 
    667         } 
    668         break; 
    669     case CK_History: 
    670         edit_load_file_from_history (h); 
    671         break; 
    672     case CK_EditSyntaxFile: 
    673         edit_load_syntax_file (h); 
    674         break; 
    675     case CK_EditUserMenu: 
    676         edit_load_menu_file (h); 
    677         break; 
    678     case CK_Close: 
    679         /* if there are no opened files anymore, close MC editor */ 
    680         if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) && 
    681             edit_close_cmd ((WEdit *) g->current->data) && find_editor (h) == NULL) 
    682             dlg_stop (h); 
    683         break; 
    684     case CK_Help: 
    685         edit_help (); 
    686         /* edit->force |= REDRAW_COMPLETELY; */ 
    687         break; 
    688     case CK_Menu: 
    689         edit_menu_cmd (h); 
    690         break; 
    691     case CK_Quit: 
    692     case CK_Cancel: 
    693         /* don't close editor due to SIGINT, but stop move/resize window */ 
    694         { 
    695             Widget *w = WIDGET (g->current->data); 
    696  
    697             if (edit_widget_is_editor (w) && ((WEdit *) w)->drag_state != MCEDIT_DRAG_NONE) 
    698                 edit_restore_size ((WEdit *) w); 
    699             else if (command == CK_Quit) 
    700                 dlg_stop (h); 
    701         } 
    702         break; 
    703     case CK_About: 
    704         edit_about (); 
    705         break; 
    706     case CK_SyntaxOnOff: 
    707         edit_syntax_onoff_cmd (h); 
    708         break; 
    709     case CK_ShowTabTws: 
    710         edit_show_tabs_tws_cmd (h); 
    711         break; 
    712     case CK_ShowMargin: 
    713         edit_show_margin_cmd (h); 
    714         break; 
    715     case CK_ShowNumbers: 
    716         edit_show_numbers_cmd (h); 
    717         break; 
    718     case CK_Refresh: 
    719         edit_refresh_cmd (); 
    720         break; 
    721     case CK_Shell: 
    722         toggle_subshell (); 
    723         break; 
    724     case CK_LearnKeys: 
    725         learn_keys (); 
    726         break; 
    727     case CK_WindowMove: 
    728     case CK_WindowResize: 
    729         if (edit_widget_is_editor (CONST_WIDGET (g->current->data))) 
    730             edit_handle_move_resize ((WEdit *) g->current->data, command); 
    731         break; 
    732     case CK_WindowList: 
    733         edit_window_list (h); 
    734         break; 
    735     case CK_WindowCascade: 
    736         edit_window_cascade (h); 
    737         break; 
    738     case CK_WindowTile: 
    739         edit_window_tile (h); 
    740         break; 
    741     case CK_WindowNext: 
    742         group_select_next_widget (g); 
    743         break; 
    744     case CK_WindowPrev: 
    745         group_select_prev_widget (g); 
    746         break; 
    747     case CK_Options: 
    748         edit_options_dialog (h); 
    749         break; 
    750     case CK_OptionsSaveMode: 
    751         edit_save_mode_cmd (); 
    752         break; 
    753     case CK_SaveSetup: 
    754         save_setup_cmd (); 
    755         break; 
    756     default: 
    757         ret = MSG_NOT_HANDLED; 
    758         break; 
    759     } 
    760  
    761     return ret; 
    762 } 
    763  
    764635/* --------------------------------------------------------------------------------------------- */ 
    765636/* 
    766637 * Translate the keycode into either 'command' or 'char_for_insertion'. 
    edit_update_cursor (WEdit * edit, const mouse_event_t * event) 
    1018889    return done; 
    1019890} 
    1020891 
    1021 /* --------------------------------------------------------------------------------------------- */ 
    1022 /** Callback for the edit dialog */ 
    1023  
    1024 static cb_ret_t 
    1025 edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
    1026 { 
    1027     WGroup *g = GROUP (w); 
    1028     WDialog *h = DIALOG (w); 
    1029  
    1030     switch (msg) 
    1031     { 
    1032     case MSG_INIT: 
    1033         edit_dlg_init (); 
    1034         return MSG_HANDLED; 
    1035  
    1036     case MSG_RESIZE: 
    1037         dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL); 
    1038         menubar_arrange (find_menubar (h)); 
    1039         return MSG_HANDLED; 
    1040  
    1041     case MSG_ACTION: 
    1042         { 
    1043             /* Handle shortcuts, menu, and buttonbar. */ 
    1044  
    1045             cb_ret_t result; 
    1046  
    1047             result = edit_dialog_command_execute (h, parm); 
    1048  
    1049             /* We forward any commands coming from the menu, and which haven't been 
    1050                handled by the dialog, to the focused WEdit window. */ 
    1051             if (result == MSG_NOT_HANDLED && sender == WIDGET (find_menubar (h))) 
    1052                 result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL); 
    1053  
    1054             return result; 
    1055         } 
    1056  
    1057     case MSG_KEY: 
    1058         { 
    1059             Widget *we = WIDGET (g->current->data); 
    1060             cb_ret_t ret = MSG_NOT_HANDLED; 
    1061  
    1062             if (edit_widget_is_editor (we)) 
    1063             { 
    1064                 gboolean ext_mode; 
    1065                 long command; 
    1066  
    1067                 /* keep and then extmod flag */ 
    1068                 ext_mode = we->ext_mode; 
    1069                 command = widget_lookup_key (we, parm); 
    1070                 we->ext_mode = ext_mode; 
    1071  
    1072                 if (command == CK_IgnoreKey) 
    1073                     we->ext_mode = FALSE; 
    1074                 else 
    1075                 { 
    1076                     ret = edit_dialog_command_execute (h, command); 
    1077                     /* if command was not handled, keep the extended mode 
    1078                        for the further key processing */ 
    1079                     if (ret == MSG_HANDLED) 
    1080                         we->ext_mode = FALSE; 
    1081                 } 
    1082             } 
    1083  
    1084             /* 
    1085              * Due to the "end of bracket" escape the editor sees input with is_idle() == false 
    1086              * (expects more characters) and hence doesn't yet refresh the screen, but then 
    1087              * no further characters arrive (there's only an "end of bracket" which is swallowed 
    1088              * by tty_get_event()), so you end up with a screen that's not refreshed after pasting. 
    1089              * So let's trigger an IDLE signal. 
    1090              */ 
    1091             if (!is_idle ()) 
    1092                 widget_idle (w, TRUE); 
    1093             return ret; 
    1094         } 
    1095  
    1096         /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */ 
    1097     case MSG_UNHANDLED_KEY: 
    1098         return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED; 
    1099  
    1100     case MSG_VALIDATE: 
    1101         edit_quit (h); 
    1102         return MSG_HANDLED; 
    1103  
    1104     case MSG_END: 
    1105         edit_dlg_deinit (); 
    1106         return MSG_HANDLED; 
    1107  
    1108     case MSG_IDLE: 
    1109         widget_idle (w, FALSE); 
    1110         return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL); 
    1111  
    1112     default: 
    1113         return dlg_default_callback (w, sender, msg, parm, data); 
    1114     } 
    1115 } 
    1116  
    1117892/* --------------------------------------------------------------------------------------------- */ 
    1118893 
    1119894/** 
    edit_dialog_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm 
    1208983 
    1209984/* --------------------------------------------------------------------------------------------- */ 
    1210985 
    1211 static cb_ret_t 
     986/** 
     987 * Handle move/resize mouse events. 
     988 */ 
     989static void 
     990edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
     991{ 
     992    WEdit *edit = (WEdit *) (w); 
     993    Widget *h = WIDGET (w->owner); 
     994    int global_x, global_y; 
     995 
     996    if (msg == MSG_MOUSE_UP) 
     997    { 
     998        /* Exit move/resize mode. */ 
     999        edit_execute_cmd (edit, CK_Enter, -1); 
     1000        edit_update_screen (edit);      /* Paint the buttonbar over our possibly overlapping frame. */ 
     1001        return; 
     1002    } 
     1003 
     1004    if (msg != MSG_MOUSE_DRAG) 
     1005        /** 
     1006         * We ignore any other events. Specifically, MSG_MOUSE_DOWN. 
     1007         * 
     1008         * When the move/resize is initiated by the menu, we let the user 
     1009         * stop it by clicking with the mouse. Which is why we don't want 
     1010         * a mouse down to affect the window. 
     1011         */ 
     1012        return; 
     1013 
     1014    /* Convert point to global coordinates for easier calculations. */ 
     1015    global_x = event->x + w->x; 
     1016    global_y = event->y + w->y; 
     1017 
     1018    /* Clamp the point to the dialog's client area. */ 
     1019    global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */ 
     1020    global_x = CLAMP (global_x, h->x, h->x + h->cols - 1);      /* Currently a no-op, as the dialog has no left/right margins. */ 
     1021 
     1022    if (edit->drag_state == MCEDIT_DRAG_MOVE) 
     1023    { 
     1024        w->y = global_y; 
     1025        w->x = global_x - edit->drag_state_start; 
     1026    } 
     1027    else if (edit->drag_state == MCEDIT_DRAG_RESIZE) 
     1028    { 
     1029        w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1); 
     1030        w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1); 
     1031    } 
     1032 
     1033    edit->force |= REDRAW_COMPLETELY;   /* Not really needed as WEdit's MSG_DRAW already does this. */ 
     1034 
     1035    /* We draw the whole dialog because dragging/resizing exposes area beneath. */ 
     1036    widget_draw (WIDGET (w->owner)); 
     1037} 
     1038 
     1039/* --------------------------------------------------------------------------------------------- */ 
     1040/*** public functions ****************************************************************************/ 
     1041/* --------------------------------------------------------------------------------------------- */ 
     1042 
     1043cb_ret_t 
     1044edit_dialog_command_execute (WDialog * h, long command, void *data) 
     1045{ 
     1046    WGroup *g = GROUP (h); 
     1047    Widget *wh = WIDGET (h); 
     1048    cb_ret_t ret = MSG_HANDLED; 
     1049 
     1050    switch (command) 
     1051    { 
     1052    case CK_EditNew: 
     1053        edit_add_window (h, wh->y + 1, wh->x, wh->lines - 2, wh->cols, NULL, 0); 
     1054        break; 
     1055    case CK_EditFile: 
     1056        edit_load_cmd (h, NULL); 
     1057        break; 
     1058    case CK_OtherFile: 
     1059        { 
     1060            WEdit *e = (WEdit *) g->current->data; 
     1061            gboolean retflag = FALSE; 
     1062 
     1063            if (e != NULL && edit_widget_is_editor (CONST_WIDGET (e))) 
     1064            { 
     1065                retflag = edit_compute_other_file_vfs_path (e); 
     1066                if (retflag) 
     1067                    retflag = edit_switch_to_file (h, e->otherfile_vpath); 
     1068            } 
     1069            if (!retflag) 
     1070                ret = MSG_NOT_HANDLED; 
     1071        } 
     1072        break; 
     1073    case CK_History: 
     1074        edit_load_file_from_history (h); 
     1075        break; 
     1076    case CK_EditSyntaxFile: 
     1077        edit_load_syntax_file (h); 
     1078        break; 
     1079    case CK_EditUserMenu: 
     1080        edit_load_menu_file (h); 
     1081        break; 
     1082    case CK_Close: 
     1083        /* if there are no opened files anymore, close MC editor */ 
     1084        if ((edit_widget_is_editor (CONST_WIDGET (g->current->data)) || 
     1085            edit_widget_is_cli(CONST_WIDGET(g->current->data))) && 
     1086            edit_close_cmd ((WEdit *) g->current->data) && find_editor (h) == NULL) 
     1087            dlg_stop (h); 
     1088        break; 
     1089    case CK_Help: 
     1090        edit_help (); 
     1091        /* edit->force |= REDRAW_COMPLETELY; */ 
     1092        break; 
     1093    case CK_Menu: 
     1094        edit_menu_cmd (h); 
     1095        break; 
     1096    case CK_Quit: 
     1097    case CK_Cancel: 
     1098        /* don't close editor due to SIGINT, but stop move/resize window */ 
     1099        { 
     1100            Widget *w = WIDGET (g->current->data); 
     1101 
     1102            if (edit_widget_is_editor (w) && ((WEdit *) w)->drag_state != MCEDIT_DRAG_NONE) 
     1103                edit_restore_size ((WEdit *) w); 
     1104            else if (command == CK_Quit) 
     1105                dlg_stop (h); 
     1106        } 
     1107        break; 
     1108    case CK_About: 
     1109        edit_about (); 
     1110        break; 
     1111    case CK_SyntaxOnOff: 
     1112        edit_syntax_onoff_cmd (h); 
     1113        break; 
     1114    case CK_ShowTabTws: 
     1115        edit_show_tabs_tws_cmd (h); 
     1116        break; 
     1117    case CK_ShowMargin: 
     1118        edit_show_margin_cmd (h); 
     1119        break; 
     1120    case CK_ShowNumbers: 
     1121        edit_show_numbers_cmd (h); 
     1122        break; 
     1123    case CK_Refresh: 
     1124        edit_refresh_cmd (); 
     1125        break; 
     1126    case CK_InstructionStation: 
     1127        edit_add_instr_stn_window (h, INSTR_STN_NO_FLAGS, data); 
     1128        break; 
     1129    case CK_Shell: 
     1130        toggle_subshell (); 
     1131        break; 
     1132    case CK_LearnKeys: 
     1133        learn_keys (); 
     1134        break; 
     1135    case CK_WindowMove: 
     1136    case CK_WindowResize: 
     1137        if (edit_widget_is_editor (CONST_WIDGET (g->current->data))) 
     1138            edit_handle_move_resize ((WEdit *) g->current->data, command); 
     1139        break; 
     1140    case CK_WindowList: 
     1141        edit_window_list (h); 
     1142        break; 
     1143    case CK_WindowCascade: 
     1144        edit_window_cascade (h); 
     1145        break; 
     1146    case CK_WindowTile: 
     1147        edit_window_tile (h); 
     1148        break; 
     1149    case CK_WindowNext: 
     1150        group_select_next_widget (g); 
     1151        break; 
     1152    case CK_WindowPrev: 
     1153        group_select_prev_widget (g); 
     1154        break; 
     1155    case CK_Options: 
     1156        edit_options_dialog (h); 
     1157        break; 
     1158    case CK_OptionsSaveMode: 
     1159        edit_save_mode_cmd (); 
     1160        break; 
     1161    case CK_SaveSetup: 
     1162        save_setup_cmd (); 
     1163        break; 
     1164    default: 
     1165        ret = MSG_NOT_HANDLED; 
     1166        break; 
     1167    } 
     1168 
     1169    return ret; 
     1170} 
     1171 
     1172/* --------------------------------------------------------------------------------------------- */ 
     1173/** Callback for the edit dialog */ 
     1174 
     1175cb_ret_t 
     1176edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
     1177{ 
     1178    WGroup *g = GROUP (w); 
     1179    WDialog *h = DIALOG (w); 
     1180 
     1181    switch (msg) 
     1182    { 
     1183    case MSG_INIT: 
     1184        edit_dlg_init (); 
     1185        return MSG_HANDLED; 
     1186 
     1187    case MSG_RESIZE: 
     1188        dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL); 
     1189        menubar_arrange (find_menubar (h)); 
     1190        return MSG_HANDLED; 
     1191 
     1192    case MSG_ACTION: 
     1193        { 
     1194            /* Handle shortcuts, menu, and buttonbar. */ 
     1195 
     1196            cb_ret_t result; 
     1197 
     1198            result = edit_dialog_command_execute (h, parm, data); 
     1199 
     1200            /* We forward any commands coming from the menu, and which haven't been 
     1201               handled by the dialog, to the focused WEdit window. */ 
     1202            if (result == MSG_NOT_HANDLED && sender == WIDGET (find_menubar (h))) 
     1203                result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL); 
     1204 
     1205            return result; 
     1206        } 
     1207 
     1208    case MSG_KEY: 
     1209        { 
     1210            Widget *we = WIDGET (g->current->data); 
     1211            cb_ret_t ret = MSG_NOT_HANDLED; 
     1212 
     1213            if (edit_widget_is_editor (we)) 
     1214            { 
     1215                gboolean ext_mode; 
     1216                long command; 
     1217 
     1218                /* keep and then extmod flag */ 
     1219                ext_mode = we->ext_mode; 
     1220                command = widget_lookup_key (we, parm); 
     1221                we->ext_mode = ext_mode; 
     1222 
     1223                if (command == CK_IgnoreKey) 
     1224                    we->ext_mode = FALSE; 
     1225                else 
     1226                { 
     1227                    ret = edit_dialog_command_execute (h, command, data); 
     1228                    /* if command was not handled, keep the extended mode 
     1229                       for the further key processing */ 
     1230                    if (ret == MSG_HANDLED) 
     1231                        we->ext_mode = FALSE; 
     1232                } 
     1233            } 
     1234 
     1235            /* 
     1236             * Due to the "end of bracket" escape the editor sees input with is_idle() == false 
     1237             * (expects more characters) and hence doesn't yet refresh the screen, but then 
     1238             * no further characters arrive (there's only an "end of bracket" which is swallowed 
     1239             * by tty_get_event()), so you end up with a screen that's not refreshed after pasting. 
     1240             * So let's trigger an IDLE signal. 
     1241             */ 
     1242            if (!is_idle ()) 
     1243                widget_idle (w, TRUE); 
     1244            return ret; 
     1245        } 
     1246 
     1247        /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */ 
     1248    case MSG_UNHANDLED_KEY: 
     1249        return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED; 
     1250 
     1251    case MSG_VALIDATE: 
     1252        edit_quit (h); 
     1253        return MSG_HANDLED; 
     1254 
     1255    case MSG_END: 
     1256        edit_dlg_deinit (); 
     1257        return MSG_HANDLED; 
     1258 
     1259    case MSG_IDLE: 
     1260        widget_idle (w, FALSE); 
     1261        return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL); 
     1262 
     1263    default: 
     1264        return dlg_default_callback (w, sender, msg, parm, data); 
     1265    } 
     1266} 
     1267 
     1268/* --------------------------------------------------------------------------------------------- */ 
     1269 
     1270cb_ret_t 
    12121271edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
    12131272{ 
    12141273    WEdit *e = (WEdit *) w; 
    edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *da 
    12781337 
    12791338/* --------------------------------------------------------------------------------------------- */ 
    12801339 
    1281 /** 
    1282  * Handle move/resize mouse events. 
    1283  */ 
    1284 static void 
    1285 edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    1286 { 
    1287     WEdit *edit = (WEdit *) (w); 
    1288     Widget *h = WIDGET (w->owner); 
    1289     int global_x, global_y; 
    1290  
    1291     if (msg == MSG_MOUSE_UP) 
    1292     { 
    1293         /* Exit move/resize mode. */ 
    1294         edit_execute_cmd (edit, CK_Enter, -1); 
    1295         edit_update_screen (edit);      /* Paint the buttonbar over our possibly overlapping frame. */ 
    1296         return; 
    1297     } 
    1298  
    1299     if (msg != MSG_MOUSE_DRAG) 
    1300         /** 
    1301          * We ignore any other events. Specifically, MSG_MOUSE_DOWN. 
    1302          * 
    1303          * When the move/resize is initiated by the menu, we let the user 
    1304          * stop it by clicking with the mouse. Which is why we don't want 
    1305          * a mouse down to affect the window. 
    1306          */ 
    1307         return; 
    1308  
    1309     /* Convert point to global coordinates for easier calculations. */ 
    1310     global_x = event->x + w->x; 
    1311     global_y = event->y + w->y; 
    1312  
    1313     /* Clamp the point to the dialog's client area. */ 
    1314     global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */ 
    1315     global_x = CLAMP (global_x, h->x, h->x + h->cols - 1);      /* Currently a no-op, as the dialog has no left/right margins. */ 
    1316  
    1317     if (edit->drag_state == MCEDIT_DRAG_MOVE) 
    1318     { 
    1319         w->y = global_y; 
    1320         w->x = global_x - edit->drag_state_start; 
    1321     } 
    1322     else if (edit->drag_state == MCEDIT_DRAG_RESIZE) 
    1323     { 
    1324         w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1); 
    1325         w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1); 
    1326     } 
    1327  
    1328     edit->force |= REDRAW_COMPLETELY;   /* Not really needed as WEdit's MSG_DRAW already does this. */ 
    1329  
    1330     /* We draw the whole dialog because dragging/resizing exposes area beneath. */ 
    1331     widget_draw (WIDGET (w->owner)); 
    1332 } 
    1333  
    1334 /* --------------------------------------------------------------------------------------------- */ 
    1335  
    13361340/** 
    13371341 * Handle mouse events of editor window 
    13381342 * 
    edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * even 
    13401344 * @param msg mouse event message 
    13411345 * @param event mouse event data 
    13421346 */ 
    1343 static void 
     1347void 
    13441348edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    13451349{ 
    13461350    WEdit *edit = (WEdit *) w; 
    edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 
    14551459} 
    14561460 
    14571461/* --------------------------------------------------------------------------------------------- */ 
    1458 /*** public functions ****************************************************************************/ 
     1462/* Creates a CLI special window, windowed (not fullscreen) */ 
     1463 
     1464gboolean 
     1465edit_add_instr_stn_window (WDialog * h, instr_stn_flags_t flags, void *data) 
     1466{ 
     1467    WInstructionStation *ip; 
     1468 
     1469    /* Note passing of data - ability to alter program that's run in this station */ 
     1470    ip = instr_stn_create (3, 10, 17, 80, flags, (char*) data); 
     1471 
     1472    if (ip == NULL) 
     1473        return FALSE; 
     1474 
     1475    /* Add the editor window (WEdit) to the front dialog */ 
     1476    group_add_widget_autopos (GROUP (h), ip, WPOS_KEEP_ALL, NULL); 
     1477    widget_draw (WIDGET (h)); 
     1478    return TRUE; 
     1479} 
     1480 
    14591481/* --------------------------------------------------------------------------------------------- */ 
    14601482 
    14611483gboolean 
    edit_add_window (WDialog * h, int y, int x, int lines, int cols, const vfs_path_ 
    16861708    WEdit *edit; 
    16871709    Widget *w; 
    16881710 
    1689     edit = edit_init (NULL, y, x, lines, cols, f, fline); 
     1711    edit = edit_init (NULL, y, x, lines, cols, f, fline, 0); 
    16901712    if (edit == NULL) 
    16911713        return FALSE; 
    16921714 
  • src/editor/editwidget.h

    diff --git a/src/editor/editwidget.h b/src/editor/editwidget.h
    index b4b10692e..a80057393 100644
    a b  
    1313 
    1414/*** typedefs(not structures) and defined constants **********************************************/ 
    1515 
     16#define EDIT(x) ((WEdit *)(x)) 
     17#define CONST_EDIT(x) ((const WEdit *)(x)) 
     18 
    1619#define N_LINE_CACHES 32 
    1720 
    1821/*** enums ***************************************************************************************/ 
  • new file src/editor/instr_station.c

    diff --git a/src/editor/instr_station.c b/src/editor/instr_station.c
    new file mode 100644
    index 000000000..cceb25e01
    - +  
     1/* 
     2   Implementation of a CLI window for MCEdit. 
     3 
     4   Copyright (C) 2021 
     5   Free Software Foundation, Inc. 
     6 
     7   Written by: 
     8   Sebastian Gniazdowski <sgniazdowski@gmail.com>, 2021. 
     9 
     10   This file is part of the Midnight Commander. 
     11 
     12   The Midnight Commander is free software: you can redistribute it 
     13   and/or modify it under the terms of the GNU General Public License as 
     14   published by the Free Software Foundation, either version 3 of the License, 
     15   or (at your option) any later version. 
     16 
     17   The Midnight Commander is distributed in the hope that it will be useful, 
     18   but WITHOUT ANY WARRANTY; without even the implied warranty of 
     19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
     20   GNU General Public License for more details. 
     21 
     22   You should have received a copy of the GNU General Public License 
     23   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
     24 */ 
     25 
     26/** \file instr_station.c 
     27 *  \brief Implementation of a CLI special window for MCEdit. 
     28 *  \author Sebastian Gniazdowski 
     29 *  \date 2021 
     30 * 
     31 *  Such window runs a set up program (/bin/bash by default) allowing to 
     32 *  provide an input to it and read its output. 
     33 */ 
     34 
     35#include <config.h> 
     36 
     37#include "lib/global.h" 
     38 
     39#include "src/editor/editwidget.h" 
     40 
     41#include "lib/sub-util.h" 
     42#include "lib/tty/key.h" 
     43#include "lib/widget.h" 
     44#include "src/editor/instr_station.h" 
     45 
     46/*** global variables ****************************************************************************/ 
     47 
     48/*** file scope macro definitions ****************************************************************/ 
     49 
     50/*** file scope type declarations ****************************************************************/ 
     51 
     52typedef enum instr_stn_io_type 
     53{ 
     54    STDIN = 0, 
     55    STDOUT, 
     56    STDERR 
     57} instr_stn_io_type_t; 
     58 
     59/*** file scope variables ************************************************************************/ 
     60 
     61static gboolean alt_prog_first_run = TRUE; 
     62 
     63/*** file scope functions ************************************************************************/ 
     64/* --------------------------------------------------------------------------------------------- */ 
     65 
     66static char * 
     67instr_stn_get_instruction_text (WInstructionStation * ip) 
     68{ 
     69    char *ret_str; 
     70    GString *instr; 
     71    off_t bol, eol, begin_instr; 
     72    long size; 
     73    int idx, byte; 
     74 
     75    /* Calculate offset of text after prompt */ 
     76    bol = edit_buffer_get_current_bol (&EDIT (ip)->buffer); 
     77    eol = edit_buffer_get_current_eol (&EDIT (ip)->buffer); 
     78    begin_instr = bol + ip->prompt_span; 
     79 
     80    /* Is there anything entered? */ 
     81    size = ((long) eol) - ((long) begin_instr); 
     82    if (size <= 0) 
     83        return NULL; 
     84 
     85    /* Allocate expected size string and fill it */ 
     86    instr = g_string_sized_new (size + 2); 
     87    for (idx = 0; idx < size; idx++) 
     88    { 
     89        byte = edit_buffer_get_byte (&EDIT (ip)->buffer, begin_instr + idx); 
     90        g_string_append_c (instr, byte); 
     91    } 
     92 
     93    /* Append new line if needed */ 
     94    if (instr->str[instr->len - 1] != '\n') 
     95        g_string_append_c (instr, '\n'); 
     96 
     97    /* Return char buffer */ 
     98    ret_str = instr->str; 
     99    g_string_free (instr, FALSE); 
     100    return ret_str; 
     101} 
     102 
     103/* --------------------------------------------------------------------------------------------- */ 
     104 
     105static gboolean 
     106instr_stn_release (WInstructionStation * ip, gboolean free_all) 
     107{ 
     108    /* Already relased? */ 
     109    if (ip == NULL) 
     110    { 
     111        if (free_all) 
     112            return TRUE; 
     113        else 
     114            return FALSE; 
     115    } 
     116 
     117    /* Already cleaned up (for a restart of program) ? */ 
     118    if (ip->finalized && !free_all) 
     119        return FALSE; 
     120    ip->finalized = TRUE; 
     121 
     122    /* Close process watcher first, suppressing *program_ended_cb() callback */ 
     123    if (ip->proc_src_id != 0) 
     124    { 
     125        GSource *src; 
     126        src = g_main_context_find_source_by_id (NULL, ip->proc_src_id); 
     127        if (src != NULL) 
     128            g_source_destroy (src); 
     129        ip->proc_src_id = 0; 
     130    } 
     131 
     132    /* Release the pipes, channels, etc. */ 
     133    for (int i = 0; i <= STDERR; i++) 
     134    { 
     135        /* Source */ 
     136        if (ip->io[i].src_id != 0) 
     137        { 
     138            GSource *src; 
     139            src = g_main_context_find_source_by_id (NULL, ip->io[i].src_id); 
     140            if (src != NULL) 
     141                g_source_destroy (src); 
     142            ip->io[i].src_id = 0; 
     143        } 
     144        /* Channel */ 
     145        if (ip->io[i].ch != NULL) 
     146        { 
     147            g_io_channel_unref (ip->io[i].ch); 
     148            ip->io[i].ch = NULL; 
     149        } 
     150        /* Pipe */ 
     151        if (ip->io[i].fd >= 0) 
     152        { 
     153            close (ip->io[i].fd); 
     154            ip->io[i].fd = -1; 
     155        } 
     156    } 
     157 
     158    /* Remove prompt guard recurring timeout callback function */ 
     159    if (ip->prompt_timeout_id != 0) 
     160    { 
     161        g_source_remove(ip->prompt_timeout_id); 
     162        ip->prompt_timeout_id = 0; 
     163    } 
     164 
     165    MC_PTR_FREE (ip->instruction); 
     166    /* Release prompt */ 
     167    MC_PTR_FREE (ip->prompt); 
     168    /* Release error prompt */ 
     169    MC_PTR_FREE (ip->eprompt); 
     170 
     171    /* Clear prompt information */ 
     172    ip->prompt_span = 0; 
     173    ip->eprompt_span = 0; 
     174 
     175    /* Set various indicator flags to some inactive state */ 
     176    ip->io_complete = TRUE; 
     177    ip->first_response = TRUE; 
     178    ip->before_first_prompt = TRUE; 
     179    ip->prompt_shown = FALSE; 
     180    ip->flags = INSTR_STN_NO_FLAGS; 
     181 
     182    /* Clear any GError */ 
     183    if (ip->error != NULL) 
     184    { 
     185        g_error_free (ip->error); 
     186        ip->error = NULL; 
     187    } 
     188 
     189    /* Release space occupied by WInstructionStation object? */ 
     190    if (free_all) 
     191    { 
     192        MC_PTR_FREE (ip->program); 
     193        /* Free main object */ 
     194        g_free (ip); 
     195    } 
     196    else 
     197        ip->program = NULL; 
     198 
     199    return TRUE; 
     200} 
     201 
     202/* --------------------------------------------------------------------------------------------- */ 
     203 
     204static cb_ret_t 
     205instr_stn_run_novel_state_id (WInstructionStation * ip, long novel_state_id, void *data) 
     206{ 
     207    cb_ret_t ret = MSG_NOT_HANDLED; 
     208    switch (novel_state_id) 
     209    { 
     210    case CK_BackSpace: 
     211        if (ip->cur_col <= ip->prompt_span) 
     212            ret = MSG_HANDLED; 
     213        else 
     214        { 
     215            EDIT (ip)->force |= REDRAW_PAGE; 
     216            edit_update_screen (EDIT (ip)); 
     217        } 
     218        break; 
     219    case CK_Enter: 
     220    case CK_Return: 
     221        if (EDIT (ip)->buffer.curs_line == ip->cur_line) 
     222        { 
     223            ip->instruction = instr_stn_get_instruction_text (ip); 
     224            ip->io_complete = FALSE; 
     225            /* Should dispatch to write callback which will send instruction */ 
     226            g_main_context_iteration (NULL, 0); 
     227            repaint_screen(); 
     228        } 
     229        ret = MSG_HANDLED; 
     230        break; 
     231    /* InstructionStation activity invoked when focus is at a station window behaves differently */ 
     232    case CK_InstructionStation: 
     233        { 
     234            /* Save needed information */ 
     235            WGroup *owner_save = WIDGET(ip)->owner; 
     236            char *program_save; 
     237 
     238             /* End previous program */ 
     239            if(kill (ip->process_id, 15) == -1) 
     240                mc_log("Problem sending TERM signal to %d: %s", ip->process_id, g_strerror(errno)); 
     241            else 
     242                mc_log("Ending process %d %s.", ip->process_id, ip->prev_program); 
     243 
     244            /* Allow process watcher callbacks to be called */ 
     245            g_usleep(150000); 
     246            g_main_context_iteration(NULL, FALSE); 
     247            /* Allow the process end message to flash noticeably long */ 
     248            g_usleep(200000); 
     249 
     250            /* Clean WInstructionStation object */ 
     251            instr_stn_release(ip, FALSE); 
     252            /* ...and reinitialize it */ 
     253            program_save = ip->prev_program; 
     254            if(instr_stn_init(ip, -1, -1, -1, -1, ip->prev_flags, program_save) != NULL) 
     255                ret = MSG_HANDLED; 
     256            g_free(program_save); 
     257 
     258            WIDGET(ip)->owner = owner_save; 
     259            widget_draw (WIDGET (owner_save)); 
     260        } 
     261        break; 
     262    default: 
     263        break; 
     264    } 
     265    return ret; 
     266} 
     267 
     268/* --------------------------------------------------------------------------------------------- */ 
     269 
     270static void 
     271instr_stn_show_prompt (WInstructionStation * ip, gboolean is_ok) 
     272{ 
     273    char *sel_prompt = is_ok ? ip->prompt : ip->eprompt; 
     274    int sel_prompt_span = is_ok ? ip->prompt_span : ip->eprompt_span; 
     275 
     276    if (has_flag(ip->flags, INSTR_STN_NO_PROMPT)) 
     277        return; 
     278 
     279    /* Print prompt */ 
     280    if (!edit_newline_end_check_only (EDIT (ip)) && !ip->before_first_prompt) 
     281    { 
     282        edit_insert (EDIT (ip), '\n'); 
     283        ip->cur_line += 1; 
     284    } else 
     285        ip->before_first_prompt = FALSE; 
     286    edit_insert_string (EDIT (ip), sel_prompt, sel_prompt_span); 
     287 
     288    /* Raise an indicator flag */ 
     289    ip->prompt_shown = TRUE; 
     290 
     291    /* Set cursor position to reflect state */ 
     292    ip->cur_col = sel_prompt_span; 
     293} 
     294 
     295/* --------------------------------------------------------------------------------------------- */ 
     296/* Remove prompt from buffer (basically clear current line). */ 
     297 
     298static void 
     299instr_stn_clear_prompt (WInstructionStation * ip) 
     300{ 
     301    off_t bol, eol, idx; 
     302 
     303    if (has_flag(ip->flags, INSTR_STN_NO_PROMPT)) 
     304        return; 
     305 
     306    eol = edit_buffer_get_current_eol(&EDIT(ip)->buffer); 
     307    bol = edit_buffer_get_current_bol(&EDIT(ip)->buffer); 
     308    for (idx=0; idx < eol - bol; idx ++) 
     309        edit_backspace(EDIT(ip), TRUE); 
     310    ip->prompt_shown = FALSE; 
     311} 
     312 
     313/* --------------------------------------------------------------------------------------------- */ 
     314 
     315static gboolean 
     316prompt_drawing_timeout_callback (gpointer data) 
     317{ 
     318    WInstructionStation *ip = INSTR_STATION (data); 
     319    guint64 cur_time; 
     320    cur_time = g_get_real_time (); 
     321    /* Draw prompt after 0.5 seconds of no output from program */ 
     322    if (cur_time - ip->read_time > 500000 && !ip->prompt_shown) 
     323    { 
     324        instr_stn_show_prompt (ip, TRUE); 
     325        repaint_screen(); 
     326    } 
     327    return TRUE; 
     328} 
     329 
     330/* --------------------------------------------------------------------------------------------- */ 
     331 
     332static void 
     333program_ended_callback (GPid pid, gint exit_code, gpointer user_data) 
     334{ 
     335    WInstructionStation *ip = INSTR_STATION (user_data); 
     336    char *msg; 
     337    gboolean st_ret; 
     338 
     339    /* Release object leaving it uninitialized (no freeing of main object pointer) */ 
     340    instr_stn_release (ip, FALSE); 
     341 
     342    g_spawn_close_pid (pid); 
     343 
     344    /* Examine exit code of closed program */ 
     345#ifdef GLIB_VERSION_2_34 
     346    st_ret = g_spawn_check_exit_status (exit_code, NULL); 
     347#else 
     348    st_ret = (exit_code == 0); 
     349#endif 
     350 
     351    /* Choose message basing on exit code */ 
     352    if (st_ret) 
     353        msg = g_strdup(_("\nProgram closed.\n")); 
     354    else 
     355        msg = g_strdup_printf(_("\nProgram closed abnormally (exit code: %d).\n"), exit_code); 
     356 
     357    /* Insert/print message to buffer and to mc.log */ 
     358    edit_insert_string (EDIT (ip), msg, strlen (msg)); 
     359    mc_log("%s", msg); 
     360 
     361    /* Release message upon using/printing it */ 
     362    g_free(msg); 
     363    repaint_screen(); 
     364} 
     365 
     366/* --------------------------------------------------------------------------------------------- */ 
     367 
     368static gboolean 
     369helper_configure_src_and_ch (GIOChannel ** ch, gint fd, guint * src_id, gboolean write, 
     370                             GIOFunc cb_fun, gpointer data, const char *name) 
     371{ 
     372    /* Initialize the output variables */ 
     373    *ch = NULL; 
     374    *src_id = 0; 
     375 
     376    *ch = g_io_channel_unix_new (fd); 
     377 
     378    /* Channel created? */ 
     379    if (*ch == NULL) 
     380        goto cleanup_and_err; 
     381 
     382    /* Automatic shutdown of channel */ 
     383    g_io_channel_set_close_on_unref (*ch, TRUE); 
     384 
     385    /* Trim down buffering on this channel */ 
     386    g_io_channel_set_buffer_size (*ch, 5); 
     387 
     388    /* Attempt to set non-blocking flag on channel */ 
     389    if (g_io_channel_set_flags (*ch, G_IO_FLAG_NONBLOCK, NULL) != G_IO_STATUS_NORMAL) 
     390        mc_log ("Problem setting a channel to non-blocking state (channel flags: 0x%x)", 
     391                g_io_channel_get_flags (*ch)); 
     392 
     393    /* Create GSource */ 
     394    *src_id = g_io_add_watch (*ch, (write ? G_IO_OUT : G_IO_IN) | G_IO_HUP | G_IO_ERR, cb_fun, data); 
     395 
     396    /* Source created OK? */ 
     397    if (*src_id == 0) 
     398        goto cleanup_and_err; 
     399 
     400    /* Configure the sources */ 
     401    g_source_set_name_by_id (*src_id, name); 
     402 
     403    /* Return true */ 
     404    return TRUE; 
     405 
     406  cleanup_and_err: 
     407    if (*src_id != 0) 
     408    { 
     409        GSource *src; 
     410        src = g_main_context_find_source_by_id (NULL, *src_id); 
     411        if (src != NULL) 
     412            /* Triggers also unref of wrapped channel */ 
     413            g_source_destroy (src); 
     414    } 
     415    if (*ch != NULL) 
     416        g_io_channel_unref (*ch); 
     417    *ch = NULL; 
     418    *src_id = 0; 
     419 
     420    return FALSE; 
     421} 
     422 
     423/* --------------------------------------------------------------------------------------------- */ 
     424 
     425static gboolean 
     426helper_read_all (GIOChannel * channel, char *buf, gsize * out_bytes_read_in_len) 
     427{ 
     428    gsize len = *out_bytes_read_in_len; 
     429    gsize count, retries = 10; 
     430    GIOStatus stat; 
     431 
     432    *out_bytes_read_in_len = 0; 
     433 
     434    while (*out_bytes_read_in_len < len) 
     435    { 
     436        count = 0; 
     437        stat = 
     438            g_io_channel_read_chars (channel, buf + *out_bytes_read_in_len, 
     439                                     len - *out_bytes_read_in_len, &count, NULL); 
     440        *out_bytes_read_in_len += count; 
     441 
     442        /* Should end the reading ? */ 
     443        if (stat == G_IO_STATUS_ERROR || (stat == G_IO_STATUS_NORMAL && count == 0) || 
     444            stat == G_IO_STATUS_EOF) 
     445            return (stat != G_IO_STATUS_ERROR); 
     446        else if (stat == G_IO_STATUS_AGAIN && retries-- == 0) 
     447            /* Exhausted retries - there must be no data to read - return OK */ 
     448            return TRUE; 
     449    } 
     450 
     451    return TRUE; 
     452} 
     453 
     454/* --------------------------------------------------------------------------------------------- */ 
     455 
     456static gboolean 
     457helper_write_all (GIOChannel * channel, char *buf, gsize * out_bytes_written_in_len) 
     458{ 
     459    gsize len = *out_bytes_written_in_len; 
     460    gsize count; 
     461    GIOError err; 
     462 
     463    *out_bytes_written_in_len = 0; 
     464 
     465    while (*out_bytes_written_in_len < len) 
     466    { 
     467        count = 0; 
     468        err = 
     469            g_io_channel_write (channel, buf + *out_bytes_written_in_len, 
     470                                len - *out_bytes_written_in_len, &count); 
     471        *out_bytes_written_in_len += count; 
     472 
     473        if (err && err != G_IO_ERROR_AGAIN) 
     474            return FALSE; 
     475    } 
     476 
     477    return TRUE; 
     478} 
     479 
     480/* --------------------------------------------------------------------------------------------- */ 
     481 
     482static gboolean 
     483stdout_read_callback (GIOChannel * source, GIOCondition condition, gpointer data) 
     484{ 
     485    WInstructionStation *ip = INSTR_STATION (data); 
     486    char buf[2048]; 
     487    gsize count = 2047; 
     488    gboolean r_ret; 
     489 
     490    /* Active only when a recognized event of interest occurs */ 
     491    if ((condition & (G_IO_IN|G_IO_HUP|G_IO_ERR)) == 0) 
     492        return TRUE; 
     493    else 
     494        ip->io_complete = FALSE; 
     495 
     496    /* Repeat read of 2KiB-1 maximum bytes until 0 bytes are being read */ 
     497    do { 
     498        r_ret = helper_read_all (source, buf, &count); 
     499        buf[count] = '\0'; 
     500        if (count == 0 && !r_ret) 
     501        { 
     502            mc_log ("Error while reading program's output: %s", g_strerror (errno)); 
     503        } 
     504        else 
     505        { 
     506            ip->read_time = g_get_real_time (); 
     507            buf[count] = '\0'; 
     508            if (count > 0) 
     509            { 
     510                gsize idx = 0, nl_count = 0; 
     511 
     512                /* Count number of read lines */ 
     513                char *p = buf; 
     514                while (idx++ < count) 
     515                    if (*p++ == '\n') 
     516                        nl_count++; 
     517 
     518                /* Move to next line after prompt */ 
     519                if (ip->first_response) 
     520                { 
     521                    edit_insert (EDIT (ip), '\n'); 
     522                    nl_count++; 
     523                    ip->first_response = FALSE; 
     524                    ip->prompt_shown = FALSE; 
     525                } 
     526                else if(ip->prompt_shown) 
     527                    instr_stn_clear_prompt(ip); 
     528 
     529                /* Print whole buffer into file's window  */ 
     530                edit_insert_string (EDIT (ip), buf, count); 
     531 
     532                /* Increase the row pointer */ 
     533                ip->cur_line += nl_count; 
     534            } 
     535            else if (!ip->prompt_shown) 
     536                instr_stn_show_prompt (ip, r_ret); 
     537 
     538            repaint_screen (); 
     539        } 
     540    } while (count != 0); 
     541 
     542    ip->io_complete = r_ret; 
     543    return condition != G_IO_HUP; 
     544} 
     545 
     546/* --------------------------------------------------------------------------------------------- */ 
     547 
     548static gboolean 
     549stdin_write_callback (GIOChannel * source, GIOCondition condition, gpointer data) 
     550{ 
     551    WInstructionStation *ip = INSTR_STATION (data); 
     552    gsize count; 
     553    gboolean ret = TRUE; 
     554 
     555    /* Any instruction to send? */ 
     556    if (ip->instruction == NULL) 
     557        return ret; 
     558    else 
     559        ip->io_complete = FALSE; 
     560 
     561    if ((condition & G_IO_OUT) == 0) 
     562        return TRUE; 
     563 
     564    errno = 0; 
     565    count = strlen (ip->instruction); 
     566    ret = helper_write_all (ip->io[STDIN].ch, ip->instruction, &count); 
     567    if (!ret) 
     568    { 
     569        mc_log ("Error during sending instruction to program: %s", g_strerror (errno)); 
     570    } 
     571    else 
     572    { 
     573        ip->first_response = TRUE; 
     574        MC_PTR_FREE (ip->instruction); 
     575        if (ip->prompt_timeout_id == 0) 
     576            ip->prompt_timeout_id = g_timeout_add (200, prompt_drawing_timeout_callback, ip); 
     577    } 
     578 
     579    g_io_channel_flush (ip->io[STDIN].ch, NULL); 
     580    ip->io_complete = TRUE; 
     581 
     582    return ret; 
     583} 
     584 
     585/* --------------------------------------------------------------------------------------------- */ 
     586 
     587/* --------------------------------------------------------------------------------------------- */ 
     588/*** public functions ****************************************************************************/ 
     589/* --------------------------------------------------------------------------------------------- */ 
     590 
     591WInstructionStation * 
     592instr_stn_create (int y, int x, int lines, int cols, instr_stn_flags_t flags, const char *program) 
     593{ 
     594    WInstructionStation *ip, *iret; 
     595 
     596    ip = g_new0 (WInstructionStation, 1); 
     597    if (ip == NULL) 
     598        return NULL; 
     599 
     600    iret = instr_stn_init(ip, y, x, lines, cols, flags, program); 
     601    if (iret == NULL) 
     602        MC_PTR_FREE(ip); 
     603 
     604    return ip; 
     605} 
     606 
     607/* --------------------------------------------------------------------------------------------- */ 
     608/* Initializes a preallocated object */ 
     609 
     610WInstructionStation * 
     611instr_stn_init (WInstructionStation *ip, int y, int x, int lines, int cols, instr_stn_flags_t flags, const char *program) 
     612{ 
     613    WEdit *iret; 
     614    gboolean tmp_ret = FALSE; 
     615    char **program_cline; 
     616    int rounds = 0; 
     617 
     618    if (ip == NULL) 
     619        goto cleanup_and_err; 
     620 
     621    /* Handle special -1 values indicating reuse of old values */ 
     622    if (ip->finalized) 
     623    { 
     624        y = (y == -1) ? WIDGET(ip)->y : y; 
     625        x = (x == -1) ? WIDGET(ip)->x : x; 
     626        lines = (lines == -1) ? WIDGET(ip)->lines : lines; 
     627        cols = (cols == -1) ? WIDGET(ip)->cols : cols; 
     628        ip->finalized = FALSE; 
     629    } 
     630    iret = edit_init (EDIT (ip), y, x, lines, cols, NULL, 0, EDIT_DO_INIT_BASE_CLASS); 
     631    if (iret == NULL) 
     632        goto cleanup_and_err; 
     633 
     634    WIDGET (ip)->callback = instr_stn_callback; 
     635    WIDGET (ip)->mouse_callback = edit_mouse_callback; 
     636    EDIT (ip)->fullscreen = 0; 
     637 
     638    ip->io[0].name = g_strdup ("Std-In (WInstructionStation)"); 
     639    ip->io[1].name = g_strdup ("Std-Out (WInstructionStation)"); 
     640    ip->io[2].name = g_strdup ("Std-Error (WInstructionStation)"); 
     641    ip->io[0].fd = ip->io[1].fd = ip->io[2].fd = -1; 
     642 
     643    ip->before_first_prompt = TRUE; 
     644    ip->prompt = g_strdup ("[guest@localhost]# "); 
     645    ip->prompt_span = strlen (ip->prompt); 
     646    ip->eprompt = g_strdup ("[guest@localhost]! "); 
     647    ip->eprompt_span = strlen (ip->eprompt); 
     648    ip->cur_col = 0; 
     649    ip->cur_line = 0; 
     650    ip->flags = flags; 
     651    ip->prev_flags = flags; 
     652 
     653    /* Set initial program (most probably a shell, like Bash) */ 
     654    if (program != NULL) 
     655        ip->program = g_strdup (program); 
     656    else 
     657        /* Fallback instruction if no program given */ 
     658        ip->program = g_strdup ("bash"); 
     659 
     660    ip->prev_program = ip->program; 
     661 
     662    while (++rounds <= 2 && !tmp_ret) 
     663    { 
     664        int argc = 0; 
     665        if (ip->error != NULL) 
     666        { 
     667            g_error_free (ip->error); 
     668            ip->error = NULL; 
     669        } 
     670        tmp_ret = g_shell_parse_argv(ip->program, &argc, &program_cline, &ip->error); 
     671        if (tmp_ret) 
     672            tmp_ret = g_spawn_async_with_pipes (NULL, program_cline, 
     673                                             NULL, G_SPAWN_SEARCH_PATH | 
     674                                             G_SPAWN_DO_NOT_REAP_CHILD | 
     675                                             G_SPAWN_LEAVE_DESCRIPTORS_OPEN, 
     676                                             NULL, NULL, &ip->process_id, 
     677                                             has_flag(flags, INSTR_STN_NO_STDIN) ? NULL : &ip->io[STDIN].fd, 
     678                                             &ip->io[STDOUT].fd, &ip->io[STDERR].fd, &ip->error); 
     679 
     680        if (!tmp_ret) 
     681        { 
     682            char *new_program, *msg_text; 
     683            mc_log ("Creating the background process [program:%s] failed: %s", 
     684                    program, ip->error ? ip->error->message : "<no error message>"); 
     685 
     686            /* Create a transatable, parametrized message */ 
     687            msg_text = g_strdup_printf ("%s%s:", _("The specified program has failed to run.\n" 
     688                                                   "Enter a new full path or a program name"), 
     689                                        (rounds >= 2 ? _(" (last try)") : "")); 
     690 
     691            /* Display message asking for a new program */ 
     692            new_program = input_expand_dialog (_("Provide an alternate program to run"), 
     693                                               msg_text, "instruction-station", 
     694                                               alt_prog_first_run ? ip->program : INPUT_LAST_TEXT, 
     695                                               INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_FILENAMES | 
     696                                               INPUT_COMPLETE_VARIABLES); 
     697            g_free(msg_text); 
     698            alt_prog_first_run = FALSE; 
     699 
     700            /* Obtained a new program? */ 
     701            if (new_program != NULL) 
     702            { 
     703                g_free (ip->program); 
     704                ip->program = new_program; 
     705            } 
     706        } 
     707    } 
     708 
     709    /* If no program to run has been found, then exit. */ 
     710    if (!tmp_ret) 
     711    { 
     712        message (D_ERROR, _("InstructionStation startup failed"), 
     713                 _("Failed to initialize the CLI window\n(reason: %s)."), 
     714                 ip->error ? ip->error->message : "<no error message>"); 
     715        goto cleanup_and_err; 
     716    } 
     717 
     718    for (int i = 0; i <= STDERR; i++) 
     719    { 
     720        if (has_flag(flags, INSTR_STN_NO_STDIN) && i == STDIN) 
     721            continue; 
     722 
     723        if (helper_configure_src_and_ch (&ip->io[i].ch, ip->io[i].fd, &ip->io[i].src_id, 
     724                                         (i == STDIN ? TRUE : FALSE), 
     725                                         (i == STDIN ? stdin_write_callback : stdout_read_callback), 
     726                                         ip, ip->io[i].name) == FALSE) 
     727            goto cleanup_and_err; 
     728    } 
     729    /* Set a process watcher and restarter for the program */ 
     730    ip->proc_src_id = g_child_watch_add (ip->process_id, program_ended_callback, ip); 
     731 
     732    /* Display initial prompt */ 
     733    instr_stn_show_prompt (ip, TRUE); 
     734    ip->io_complete = TRUE; 
     735 
     736    return ip; 
     737 
     738  cleanup_and_err: 
     739 
     740    instr_stn_release (ip, TRUE); 
     741    return NULL; 
     742} 
     743 
     744/* --------------------------------------------------------------------------------------------- */ 
     745/* Check if widget is a WInstructionStation class type. */ 
     746 
     747gboolean 
     748edit_widget_is_cli (const Widget * w) 
     749{ 
     750    return (w != NULL && w->callback == instr_stn_callback); 
     751} 
     752 
     753/* --------------------------------------------------------------------------------------------- */ 
     754 
     755cb_ret_t 
     756instr_stn_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 
     757{ 
     758    cb_ret_t ret = MSG_NOT_HANDLED; 
     759    WInstructionStation *ip = INSTR_STATION (w); 
     760 
     761    switch (msg) 
     762    { 
     763 
     764    case MSG_KEY: 
     765        { 
     766            gboolean ext_mode; 
     767            long novel_state_id; 
     768 
     769            /* keep and then extmod flag */ 
     770            ext_mode = w->ext_mode; 
     771            novel_state_id = widget_lookup_key (w, parm); 
     772            w->ext_mode = ext_mode; 
     773 
     774            if (novel_state_id == CK_IgnoreKey) 
     775                w->ext_mode = FALSE; 
     776            else 
     777            { 
     778                /* Treat Station action specially when run from within a station window */ 
     779                if (novel_state_id != CK_InstructionStation) 
     780                    ret = edit_dialog_command_execute (DIALOG(w->owner), novel_state_id, data); 
     781                if (ret == MSG_NOT_HANDLED) 
     782                    ret = instr_stn_run_novel_state_id (ip, novel_state_id, data); 
     783                /* if activity was not handled, keep the extended mode 
     784                   for the further key processing */ 
     785                if (ret == MSG_HANDLED) 
     786                    w->ext_mode = FALSE; 
     787            } 
     788 
     789            /* 
     790             * Due to the "end of bracket" escape the editor sees input with is_idle() == false 
     791             * (expects more characters) and hence doesn't yet refresh the screen, but then 
     792             * no further characters arrive (there's only an "end of bracket" which is swallowed 
     793             * by tty_get_event()), so you end up with a screen that's not refreshed after pasting. 
     794             * So let's trigger an IDLE signal. 
     795             */ 
     796            if (!is_idle ()) 
     797                widget_idle (w, TRUE); 
     798        } 
     799        break; 
     800    case MSG_ACTION: 
     801        ret = instr_stn_run_novel_state_id (ip, parm, data); 
     802        break; 
     803    case MSG_DESTROY: 
     804        /* Message will be passed on to WEdit callback which will g_free() ↔ passing FALSE */ 
     805        instr_stn_release(ip, FALSE); 
     806        g_free(ip->prev_program); 
     807        break; 
     808    default: 
     809        break; 
     810    } 
     811 
     812    /* Redirect message to base object (i.e.: editor window «» WEdit) if needed */ 
     813    if (ret == MSG_NOT_HANDLED) 
     814    { 
     815        ret = edit_callback (w, sender, msg, parm, data); 
     816        ip->cur_col = EDIT (w)->curs_col; 
     817    } 
     818    return ret; 
     819} 
     820 
     821/* --------------------------------------------------------------------------------------------- */ 
  • new file src/editor/instr_station.h

    diff --git a/src/editor/instr_station.h b/src/editor/instr_station.h
    new file mode 100644
    index 000000000..1a996ed0e
    - +  
     1#ifndef MC__INSTR_STATION_H 
     2#define MC__INSTR_STATION_H 
     3 
     4/*** typedefs(not structures) and defined constants **********************************************/ 
     5 
     6#define INSTR_STATION(x) ((WInstructionStation *)x) 
     7#define CONST_INSTR_STATION(x) ((const WInstructionStation *)x) 
     8 
     9/*** enums ***************************************************************************************/ 
     10 
     11/*** structures declarations (and typedefs of structures)*****************************************/ 
     12 
     13typedef struct 
     14{ 
     15    WEdit base; 
     16 
     17    /* Configuration flags */ 
     18    instr_stn_flags_t flags; 
     19    /* Save of flags for use upon finalization of this object */ 
     20    instr_stn_flags_t prev_flags; 
     21 
     22    int cur_line;    /* Current line where prompt is */ 
     23    int cur_col;     /* Current column where prompt ends */ 
     24    int prompt_span; /* Length of prompt in bytes */ 
     25    int eprompt_span;/* Length of error prompt */ 
     26 
     27    /* Complete command that's being run in this station */ 
     28    char *program; 
     29    /* Saved program string from before calling instr_stn_release(...,FALSE) (i.e.: finalization) */ 
     30    char *prev_program; 
     31    /* OK prompt */ 
     32    char *prompt; 
     33    /* Failure prompt */ 
     34    char *eprompt; 
     35    /* The current instruction to send (filled for the writer GSource's pseudo thread)*/ 
     36    char *instruction; 
     37 
     38    /* An indicator of whether the prompt to show is the first one */ 
     39    gboolean before_first_prompt; 
     40    /* An indicator of waiting for an initial response from program */ 
     41    gboolean first_response; 
     42    /* An indicator of the prompt being printed or not */ 
     43    gboolean prompt_shown; 
     44    /* ID of prompt drawing GLib's timeout recurring function */ 
     45    guint prompt_timeout_id; 
     46 
     47    /* Process ID of the program */ 
     48    GPid process_id; 
     49 
     50    /* Pipes to and from the program – series of fd+channel+GSource collections */ 
     51    struct 
     52    { 
     53        gint fd; 
     54        GIOChannel *ch; 
     55        guint src_id; 
     56        char *name; 
     57    } io[3]; 
     58 
     59    /* Time of last non empty read of data from program */ 
     60    guint64 read_time; 
     61 
     62    /* GSource id of program process watcher */ 
     63    guint proc_src_id; 
     64 
     65    /* A GError placeholder */ 
     66    GError *error; 
     67 
     68    /* Indicator of an idle state – set when there's no data to send and read */ 
     69    gboolean io_complete; 
     70 
     71    /* Indicator of instr_release being already called on WInstructionStation object */ 
     72    gboolean finalized; 
     73} WInstructionStation; 
     74 
     75/*** global variables defined in .c file *********************************************************/ 
     76 
     77/*** declarations of public functions ************************************************************/ 
     78 
     79WInstructionStation *instr_stn_create (int y, int x, int lines, int cols, instr_stn_flags_t flags, const char *program); 
     80WInstructionStation *instr_stn_init (WInstructionStation *ip, int y, int x, int lines, int cols, instr_stn_flags_t flags, const char *program); 
     81gboolean edit_widget_is_cli (const Widget * w); 
     82cb_ret_t instr_stn_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 
     83 
     84/*** inline functions ****************************************************************************/ 
     85 
     86#endif /* MC__INSTR_STATION_H */ 
  • src/keybind-defaults.c

    diff --git a/src/keybind-defaults.c b/src/keybind-defaults.c
    index db483382a..c0cede558 100644
    a b static const global_keymap_ini_t default_editor_keymap[] = { 
    478478    {"FileNext", "alt-plus"}, 
    479479    {"Sort", "alt-t"}, 
    480480    {"Mail", "alt-m"}, 
     481    {"InstructionStation", "alt-i"}, 
    481482    {"ExternalCommand", "alt-u"}, 
    482483    {"WindowMove", "ctrl-alt-e"}, 
    483484    {"WindowResize", "ctrl-alt-s"},