Ticket #4114: persistent_subshell_command_buffer.patch

File persistent_subshell_command_buffer.patch, 20.2 KB (added by congest, 4 years ago)
  • src/execute.c

    diff --git a/src/execute.c b/src/execute.c
    index d9b306e67..f35c53d94 100644
    a b toggle_subshell (void) 
    551551    { 
    552552        if (mc_global.mc_run_mode == MC_RUN_FULL) 
    553553        { 
    554             do_load_prompt (); 
    555554            if (new_dir_vpath != NULL) 
    556555                do_possible_cd (new_dir_vpath); 
    557556        } 
  • src/subshell/common.c

    diff --git a/src/subshell/common.c b/src/subshell/common.c
    index 06699233c..ed6f94493 100644
    a b  
    105105#include "lib/util.h" 
    106106#include "lib/widget.h" 
    107107 
     108#include "src/filemanager/layout.h"        /* setup_cmdline() */ 
     109#include "src/filemanager/command.h"        /* cmdline */ 
     110 
    108111#include "subshell.h" 
    109112#include "internal.h" 
    110113 
    static char pty_buffer[PTY_BUFFER_SIZE] = "\0"; 
    169172/* To pass CWD info from the subshell to MC */ 
    170173static int subshell_pipe[2]; 
    171174 
     175/* To pass command buffer info from the subshell to MC */ 
     176static int command_buffer_pipe[2]; 
     177 
    172178/* The subshell's process ID */ 
    173179static pid_t subshell_pid = 1; 
    174180 
    static char subshell_cwd[MC_MAXPATHLEN + 1]; 
    178184/* Flag to indicate whether the subshell is ready for next command */ 
    179185static int subshell_ready; 
    180186 
     187/* Flag to indicate if the contents of the subshell command line need to be cleared before */ 
     188/* executing a command. This should only end up set to true if there is some sort of error.*/ 
     189/* This allows us to recover gracefully from an error.*/ 
     190static int subshell_should_clear_command_line; 
     191 
     192/* This is the local variable where the subshell prompt is stored while we are working on it. */ 
     193static GString *subshell_prompt_temp_buffer = NULL; 
     194 
    181195/* The following two flags can be changed by the SIGCHLD handler. This is */ 
    182196/* OK, because the 'int' type is updated atomically on all known machines */ 
    183197static volatile int subshell_alive, subshell_stopped; 
    init_subshell_child (const char *pty_name) 
    362376    dup2 (subshell_pty_slave, STDERR_FILENO); 
    363377 
    364378    close (subshell_pipe[READ]); 
     379    if (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) 
     380        close (command_buffer_pipe[READ]); 
    365381    close (subshell_pty_slave); /* These may be FD_CLOEXEC, but just in case... */ 
    366382    /* Close master side of pty.  This is important; apart from */ 
    367383    /* freeing up the descriptor for use in the subshell, it also       */ 
    synchronize (void) 
    469485} 
    470486 
    471487/* --------------------------------------------------------------------------------------------- */ 
     488 
     489static int 
     490flush_subshell (int max_wait_length) 
     491{ 
     492    int rc = 0; 
     493    ssize_t bytes = 0; 
     494    struct timeval timeleft = { 0, 0 }; 
     495    int return_value = 0; 
     496    fd_set tmp; 
     497 
     498    timeleft.tv_sec = max_wait_length; 
     499    FD_ZERO (&tmp); 
     500    FD_SET (mc_global.tty.subshell_pty, &tmp); 
     501 
     502    while (subshell_alive 
     503           && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0) 
     504    { 
     505        /* Check for 'select' errors */ 
     506        if (rc == -1) 
     507        { 
     508            if (errno == EINTR) 
     509            { 
     510                if (tty_got_winch ()) 
     511                    tty_change_screen_size (); 
     512 
     513                continue; 
     514            } 
     515 
     516            fprintf (stderr, "select (FD_SETSIZE, &tmp...): %s\r\n", unix_error_string (errno)); 
     517            exit (EXIT_FAILURE); 
     518        } 
     519        return_value = 1; 
     520        timeleft.tv_sec = 0; 
     521        timeleft.tv_usec = 0; 
     522 
     523        bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer)); 
     524        write_all (STDOUT_FILENO, pty_buffer, bytes); 
     525    } 
     526    return return_value; 
     527} 
     528 
     529/* --------------------------------------------------------------------------------------------- */ 
     530/* Get the contents of the current subshell command line buffer, and */ 
     531/* transfer the contents to the panel command prompt. */ 
     532 
     533static gboolean 
     534read_command_line_buffer(void) 
     535{ 
     536    fd_set read_set; 
     537    int j; 
     538    int bytes; 
     539    char subshell_command_buffer[BUF_LARGE]; 
     540    struct timeval subshell_prompt_timer = { 0, 0 }; 
     541    int command_buffer_length; 
     542    int cursor_position; 
     543    int maxfdp; 
     544 
     545    if (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) 
     546    { 
     547        FD_ZERO (&read_set); 
     548        FD_SET (command_buffer_pipe[READ], &read_set); 
     549        maxfdp = command_buffer_pipe[READ]; 
     550        /* First, flush the command buffer pipe. This pipe shouldn't be written 
     551           to under normal circumstances, but if it somehow does get written 
     552           to, we need to make sure to discard whatever data is there before 
     553           we try to use it. */ 
     554        while (select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer) != 0) 
     555        { 
     556            bytes = read (command_buffer_pipe[READ], subshell_command_buffer, sizeof(subshell_command_buffer)); 
     557        } 
     558 
     559        subshell_prompt_timer.tv_sec = 1; 
     560        /* get contents of command line buffer */ 
     561        write_all (mc_global.tty.subshell_pty, "\033[98~", 5); 
     562        FD_ZERO (&read_set); 
     563        FD_SET (command_buffer_pipe[READ], &read_set); 
     564        maxfdp = command_buffer_pipe[READ]; 
     565        if (select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer) != 1) 
     566            return 1; 
     567        bytes = read (command_buffer_pipe[READ], subshell_command_buffer, sizeof(subshell_command_buffer)); 
     568        if (bytes == sizeof(subshell_command_buffer)) 
     569            return 1; 
     570        subshell_command_buffer[bytes - 1] = 0; 
     571 
     572        /* Erase all non-text characters in the command buffer, such as tab, or newline, as this could cause problems. */ 
     573        for(j = 0;j < bytes - 1;j++) 
     574        { 
     575            if (!(subshell_command_buffer[j] >= 32 && subshell_command_buffer[j] <= 126)) 
     576                subshell_command_buffer[j] = ' '; 
     577        } 
     578        input_assign_text (cmdline, subshell_command_buffer); 
     579        command_buffer_length = bytes - 1; 
     580 
     581        /* get cursor position */ 
     582        write_all (mc_global.tty.subshell_pty, "\033[99~", 5); 
     583        FD_ZERO (&read_set); 
     584        FD_SET (command_buffer_pipe[READ], &read_set); 
     585        maxfdp = command_buffer_pipe[READ]; 
     586        if (select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer) != 1) 
     587            return 1; 
     588        bytes = read (command_buffer_pipe[READ], subshell_command_buffer, sizeof(subshell_command_buffer)); 
     589        subshell_command_buffer[bytes - 1] = 0; 
     590        cursor_position = atoi(subshell_command_buffer); 
     591        cmdline->point = cursor_position; 
     592        flush_subshell(0);    /* We need to send any remaining data to STDOUT before we finish. */ 
     593 
     594        /* Now we erase the current contents of the command line buffer */ 
     595        if (mc_global.shell->type != SHELL_ZSH)    /* In zsh, we can just press c-u to clear the line, without needing to go to the end of the line first first. In all other shells, we must go to the end of the line first. */ 
     596        { 
     597            if (cursor_position != command_buffer_length)  /* If we are not at the end of the line, we go to the end. */ 
     598            { 
     599                write_all (mc_global.tty.subshell_pty, "\005", 1); 
     600                if (flush_subshell(1) != 1) 
     601                    return 1; 
     602            } 
     603        } 
     604        if (command_buffer_length > 0)    /* Now we clear the line. */ 
     605        { 
     606            write_all (mc_global.tty.subshell_pty, "\025", 1); 
     607            if (flush_subshell(1) != 1) 
     608                return 1; 
     609        } 
     610    } 
     611    return 0; 
     612} 
     613 
     614/* --------------------------------------------------------------------------------------------- */ 
     615 
     616static void 
     617clear_subshell_prompt_string (void) 
     618{ 
     619    if(subshell_prompt_temp_buffer != NULL) 
     620        g_string_set_size (subshell_prompt_temp_buffer, 0); 
     621} 
     622 
     623/* --------------------------------------------------------------------------------------------- */ 
     624 
     625static gboolean 
     626parse_subshell_prompt_string (const char *buffer, int bytes) 
     627{ 
     628    int rc = 0; 
     629    int i; 
     630 
     631    /* First time through */ 
     632    if (subshell_prompt == NULL) 
     633    { 
     634        subshell_prompt = g_string_sized_new (INITIAL_PROMPT_SIZE); 
     635    } 
     636    if (subshell_prompt_temp_buffer == NULL) 
     637    { 
     638        subshell_prompt_temp_buffer = g_string_sized_new (INITIAL_PROMPT_SIZE); 
     639    } 
     640 
     641    /* Extract the prompt from the shell output */ 
     642    for (i = 0; i < bytes; i++) 
     643        if (buffer[i] == '\n' || buffer[i] == '\r') 
     644        { 
     645            g_string_set_size (subshell_prompt_temp_buffer, 0); 
     646        } 
     647        else if (buffer[i] != '\0') 
     648            g_string_append_c (subshell_prompt_temp_buffer, buffer[i]); 
     649 
     650    return (rc != 0 || bytes != 0); 
     651} 
     652 
     653/* --------------------------------------------------------------------------------------------- */ 
     654 
     655static void 
     656set_prompt_string (void) 
     657{ 
     658    if (subshell_prompt_temp_buffer->len != 0) 
     659        g_string_assign (subshell_prompt, subshell_prompt_temp_buffer->str); 
     660 
     661    setup_cmdline(); 
     662} 
     663 
     664/* --------------------------------------------------------------------------------------------- */ 
    472665/** Feed the subshell our keyboard input until it says it's finished */ 
    473666 
    474667static gboolean 
    feed_subshell (int how, gboolean fail_on_error) 
    477670    fd_set read_set;            /* For 'select' */ 
    478671    int bytes;                  /* For the return value from 'read' */ 
    479672    int i;                      /* Loop counter */ 
     673    int should_read_new_prompt = FALSE; 
    480674 
    481675    struct timeval wtime;       /* Maximum time we wait for the subshell */ 
    482676    struct timeval *wptr; 
    483677 
     678    subshell_should_clear_command_line = FALSE; 
     679 
    484680    /* we wait up to 10 seconds if fail_on_error, forever otherwise */ 
    485681    wtime.tv_sec = 10; 
    486682    wtime.tv_usec = 0; 
    feed_subshell (int how, gboolean fail_on_error) 
    549745 
    550746            if (how == VISIBLY) 
    551747                write_all (STDOUT_FILENO, pty_buffer, bytes); 
     748            if (should_read_new_prompt) 
     749            { 
     750                parse_subshell_prompt_string(pty_buffer, bytes); 
     751            } 
     752 
    552753        } 
    553754 
    554755        else if (FD_ISSET (subshell_pipe[READ], &read_set)) 
    feed_subshell (int how, gboolean fail_on_error) 
    573774                subshell_state = INACTIVE; 
    574775                return TRUE; 
    575776            } 
     777            should_read_new_prompt = TRUE; 
     778            clear_subshell_prompt_string(); 
    576779        } 
    577780 
    578781        else if (FD_ISSET (STDIN_FILENO, &read_set)) 
    579782            /* Read from stdin, write to the subshell */ 
    580783        { 
     784            should_read_new_prompt = FALSE; 
    581785            bytes = read (STDIN_FILENO, pty_buffer, sizeof (pty_buffer)); 
    582786            if (bytes <= 0) 
    583787            { 
    feed_subshell (int how, gboolean fail_on_error) 
    592796                { 
    593797                    write_all (mc_global.tty.subshell_pty, pty_buffer, i); 
    594798                    if (subshell_ready) 
     799                    { 
    595800                        subshell_state = INACTIVE; 
     801                        set_prompt_string (); 
     802                        if (subshell_ready == TRUE && read_command_line_buffer() != 0) 
     803                        { 
     804                            /* If we got here, some unforseen error must have occurred. */ 
     805                            flush_subshell(0); 
     806                            input_assign_text (cmdline, ""); 
     807                            subshell_should_clear_command_line = TRUE; 
     808                            return TRUE; 
     809                        } 
     810                    } 
    596811                    return TRUE; 
    597812                } 
    598813 
    599814            write_all (mc_global.tty.subshell_pty, pty_buffer, bytes); 
    600815 
    601816            if (pty_buffer[bytes - 1] == '\n' || pty_buffer[bytes - 1] == '\r') 
     817            { 
     818                /* We should only clear the command line if we are using a shell that supports persistent command buffer, 
     819                   otherwise we get awkward results. */ 
     820                if (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) 
     821                    input_assign_text (cmdline, ""); 
    602822                subshell_ready = FALSE; 
     823            } 
    603824        } 
    604825        else 
    605826            return FALSE; 
    init_subshell_precmd (char *precmd, size_t buff_size) 
    7841005    { 
    7851006    case SHELL_BASH: 
    7861007        g_snprintf (precmd, buff_size, 
     1008                    " bind -x '\"\\033[98~\":\"echo $READLINE_LINE>&%d\"'\n" 
     1009                    " bind -x '\"\\033[99~\":\"echo $READLINE_POINT>&%d\"'\n" 
    7871010                    " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n" 
    788                     "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]); 
     1011                    "PS1='\\u@\\h:\\w\\$ '\n" 
     1012        , command_buffer_pipe[WRITE], command_buffer_pipe[WRITE], subshell_pipe[WRITE]); 
    7891013        break; 
    7901014 
    7911015    case SHELL_ASH_BUSYBOX: 
    init_subshell_precmd (char *precmd, size_t buff_size) 
    8441068 
    8451069    case SHELL_ZSH: 
    8461070        g_snprintf (precmd, buff_size, 
     1071                    " mc_print_command_buffer () { echo $BUFFER >&%d}\n" 
     1072                    " zle -N mc_print_command_buffer\n" 
     1073                    " bindkey '^[[98~' mc_print_command_buffer\n" 
     1074                    " mc_print_cursor_position () { echo $CURSOR >&%d}\n" 
     1075                    " zle -N mc_print_cursor_position\n" 
     1076                    " bindkey '^[[99~' mc_print_cursor_position\n" 
    8471077                    " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n" 
    848                     "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]); 
     1078                    "PS1='%%n@%%m:%%~%%# '\n", command_buffer_pipe[WRITE], command_buffer_pipe[WRITE], subshell_pipe[WRITE]); 
    8491079        break; 
    8501080 
    8511081    case SHELL_TCSH: 
    init_subshell_precmd (char *precmd, size_t buff_size) 
    8541084                    "set prompt='%%n@%%m:%%~%%# '; " 
    8551085                    "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo); 
    8561086        break; 
    857  
    8581087    case SHELL_FISH: 
    8591088        g_snprintf (precmd, buff_size, 
    8601089                    " if not functions -q fish_prompt_mc;" 
    8611090                    "functions -e fish_right_prompt;" 
    8621091                    "functions -c fish_prompt fish_prompt_mc; end;" 
    8631092                    "function fish_prompt;" 
     1093                    "bind \033\[98~ 'echo (commandline)>&%d'\n" 
     1094                    "bind \033\[99~ 'echo (commandline -C)>&%d'\n" 
    8641095                    "echo \"$PWD\">&%d; fish_prompt_mc; kill -STOP %%self; end\n", 
    865                     subshell_pipe[WRITE]); 
     1096                    command_buffer_pipe[WRITE], command_buffer_pipe[WRITE], subshell_pipe[WRITE]); 
    8661097        break; 
    8671098 
    8681099    default: 
    init_subshell (void) 
    10401271            mc_global.tty.use_subshell = FALSE; 
    10411272            return; 
    10421273        } 
     1274        if (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) 
     1275        { 
     1276            if (pipe (command_buffer_pipe)) 
     1277            { 
     1278                perror (__FILE__ ": couldn't create pipe"); 
     1279                mc_global.tty.use_subshell = FALSE; 
     1280                return; 
     1281            } 
     1282        } 
    10431283    } 
    10441284 
    10451285    /* Fork the subshell */ 
    init_subshell (void) 
    10611301        /* We are in the child process */ 
    10621302        init_subshell_child (pty_name); 
    10631303    } 
    1064  
    10651304    init_subshell_precmd (precmd, BUF_MEDIUM); 
    10661305 
    10671306    write_all (mc_global.tty.subshell_pty, precmd, strlen (precmd)); 
    init_subshell (void) 
    10841323int 
    10851324invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 
    10861325{ 
     1326    size_t i; 
    10871327    /* Make the MC terminal transparent */ 
    10881328    tcsetattr (STDOUT_FILENO, TCSANOW, &raw_mode); 
    10891329 
    invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 
    11001340            /* FIXME: possibly take out this hack; the user can re-play it by hitting C-hyphen a few times! */ 
    11011341            if (subshell_ready && mc_global.mc_run_mode == MC_RUN_FULL) 
    11021342                write_all (mc_global.tty.subshell_pty, " \b", 2);       /* Hack to make prompt reappear */ 
     1343  
     1344            if (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) 
     1345            { 
     1346                /* Check to make sure there are no non text characters in the command buffer, such as tab, or newline, as this could cause problems. */ 
     1347                for(i = 0;i < strlen(cmdline->buffer);i++) 
     1348                { 
     1349                    if (!(cmdline->buffer[i] >= 32 && cmdline->buffer[i] <= 126)) 
     1350                        cmdline->buffer[i] = ' '; 
     1351                } 
     1352 
     1353                /* Write the command buffer to the subshell. */ 
     1354                write_all (mc_global.tty.subshell_pty, cmdline->buffer, strlen (cmdline->buffer)); 
     1355 
     1356                /* Put the cursor in the correct place in the subshell. */ 
     1357                for (i = 0;i < strlen (cmdline->buffer) - cmdline->point;i++) 
     1358                    write_all (mc_global.tty.subshell_pty, "\033[D", 3); 
     1359            } 
    11031360        } 
    11041361    } 
    11051362    else                        /* MC has passed us a user command */ 
    11061363    { 
     1364        /* Before we write to the command prompt, we need to clear whatever */ 
     1365        /* data is there, but only if we are using one of the shells that */ 
     1366        /* doesn't support keeping command buffer contents, OR if there was */ 
     1367        /* some sort of error. */ 
     1368        if (!(mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) || subshell_should_clear_command_line) 
     1369        { 
     1370            write_all (mc_global.tty.subshell_pty, "\003", 1); 
     1371            subshell_state = RUNNING_COMMAND; 
     1372            if (mc_global.shell->type != SHELL_FISH) 
     1373                feed_subshell (QUIETLY, FALSE); 
     1374        } 
    11071375        if (how == QUIETLY) 
    11081376            write_all (mc_global.tty.subshell_pty, " ", 1); 
    11091377        /* FIXME: if command is long (>8KB ?) we go comma */ 
    invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 
    11311399    return subshell_get_mainloop_quit (); 
    11321400} 
    11331401 
    1134  
    11351402/* --------------------------------------------------------------------------------------------- */ 
    11361403 
    11371404gboolean 
    read_subshell_prompt (void) 
    11401407    int rc = 0; 
    11411408    ssize_t bytes = 0; 
    11421409    struct timeval timeleft = { 0, 0 }; 
    1143     GString *p; 
    1144     gboolean prompt_was_reset = FALSE; 
     1410    int should_reset_prompt = TRUE; 
     1411    int got_new_prompt = FALSE; 
    11451412 
    11461413    fd_set tmp; 
    11471414    FD_ZERO (&tmp); 
    11481415    FD_SET (mc_global.tty.subshell_pty, &tmp); 
    11491416 
    1150     /* First time through */ 
    1151     if (subshell_prompt == NULL) 
    1152         subshell_prompt = g_string_sized_new (INITIAL_PROMPT_SIZE); 
    1153  
    1154     p = g_string_sized_new (INITIAL_PROMPT_SIZE); 
    1155  
    11561417    while (subshell_alive 
    11571418           && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0) 
    11581419    { 
    1159         ssize_t i; 
    1160  
    11611420        /* Check for 'select' errors */ 
    11621421        if (rc == -1) 
    11631422        { 
    read_subshell_prompt (void) 
    11741433        } 
    11751434 
    11761435        bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer)); 
    1177  
    1178         /* Extract the prompt from the shell output */ 
    1179         for (i = 0; i < bytes; i++) 
    1180             if (pty_buffer[i] == '\n' || pty_buffer[i] == '\r') 
    1181             { 
    1182                 g_string_set_size (p, 0); 
    1183                 prompt_was_reset = TRUE; 
    1184             } 
    1185             else if (pty_buffer[i] != '\0') 
    1186                 g_string_append_c (p, pty_buffer[i]); 
     1436        if (should_reset_prompt) 
     1437        { 
     1438            should_reset_prompt = FALSE; 
     1439            clear_subshell_prompt_string(); 
     1440        } 
     1441        parse_subshell_prompt_string (pty_buffer, bytes); 
     1442        got_new_prompt = TRUE; 
     1443    } 
     1444    if (got_new_prompt == TRUE) 
     1445    { 
     1446        set_prompt_string (); 
    11871447    } 
    1188  
    1189     if (p->len != 0 || prompt_was_reset) 
    1190         g_string_assign (subshell_prompt, p->str); 
    1191  
    1192     g_string_free (p, TRUE); 
    11931448 
    11941449    return (rc != 0 || bytes != 0); 
    11951450} 
    do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt) 
    12591514        return; 
    12601515    } 
    12611516 
     1517    if (!(mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH || mc_global.shell->type == SHELL_FISH) || subshell_should_clear_command_line) 
     1518    { 
     1519        write_all (mc_global.tty.subshell_pty, "\003", 1); 
     1520        subshell_state = RUNNING_COMMAND; 
     1521        if (mc_global.shell->type != SHELL_FISH) 
     1522            feed_subshell (QUIETLY, FALSE); 
     1523    } 
    12621524    /* The initial space keeps this out of the command history (in bash 
    12631525       because we set "HISTCONTROL=ignorespace") */ 
    12641526    write_all (mc_global.tty.subshell_pty, " cd ", 4);