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) 551 551 { 552 552 if (mc_global.mc_run_mode == MC_RUN_FULL) 553 553 { 554 do_load_prompt ();555 554 if (new_dir_vpath != NULL) 556 555 do_possible_cd (new_dir_vpath); 557 556 } -
src/subshell/common.c
diff --git a/src/subshell/common.c b/src/subshell/common.c index 06699233c..ed6f94493 100644
a b 105 105 #include "lib/util.h" 106 106 #include "lib/widget.h" 107 107 108 #include "src/filemanager/layout.h" /* setup_cmdline() */ 109 #include "src/filemanager/command.h" /* cmdline */ 110 108 111 #include "subshell.h" 109 112 #include "internal.h" 110 113 … … static char pty_buffer[PTY_BUFFER_SIZE] = "\0"; 169 172 /* To pass CWD info from the subshell to MC */ 170 173 static int subshell_pipe[2]; 171 174 175 /* To pass command buffer info from the subshell to MC */ 176 static int command_buffer_pipe[2]; 177 172 178 /* The subshell's process ID */ 173 179 static pid_t subshell_pid = 1; 174 180 … … static char subshell_cwd[MC_MAXPATHLEN + 1]; 178 184 /* Flag to indicate whether the subshell is ready for next command */ 179 185 static int subshell_ready; 180 186 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.*/ 190 static 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. */ 193 static GString *subshell_prompt_temp_buffer = NULL; 194 181 195 /* The following two flags can be changed by the SIGCHLD handler. This is */ 182 196 /* OK, because the 'int' type is updated atomically on all known machines */ 183 197 static volatile int subshell_alive, subshell_stopped; … … init_subshell_child (const char *pty_name) 362 376 dup2 (subshell_pty_slave, STDERR_FILENO); 363 377 364 378 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]); 365 381 close (subshell_pty_slave); /* These may be FD_CLOEXEC, but just in case... */ 366 382 /* Close master side of pty. This is important; apart from */ 367 383 /* freeing up the descriptor for use in the subshell, it also */ … … synchronize (void) 469 485 } 470 486 471 487 /* --------------------------------------------------------------------------------------------- */ 488 489 static int 490 flush_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 533 static gboolean 534 read_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 616 static void 617 clear_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 625 static gboolean 626 parse_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 655 static void 656 set_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 /* --------------------------------------------------------------------------------------------- */ 472 665 /** Feed the subshell our keyboard input until it says it's finished */ 473 666 474 667 static gboolean … … feed_subshell (int how, gboolean fail_on_error) 477 670 fd_set read_set; /* For 'select' */ 478 671 int bytes; /* For the return value from 'read' */ 479 672 int i; /* Loop counter */ 673 int should_read_new_prompt = FALSE; 480 674 481 675 struct timeval wtime; /* Maximum time we wait for the subshell */ 482 676 struct timeval *wptr; 483 677 678 subshell_should_clear_command_line = FALSE; 679 484 680 /* we wait up to 10 seconds if fail_on_error, forever otherwise */ 485 681 wtime.tv_sec = 10; 486 682 wtime.tv_usec = 0; … … feed_subshell (int how, gboolean fail_on_error) 549 745 550 746 if (how == VISIBLY) 551 747 write_all (STDOUT_FILENO, pty_buffer, bytes); 748 if (should_read_new_prompt) 749 { 750 parse_subshell_prompt_string(pty_buffer, bytes); 751 } 752 552 753 } 553 754 554 755 else if (FD_ISSET (subshell_pipe[READ], &read_set)) … … feed_subshell (int how, gboolean fail_on_error) 573 774 subshell_state = INACTIVE; 574 775 return TRUE; 575 776 } 777 should_read_new_prompt = TRUE; 778 clear_subshell_prompt_string(); 576 779 } 577 780 578 781 else if (FD_ISSET (STDIN_FILENO, &read_set)) 579 782 /* Read from stdin, write to the subshell */ 580 783 { 784 should_read_new_prompt = FALSE; 581 785 bytes = read (STDIN_FILENO, pty_buffer, sizeof (pty_buffer)); 582 786 if (bytes <= 0) 583 787 { … … feed_subshell (int how, gboolean fail_on_error) 592 796 { 593 797 write_all (mc_global.tty.subshell_pty, pty_buffer, i); 594 798 if (subshell_ready) 799 { 595 800 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 } 596 811 return TRUE; 597 812 } 598 813 599 814 write_all (mc_global.tty.subshell_pty, pty_buffer, bytes); 600 815 601 816 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, ""); 602 822 subshell_ready = FALSE; 823 } 603 824 } 604 825 else 605 826 return FALSE; … … init_subshell_precmd (char *precmd, size_t buff_size) 784 1005 { 785 1006 case SHELL_BASH: 786 1007 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" 787 1010 " 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]); 789 1013 break; 790 1014 791 1015 case SHELL_ASH_BUSYBOX: … … init_subshell_precmd (char *precmd, size_t buff_size) 844 1068 845 1069 case SHELL_ZSH: 846 1070 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" 847 1077 " _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]); 849 1079 break; 850 1080 851 1081 case SHELL_TCSH: … … init_subshell_precmd (char *precmd, size_t buff_size) 854 1084 "set prompt='%%n@%%m:%%~%%# '; " 855 1085 "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo); 856 1086 break; 857 858 1087 case SHELL_FISH: 859 1088 g_snprintf (precmd, buff_size, 860 1089 " if not functions -q fish_prompt_mc;" 861 1090 "functions -e fish_right_prompt;" 862 1091 "functions -c fish_prompt fish_prompt_mc; end;" 863 1092 "function fish_prompt;" 1093 "bind \033\[98~ 'echo (commandline)>&%d'\n" 1094 "bind \033\[99~ 'echo (commandline -C)>&%d'\n" 864 1095 "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]); 866 1097 break; 867 1098 868 1099 default: … … init_subshell (void) 1040 1271 mc_global.tty.use_subshell = FALSE; 1041 1272 return; 1042 1273 } 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 } 1043 1283 } 1044 1284 1045 1285 /* Fork the subshell */ … … init_subshell (void) 1061 1301 /* We are in the child process */ 1062 1302 init_subshell_child (pty_name); 1063 1303 } 1064 1065 1304 init_subshell_precmd (precmd, BUF_MEDIUM); 1066 1305 1067 1306 write_all (mc_global.tty.subshell_pty, precmd, strlen (precmd)); … … init_subshell (void) 1084 1323 int 1085 1324 invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 1086 1325 { 1326 size_t i; 1087 1327 /* Make the MC terminal transparent */ 1088 1328 tcsetattr (STDOUT_FILENO, TCSANOW, &raw_mode); 1089 1329 … … invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 1100 1340 /* FIXME: possibly take out this hack; the user can re-play it by hitting C-hyphen a few times! */ 1101 1341 if (subshell_ready && mc_global.mc_run_mode == MC_RUN_FULL) 1102 1342 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 } 1103 1360 } 1104 1361 } 1105 1362 else /* MC has passed us a user command */ 1106 1363 { 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 } 1107 1375 if (how == QUIETLY) 1108 1376 write_all (mc_global.tty.subshell_pty, " ", 1); 1109 1377 /* FIXME: if command is long (>8KB ?) we go comma */ … … invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath) 1131 1399 return subshell_get_mainloop_quit (); 1132 1400 } 1133 1401 1134 1135 1402 /* --------------------------------------------------------------------------------------------- */ 1136 1403 1137 1404 gboolean … … read_subshell_prompt (void) 1140 1407 int rc = 0; 1141 1408 ssize_t bytes = 0; 1142 1409 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; 1145 1412 1146 1413 fd_set tmp; 1147 1414 FD_ZERO (&tmp); 1148 1415 FD_SET (mc_global.tty.subshell_pty, &tmp); 1149 1416 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 1156 1417 while (subshell_alive 1157 1418 && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0) 1158 1419 { 1159 ssize_t i;1160 1161 1420 /* Check for 'select' errors */ 1162 1421 if (rc == -1) 1163 1422 { … … read_subshell_prompt (void) 1174 1433 } 1175 1434 1176 1435 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 (); 1187 1447 } 1188 1189 if (p->len != 0 || prompt_was_reset)1190 g_string_assign (subshell_prompt, p->str);1191 1192 g_string_free (p, TRUE);1193 1448 1194 1449 return (rc != 0 || bytes != 0); 1195 1450 } … … do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt) 1259 1514 return; 1260 1515 } 1261 1516 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 } 1262 1524 /* The initial space keeps this out of the command history (in bash 1263 1525 because we set "HISTCONTROL=ignorespace") */ 1264 1526 write_all (mc_global.tty.subshell_pty, " cd ", 4);