diff --git a/cmd-display-menu.c b/cmd-display-menu.c index a4f0184bf..98f8ca5bb 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = { .name = "display-popup", .alias = "popup", - .args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL }, - .usage = "[-BCEkN] [-b border-lines] [-c target-client] " + .args = { "Bb:Cc:d:e:Eh:ks:S:t:T:w:x:y:", 0, -1, NULL }, + .usage = "[-BCEk] [-b border-lines] [-c target-client] " "[-d start-directory] [-e environment] [-h height] " "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE " [-T title] [-w width] [-x position] [-y position] " @@ -505,14 +505,22 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) } window_pop_zoom(w); new_wp->flags |= PANE_CAPTUREALLKEYS; + if (!args_has(args, 'E')) + new_wp->flags |= PANE_CLOSEONCANCEL; options_set_number(new_wp->options, "pane-border-lines", lines); - if (args_has(args, 'E') > 1) - options_set_number(new_wp->options, "remain-on-exit", 2); - else if (args_has(args, 'E')) + if (args_has(args, 'E') > 1) { + if (args_has(args, 'k')) + options_set_number(new_wp->options, "remain-on-exit", 4); + else + options_set_number(new_wp->options, "remain-on-exit", 2); + } else if (args_has(args, 'E')) options_set_number(new_wp->options, "remain-on-exit", 0); - else + else if (args_has(args, 'k')) options_set_number(new_wp->options, "remain-on-exit", 3); + else + options_set_number(new_wp->options, "remain-on-exit", 1); + options_set_string(new_wp->options, "remain-on-exit-format", 0, "%s", ""); if (style != NULL) { if (options_set_string(new_wp->options, "window-style", 0, diff --git a/cmd-split-window.c b/cmd-split-window.c index c907dd152..3ddc33c22 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -40,8 +40,8 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "AbB:Cc:de:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-AbCdefhIkKLMOPvWZ] [-B border-lines] " + .args = { "AbB:Cc:Dde:EfF:hIkl:KLMm:Op:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-AbCDefhIkKLMOPvWZ] [-B border-lines] " "[-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " @@ -219,6 +219,8 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) new_wp->flags |= PANE_CAPTUREALLKEYS; if (args_has(args, 'C') && args_has(args, 'O')) new_wp->flags |= PANE_CLOSEONCLICK; + if (args_has(args, 'D') && args_has(args, 'O')) + new_wp->flags |= PANE_CLOSEONCANCEL; style = args_get(args, 's'); if (style != NULL) { diff --git a/options-table.c b/options-table.c index 32f842347..7a2575b02 100644 --- a/options-table.c +++ b/options-table.c @@ -91,7 +91,7 @@ static const char *options_table_window_size_list[] = { "largest", "smallest", "manual", "latest", NULL }; static const char *options_table_remain_on_exit_list[] = { - "off", "on", "failed", "key", NULL + "off", "on", "failed", "key", "failed-key", NULL }; static const char *options_table_destroy_unattached_list[] = { "off", "on", "keep-last", "keep-group", NULL @@ -1671,7 +1671,8 @@ const struct options_table_entry options_table[] = { .choices = options_table_remain_on_exit_list, .default_num = 0, .text = "Whether panes should remain ('on'), remain until a key is " - "pressed ('key') or be automatically killed ('off' or " + "pressed after any exit ('key') or after a failure " + "('failed-key'), or be automatically killed ('off' or " "'failed') when the program inside exits." }, diff --git a/regress/modal-pane.sh b/regress/modal-pane.sh index 3c5c1691a..a09eef327 100644 --- a/regress/modal-pane.sh +++ b/regress/modal-pane.sh @@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no $TMUX set -g @modal-root no $TMUX bind -n z set -g @modal-root yes -modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \ +modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \ -x 20 -y 5 -X 20 -Y 10 'cat') || - fail "new-pane -OK failed" + fail "new-pane -ODK failed" sleep 1 $TMUX2 send-keys -t "$OUTER" C-b x z Enter sleep 1 @@ -356,6 +356,28 @@ new_left=$(fmt "$modal" '#{pane_left}') new_top=$(fmt "$modal" '#{pane_top}') [ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] || fail "key-capturing modal pane did not move" +$TMUX2 send-keys -t "$OUTER" Escape +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \ + -x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') || + fail "new-pane -ODK failed" +sleep 1 +$TMUX2 send-keys -t "$OUTER" C-c +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +# A dead modal does not close on Escape or C-c without -D. +modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \ + -x 20 -y 5 -X 20 -Y 10 'sleep 1') || + fail "new-pane -O failed" +check_ok set-option -p -t "$modal" remain-on-exit on +sleep 2 +must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1 +$TMUX2 send-keys -t "$OUTER" Escape +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" check_ok kill-pane -t "$modal" sleep 1 @@ -372,8 +394,12 @@ case "$($TMUX capture-pane -pt "$modal")" in *xz*) ;; *) fail "keys did not reach display-popup pane" ;; esac -check_ok kill-pane -t "$modal" +$TMUX2 send-keys -t "$OUTER" Escape sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +$TMUX2 send-keys -t "$OUTER" C-c +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' # Creating a popup pane must not fire the split-window hook. check_ok set-hook -t modal after-split-window \ @@ -400,8 +426,56 @@ sleep 2 modal=$(fmt modal:0 '#{window_modal_pane}') [ -n "$modal" ] || fail "retained display-popup was not created" must_equal "$(fmt "$modal" '#{pane_dead}')" 1 +case "$($TMUX capture-pane -pt "$modal")" in +*'Pane is dead'*) fail "display-popup showed remain-on-exit message" ;; +esac $TMUX2 send-keys -t "$OUTER" a sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +$TMUX2 send-keys -t "$OUTER" Escape +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +$TMUX bind K display-popup -k -t "$p0" -w 20 -h 5 'printf done' +$TMUX2 send-keys -t "$OUTER" C-b K +sleep 2 +modal=$(fmt modal:0 '#{window_modal_pane}') +[ -n "$modal" ] || fail "display-popup -k was not created" +must_equal "$(fmt "$modal" '#{pane_dead}')" 1 +$TMUX2 send-keys -t "$OUTER" a +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +$TMUX bind F display-popup -EE -t "$p0" -w 20 -h 5 'exit 1' +$TMUX2 send-keys -t "$OUTER" C-b F +sleep 2 +modal=$(fmt modal:0 '#{window_modal_pane}') +[ -n "$modal" ] || fail "failed display-popup -EE did not remain" +must_equal "$(fmt "$modal" '#{pane_dead}')" 1 +$TMUX2 send-keys -t "$OUTER" a +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +$TMUX2 send-keys -t "$OUTER" Escape +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal" +check_ok kill-pane -t "$modal" +sleep 1 + +check_ok display-popup -EE -t "$p0" true +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +$TMUX bind G display-popup -EE -k -t "$p0" -w 20 -h 5 'exit 1' +$TMUX2 send-keys -t "$OUTER" C-b G +sleep 2 +modal=$(fmt modal:0 '#{window_modal_pane}') +[ -n "$modal" ] || fail "failed display-popup -EE -k did not remain" +must_equal "$(fmt "$modal" '#{pane_dead}')" 1 +must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key +$TMUX2 send-keys -t "$OUTER" a +sleep 1 +must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' + +check_ok display-popup -EE -k -t "$p0" true must_equal "$(fmt modal:0 '#{window_modal_pane}')" '' # A nonmodal floating pane may remain above zoom, and switching between it and diff --git a/server-client.c b/server-client.c index 77316a306..a1f8b3121 100644 --- a/server-client.c +++ b/server-client.c @@ -1273,15 +1273,19 @@ server_client_repeat_time(struct client *c, struct key_binding *bd) return (repeat); } -/* Handle a key press on a dead pane waiting for a key. */ +/* Handle a key press which closes a dead pane. */ static int server_client_handle_dead_key(struct window_pane *wp, key_code key) { + int remain_on_exit; + if (wp == NULL || (~wp->flags & PANE_EXITED) || KEYC_IS_MOUSE(key) || - KEYC_IS_PASTE(key) || - options_get_number(wp->options, "remain-on-exit") != 3) + KEYC_IS_PASTE(key)) + return (0); + remain_on_exit = options_get_number(wp->options, "remain-on-exit"); + if (remain_on_exit != 3 && remain_on_exit != 4) return (0); options_set_number(wp->options, "remain-on-exit", 0); server_destroy_pane(wp, 0); @@ -1634,9 +1638,9 @@ server_client_handle_key0(struct client *c, struct key_event *event, } /* - * Key presses for panes capturing all keys and in the command prompt are - * a special case. The queue might be blocked so they need to be processed - * immediately rather than queued. + * Dead panes waiting for a key, modal cancel keys, panes capturing all keys + * and the command prompt are special cases. The queue might be blocked so + * they need to be processed immediately rather than queued. */ if (~c->flags & CLIENT_READONLY) { if (c->message_string != NULL) { @@ -1646,12 +1650,19 @@ server_client_handle_key0(struct client *c, struct key_event *event, } wp = s->curw->window->active; + if (server_client_handle_dead_key(wp, event->key)) + return (0); + if (wp != NULL && + wp == wp->window->modal && + (wp->flags & PANE_CLOSEONCANCEL) && + (event->key == '\033' || event->key == ('c'|KEYC_CTRL))) { + server_kill_pane(wp); + return (0); + } if (wp != NULL && (wp->flags & PANE_CAPTUREALLKEYS) && TAILQ_EMPTY(&wp->modes) && !KEYC_IS_MOUSE(event->key)) { - if (server_client_handle_dead_key(wp, event->key)) - return (0); if (~wp->flags & PANE_EXITED) { window_pane_key(wp, c, s, s->curw, event->key, &event->m); diff --git a/server-fn.c b/server-fn.c index 697d7abc5..9721aa03b 100644 --- a/server-fn.c +++ b/server-fn.c @@ -386,6 +386,7 @@ server_destroy_pane(struct window_pane *wp, int notify) case 0: break; case 2: + case 4: if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) break; /* FALLTHROUGH */ diff --git a/tmux.1 b/tmux.1 index fcdb3e426..526d67cb3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3655,7 +3655,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl AbCdefhIkKLMOPvWZ +.Op Fl AbCDefhIkKLMOPvWZ .Op Fl B Ar border\-lines .Op Fl c Ar start\-directory .Op Fl e Ar environment @@ -3717,6 +3717,13 @@ all keys, including the prefix key, are passed directly to the modal pane. With .Fl C , the modal pane is closed when the mouse is clicked outside it. +With +.Fl D , +the modal pane is closed when +.Ql Escape +or +.Ql C-c +is pressed. .Pp The .Fl L @@ -6292,7 +6299,7 @@ uses when the colour with that index is requested. The index may be from zero to 255. .Pp .It Xo Ic remain\-on\-exit -.Op Ic on | off | failed | key +.Op Ic on | off | failed | key | failed\-key .Xc A pane with this flag set is not destroyed when the program running in it exits. @@ -6302,6 +6309,10 @@ then only when the program exit status is not zero. If set to .Ic key , the pane stays open and closes when a key is pressed. +If set to +.Ic failed\-key , +the pane stays open and closes when a key is pressed only if the program exit +status is not zero. The pane may be reactivated with the .Ic respawn\-pane command. diff --git a/tmux.h b/tmux.h index 4ce2f2d05..17cd168bc 100644 --- a/tmux.h +++ b/tmux.h @@ -1339,6 +1339,7 @@ struct window_pane { #define PANE_CLOSEONCLICK 0x80000 #define PANE_CAPTUREALLKEYS 0x100000 #define PANE_FLOATOVERZOOM 0x200000 +#define PANE_CLOSEONCANCEL 0x400000 bitstr_t *sync_dirty; u_int sync_dirty_size;