Rework of copy mode commands ("send-keys -X") to parse the arguments so
authornicm <nicm@openbsd.org>
Fri, 4 Oct 2024 07:03:08 +0000 (07:03 +0000)
committernicm <nicm@openbsd.org>
Fri, 4 Oct 2024 07:03:08 +0000 (07:03 +0000)
that flags may be detected propertly rather than just looking for
strings ("-O" and so on). Also add -C and -P flags to the copy commands:
-C prevents the commands from sending the text to the clipboard and -P
prevents them from adding the text as a paste buffer.

Note some of the default key bindings change to add "--" and any similar
custom key bindings using "send-keys -X" may need a similar change.

GitHub issue 4153.

usr.bin/tmux/key-bindings.c
usr.bin/tmux/tmux.1
usr.bin/tmux/window-copy.c

index c900084..5f14aa3 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: key-bindings.c,v 1.149 2024/08/21 05:03:13 nicm Exp $ */
+/* $OpenBSD: key-bindings.c,v 1.150 2024/10/04 07:03:08 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -49,7 +49,7 @@
        " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Top,}' '<' {send -X history-top}" \
        " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \
        " ''" \
-       " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {if -F '#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}' 'copy-mode -t='; send -Xt= search-backward \"#{q:mouse_word}\"}" \
+       " '#{?mouse_word,Search For #[underscore]#{=/9/...:mouse_word},}' 'C-r' {if -F '#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}' 'copy-mode -t='; send -Xt= search-backward -- \"#{q:mouse_word}\"}" \
        " '#{?mouse_word,Type #[underscore]#{=/9/...:mouse_word},}' 'C-y' {copy-mode -q; send-keys -l -- \"#{q:mouse_word}\"}" \
        " '#{?mouse_word,Copy #[underscore]#{=/9/...:mouse_word},}' 'c' {copy-mode -q; set-buffer -- \"#{q:mouse_word}\"}" \
        " '#{?mouse_line,Copy Line,}' 'l' {copy-mode -q; set-buffer -- \"#{q:mouse_line}\"}" \
@@ -489,26 +489,26 @@ key_bindings_init(void)
                "bind -Tcopy-mode C-k { send -X copy-pipe-end-of-line-and-cancel }",
                "bind -Tcopy-mode C-n { send -X cursor-down }",
                "bind -Tcopy-mode C-p { send -X cursor-up }",
-               "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental '%%' } }",
-               "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental '%%' } }",
+               "bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
+               "bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
                "bind -Tcopy-mode C-v { send -X page-down }",
                "bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }",
                "bind -Tcopy-mode Escape { send -X cancel }",
                "bind -Tcopy-mode Space { send -X page-down }",
                "bind -Tcopy-mode , { send -X jump-reverse }",
                "bind -Tcopy-mode \\; { send -X jump-again }",
-               "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }",
+               "bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
                "bind -Tcopy-mode N { send -X search-reverse }",
                "bind -Tcopy-mode P { send -X toggle-position }",
                "bind -Tcopy-mode R { send -X rectangle-toggle }",
-               "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }",
+               "bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
                "bind -Tcopy-mode X { send -X set-mark }",
-               "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }",
-               "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line '%%' } }",
+               "bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
+               "bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }",
                "bind -Tcopy-mode n { send -X search-again }",
                "bind -Tcopy-mode q { send -X cancel }",
                "bind -Tcopy-mode r { send -X refresh-from-pane }",
-               "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }",
+               "bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
                "bind -Tcopy-mode Home { send -X start-of-line }",
                "bind -Tcopy-mode End { send -X end-of-line }",
                "bind -Tcopy-mode MouseDown1Pane select-pane",
@@ -553,8 +553,8 @@ key_bindings_init(void)
                "bind -Tcopy-mode C-Down { send -X scroll-down }",
 
                /* Copy mode (vi) keys. */
-               "bind -Tcopy-mode-vi '#' { send -FX search-backward '#{copy_cursor_word}' }",
-               "bind -Tcopy-mode-vi * { send -FX search-forward '#{copy_cursor_word}' }",
+               "bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }",
+               "bind -Tcopy-mode-vi * { send -FX search-forward -- '#{copy_cursor_word}' }",
                "bind -Tcopy-mode-vi C-c { send -X cancel }",
                "bind -Tcopy-mode-vi C-d { send -X halfpage-down }",
                "bind -Tcopy-mode-vi C-e { send -X scroll-down }",
@@ -570,7 +570,7 @@ key_bindings_init(void)
                "bind -Tcopy-mode-vi Space { send -X begin-selection }",
                "bind -Tcopy-mode-vi '$' { send -X end-of-line }",
                "bind -Tcopy-mode-vi , { send -X jump-reverse }",
-               "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward '%%' } }",
+               "bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward -- '%%' } }",
                "bind -Tcopy-mode-vi 0 { send -X start-of-line }",
                "bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }",
                "bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }",
@@ -581,14 +581,14 @@ key_bindings_init(void)
                "bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }",
                "bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }",
                "bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }",
-               "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line '%%' } }",
+               "bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }",
                "bind -Tcopy-mode-vi \\; { send -X jump-again }",
-               "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward '%%' } }",
+               "bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward -- '%%' } }",
                "bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }",
                "bind -Tcopy-mode-vi B { send -X previous-space }",
                "bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }",
                "bind -Tcopy-mode-vi E { send -X next-space-end }",
-               "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward '%%' } }",
+               "bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
                "bind -Tcopy-mode-vi G { send -X history-bottom }",
                "bind -Tcopy-mode-vi H { send -X top-line }",
                "bind -Tcopy-mode-vi J { send -X scroll-down }",
@@ -597,14 +597,14 @@ key_bindings_init(void)
                "bind -Tcopy-mode-vi M { send -X middle-line }",
                "bind -Tcopy-mode-vi N { send -X search-reverse }",
                "bind -Tcopy-mode-vi P { send -X toggle-position }",
-               "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }",
+               "bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
                "bind -Tcopy-mode-vi V { send -X select-line }",
                "bind -Tcopy-mode-vi W { send -X next-space }",
                "bind -Tcopy-mode-vi X { send -X set-mark }",
                "bind -Tcopy-mode-vi ^ { send -X back-to-indentation }",
                "bind -Tcopy-mode-vi b { send -X previous-word }",
                "bind -Tcopy-mode-vi e { send -X next-word-end }",
-               "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward '%%' } }",
+               "bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
                "bind -Tcopy-mode-vi g { send -X history-top }",
                "bind -Tcopy-mode-vi h { send -X cursor-left }",
                "bind -Tcopy-mode-vi j { send -X cursor-down }",
@@ -615,7 +615,7 @@ key_bindings_init(void)
                "bind -Tcopy-mode-vi o { send -X other-end }",
                "bind -Tcopy-mode-vi q { send -X cancel }",
                "bind -Tcopy-mode-vi r { send -X refresh-from-pane }",
-               "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward '%%' } }",
+               "bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
                "bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
                "bind -Tcopy-mode-vi w { send -X next-word }",
                "bind -Tcopy-mode-vi '{' { send -X previous-paragraph }",
index 418621a..8538ca6 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: tmux.1,v 1.958 2024/10/01 10:10:29 nicm Exp $
+.\" $OpenBSD: tmux.1,v 1.959 2024/10/04 07:03:08 nicm Exp $
 .\"
 .\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
 .\"
@@ -14,7 +14,7 @@
 .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: October 1 2024 $
+.Dd $Mdocdate: October 4 2024 $
 .Dt TMUX 1
 .Os
 .Sh NAME
@@ -1801,6 +1801,7 @@ Exit copy mode.
 Clear the current selection.
 .It Xo
 .Ic copy-end-of-line
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Copy from the cursor position to the end of the line.
@@ -1808,11 +1809,13 @@ Copy from the cursor position to the end of the line.
 is used to name the new paste buffer.
 .It Xo
 .Ic copy-end-of-line-and-cancel
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Copy from the cursor position and exit copy mode.
 .It Xo
 .Ic copy-pipe-end-of-line
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1822,6 +1825,7 @@ Copy from the cursor position to the end of the line and pipe the text to
 is used to name the new paste buffer.
 .It Xo
 .Ic copy-pipe-end-of-line-and-cancel
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1830,16 +1834,19 @@ Same as
 but also exit copy mode.
 .It Xo
 .Ic copy-line
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Copy the entire line.
 .It Xo
 .Ic copy-line-and-cancel
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Copy the entire line and exit copy mode.
 .It Xo
 .Ic copy-pipe-line
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1849,6 +1856,7 @@ Copy the entire line and pipe the text to
 is used to name the new paste buffer.
 .It Xo
 .Ic copy-pipe-line-and-cancel
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1857,6 +1865,7 @@ Same as
 but also exit copy mode.
 .It Xo
 .Ic copy-pipe
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1866,6 +1875,7 @@ Copy the selection, clear it and pipe its text to
 is used to name the new paste buffer.
 .It Xo
 .Ic copy-pipe-no-clear
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1874,6 +1884,7 @@ Same as
 but do not clear the selection.
 .It Xo
 .Ic copy-pipe-and-cancel
+.Op Fl CP
 .Op Ar command
 .Op Ar prefix
 .Xc
@@ -1882,11 +1893,13 @@ Same as
 but also exit copy mode.
 .It Xo
 .Ic copy-selection
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Copies the current selection.
 .It Xo
 .Ic copy-selection-no-clear
+.Op Fl CP
 .Op Ar prefix
 .Xc
 Same as
@@ -1894,6 +1907,7 @@ Same as
 but do not clear the selection.
 .It Xo
 .Ic copy-selection-and-cancel
+.Op Fl CP
 .Op Ar prefix
 (vi: Enter)
 (emacs: M-w)
@@ -2341,6 +2355,16 @@ variants of some commands exit copy mode after they have completed (for copy
 commands) or when the cursor reaches the bottom (for scrolling commands).
 .Ql -no-clear
 variants do not clear the selection.
+All the copy commands can take the
+.Fl C
+and
+.Fl P
+flags.
+The
+.Fl C
+flag suppresses setting the terminal clipboard when copying, while the
+.Fl P
+flag suppresses adding a paste buffer with the text.
 .Pp
 The next and previous word keys skip over whitespace and treat consecutive
 runs of either word separators or other letters as words.
index 1e45db5..c06d1f1 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: window-copy.c,v 1.354 2024/10/01 08:01:19 nicm Exp $ */
+/* $OpenBSD: window-copy.c,v 1.355 2024/10/04 07:03:08 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -94,13 +94,14 @@ static int  window_copy_update_selection(struct window_mode_entry *, int,
 static void    window_copy_synchronize_cursor(struct window_mode_entry *, int);
 static void    *window_copy_get_selection(struct window_mode_entry *, size_t *);
 static void    window_copy_copy_buffer(struct window_mode_entry *,
-                   const char *, void *, size_t);
+                   const char *, void *, size_t, int, int);
 static void    window_copy_pipe(struct window_mode_entry *,
                    struct session *, const char *);
 static void    window_copy_copy_pipe(struct window_mode_entry *,
-                   struct session *, const char *, const char *);
+                   struct session *, const char *, const char *,
+                   int, int);
 static void    window_copy_copy_selection(struct window_mode_entry *,
-                   const char *);
+                   const char *, int, int);
 static void    window_copy_append_selection(struct window_mode_entry *);
 static void    window_copy_clear_selection(struct window_mode_entry *);
 static void    window_copy_copy_line(struct window_mode_entry *, char **,
@@ -132,7 +133,7 @@ static void window_copy_cursor_previous_word_pos(struct window_mode_entry *,
 static void    window_copy_cursor_previous_word(struct window_mode_entry *,
                    const char *, int);
 static void    window_copy_cursor_prompt(struct window_mode_entry *, int,
-                   const char *);
+                   int);
 static void    window_copy_scroll_up(struct window_mode_entry *, u_int);
 static void    window_copy_scroll_down(struct window_mode_entry *, u_int);
 static void    window_copy_rectangle_set(struct window_mode_entry *, int);
@@ -198,6 +199,7 @@ enum window_copy_cmd_clear {
 struct window_copy_cmd_state {
        struct window_mode_entry        *wme;
        struct args                     *args;
+       struct args                     *wargs;
        struct mouse_event              *m;
 
        struct client                   *c;
@@ -921,7 +923,7 @@ window_copy_expand_search_string(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
-       const char                      *ss = args_string(cs->args, 1);
+       const char                      *ss = args_string(cs->wargs, 0);
        char                            *expanded;
 
        if (ss == NULL || *ss == '\0')
@@ -1043,21 +1045,23 @@ window_copy_do_copy_end_of_line(struct window_copy_cmd_state *cs, int pipe,
        struct session                   *s = cs->s;
        struct winlink                   *wl = cs->wl;
        struct window_pane               *wp = wme->wp;
-       u_int                             count = args_count(cs->args);
+       u_int                             count = args_count(cs->wargs);
        u_int                             np = wme->prefix, ocx, ocy, ooy;
        struct window_copy_mode_data     *data = wme->data;
        char                             *prefix = NULL, *command = NULL;
-       const char                       *arg1 = args_string(cs->args, 1);
-       const char                       *arg2 = args_string(cs->args, 2);
+       const char                       *arg0 = args_string(cs->wargs, 0);
+       const char                       *arg1 = args_string(cs->wargs, 1);
+       int                               set_paste = !args_has(cs->wargs, 'P');
+       int                               set_clip = !args_has(cs->wargs, 'C');
 
        if (pipe) {
-               if (count == 3)
-                       prefix = format_single(NULL, arg2, c, s, wl, wp);
-               if (s != NULL && count > 1 && *arg1 != '\0')
-                       command = format_single(NULL, arg1, c, s, wl, wp);
-       } else {
                if (count == 2)
                        prefix = format_single(NULL, arg1, c, s, wl, wp);
+               if (s != NULL && count > 0 && *arg0 != '\0')
+                       command = format_single(NULL, arg0, c, s, wl, wp);
+       } else {
+               if (count == 1)
+                       prefix = format_single(NULL, arg0, c, s, wl, wp);
        }
 
        ocx = data->cx;
@@ -1071,9 +1075,11 @@ window_copy_do_copy_end_of_line(struct window_copy_cmd_state *cs, int pipe,
 
        if (s != NULL) {
                if (pipe)
-                       window_copy_copy_pipe(wme, s, prefix, command);
+                       window_copy_copy_pipe(wme, s, prefix, command,
+                           set_paste, set_clip);
                else
-                       window_copy_copy_selection(wme, prefix);
+                       window_copy_copy_selection(wme, prefix,
+                           set_paste, set_clip);
 
                if (cancel) {
                        free(prefix);
@@ -1126,20 +1132,22 @@ window_copy_do_copy_line(struct window_copy_cmd_state *cs, int pipe, int cancel)
        struct winlink                   *wl = cs->wl;
        struct window_pane               *wp = wme->wp;
        struct window_copy_mode_data     *data = wme->data;
-       u_int                             count = args_count(cs->args);
+       u_int                             count = args_count(cs->wargs);
        u_int                             np = wme->prefix, ocx, ocy, ooy;
        char                             *prefix = NULL, *command = NULL;
-       const char                       *arg1 = args_string(cs->args, 1);
-       const char                       *arg2 = args_string(cs->args, 2);
+       const char                       *arg0 = args_string(cs->wargs, 0);
+       const char                       *arg1 = args_string(cs->wargs, 1);
+       int                               set_paste = !args_has(cs->wargs, 'P');
+       int                               set_clip = !args_has(cs->wargs, 'C');
 
        if (pipe) {
-               if (count == 3)
-                       prefix = format_single(NULL, arg2, c, s, wl, wp);
-               if (s != NULL && count > 1 && *arg1 != '\0')
-                       command = format_single(NULL, arg1, c, s, wl, wp);
-       } else {
                if (count == 2)
                        prefix = format_single(NULL, arg1, c, s, wl, wp);
+               if (s != NULL && count > 0 && *arg0 != '\0')
+                       command = format_single(NULL, arg0, c, s, wl, wp);
+       } else {
+               if (count == 1)
+                       prefix = format_single(NULL, arg0, c, s, wl, wp);
        }
 
        ocx = data->cx;
@@ -1155,9 +1163,11 @@ window_copy_do_copy_line(struct window_copy_cmd_state *cs, int pipe, int cancel)
 
        if (s != NULL) {
                if (pipe)
-                       window_copy_copy_pipe(wme, s, prefix, command);
+                       window_copy_copy_pipe(wme, s, prefix, command,
+                           set_paste, set_clip);
                else
-                       window_copy_copy_selection(wme, prefix);
+                       window_copy_copy_selection(wme, prefix,
+                           set_paste, set_clip);
 
                if (cancel) {
                        free(prefix);
@@ -1209,13 +1219,15 @@ window_copy_cmd_copy_selection_no_clear(struct window_copy_cmd_state *cs)
        struct winlink                  *wl = cs->wl;
        struct window_pane              *wp = wme->wp;
        char                            *prefix = NULL;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
+       int                              set_paste = !args_has(cs->wargs, 'P');
+       int                              set_clip = !args_has(cs->wargs, 'C');
 
-       if (arg1 != NULL)
-               prefix = format_single(NULL, arg1, c, s, wl, wp);
+       if (arg0 != NULL)
+               prefix = format_single(NULL, arg0, c, s, wl, wp);
 
        if (s != NULL)
-               window_copy_copy_selection(wme, prefix);
+               window_copy_copy_selection(wme, prefix, set_paste, set_clip);
 
        free(prefix);
        return (WINDOW_COPY_CMD_NOTHING);
@@ -2116,15 +2128,18 @@ window_copy_cmd_copy_pipe_no_clear(struct window_copy_cmd_state *cs)
        struct winlink                  *wl = cs->wl;
        struct window_pane              *wp = wme->wp;
        char                            *command = NULL, *prefix = NULL;
-       const char                      *arg1 = args_string(cs->args, 1);
-       const char                      *arg2 = args_string(cs->args, 2);
+       const char                      *arg0 = args_string(cs->wargs, 0);
+       const char                      *arg1 = args_string(cs->wargs, 1);
+       int                              set_paste = !args_has(cs->wargs, 'P');
+       int                              set_clip = !args_has(cs->wargs, 'C');
 
-       if (arg2 != NULL)
-               prefix = format_single(NULL, arg2, c, s, wl, wp);
+       if (arg1 != NULL)
+               prefix = format_single(NULL, arg1, c, s, wl, wp);
 
-       if (s != NULL && arg1 != NULL && *arg1 != '\0')
-               command = format_single(NULL, arg1, c, s, wl, wp);
-       window_copy_copy_pipe(wme, s, prefix, command);
+       if (s != NULL && arg0 != NULL && *arg0 != '\0')
+               command = format_single(NULL, arg0, c, s, wl, wp);
+       window_copy_copy_pipe(wme, s, prefix, command,
+           set_paste, set_clip);
        free(command);
 
        free(prefix);
@@ -2160,10 +2175,10 @@ window_copy_cmd_pipe_no_clear(struct window_copy_cmd_state *cs)
        struct winlink                  *wl = cs->wl;
        struct window_pane              *wp = wme->wp;
        char                            *command = NULL;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (s != NULL && arg1 != NULL && *arg1 != '\0')
-               command = format_single(NULL, arg1, c, s, wl, wp);
+       if (s != NULL && arg0 != NULL && *arg0 != '\0')
+               command = format_single(NULL, arg0, c, s, wl, wp);
        window_copy_pipe(wme, s, command);
        free(command);
 
@@ -2194,10 +2209,10 @@ static enum window_copy_cmd_action
 window_copy_cmd_goto_line(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (*arg1 != '\0')
-               window_copy_goto_line(wme, arg1);
+       if (*arg0 != '\0')
+               window_copy_goto_line(wme, arg0);
        return (WINDOW_COPY_CMD_NOTHING);
 }
 
@@ -2207,12 +2222,12 @@ window_copy_cmd_jump_backward(struct window_copy_cmd_state *cs)
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
        u_int                            np = wme->prefix;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (*arg1 != '\0') {
+       if (*arg0 != '\0') {
                data->jumptype = WINDOW_COPY_JUMPBACKWARD;
                free(data->jumpchar);
-               data->jumpchar = utf8_fromcstr(arg1);
+               data->jumpchar = utf8_fromcstr(arg0);
                for (; np != 0; np--)
                        window_copy_cursor_jump_back(wme);
        }
@@ -2225,12 +2240,12 @@ window_copy_cmd_jump_forward(struct window_copy_cmd_state *cs)
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
        u_int                            np = wme->prefix;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (*arg1 != '\0') {
+       if (*arg0 != '\0') {
                data->jumptype = WINDOW_COPY_JUMPFORWARD;
                free(data->jumpchar);
-               data->jumpchar = utf8_fromcstr(arg1);
+               data->jumpchar = utf8_fromcstr(arg0);
                for (; np != 0; np--)
                        window_copy_cursor_jump(wme);
        }
@@ -2243,12 +2258,12 @@ window_copy_cmd_jump_to_backward(struct window_copy_cmd_state *cs)
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
        u_int                            np = wme->prefix;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (*arg1 != '\0') {
+       if (*arg0 != '\0') {
                data->jumptype = WINDOW_COPY_JUMPTOBACKWARD;
                free(data->jumpchar);
-               data->jumpchar = utf8_fromcstr(arg1);
+               data->jumpchar = utf8_fromcstr(arg0);
                for (; np != 0; np--)
                        window_copy_cursor_jump_to_back(wme);
        }
@@ -2261,12 +2276,12 @@ window_copy_cmd_jump_to_forward(struct window_copy_cmd_state *cs)
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
        u_int                            np = wme->prefix;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
 
-       if (*arg1 != '\0') {
+       if (*arg0 != '\0') {
                data->jumptype = WINDOW_COPY_JUMPTOFORWARD;
                free(data->jumpchar);
-               data->jumpchar = utf8_fromcstr(arg1);
+               data->jumpchar = utf8_fromcstr(arg0);
                for (; np != 0; np--)
                        window_copy_cursor_jump_to(wme);
        }
@@ -2286,9 +2301,8 @@ static enum window_copy_cmd_action
 window_copy_cmd_next_prompt(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
-       const char                      *arg1 = args_string(cs->args, 1);
 
-       window_copy_cursor_prompt(wme, 1, arg1);
+       window_copy_cursor_prompt(wme, 1, args_has(cs->wargs, 'o'));
        return (WINDOW_COPY_CMD_NOTHING);
 }
 
@@ -2296,9 +2310,8 @@ static enum window_copy_cmd_action
 window_copy_cmd_previous_prompt(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
-       const char                      *arg1 = args_string(cs->args, 1);
 
-       window_copy_cursor_prompt(wme, 0, arg1);
+       window_copy_cursor_prompt(wme, 0, args_has(cs->wargs, 'o'));
        return (WINDOW_COPY_CMD_NOTHING);
 }
 
@@ -2387,27 +2400,27 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
        const char                      *ss = data->searchstr;
        char                             prefix;
        enum window_copy_cmd_action      action = WINDOW_COPY_CMD_NOTHING;
 
        data->timeout = 0;
 
-       log_debug("%s: %s", __func__, arg1);
+       log_debug("%s: %s", __func__, arg0);
 
-       prefix = *arg1++;
+       prefix = *arg0++;
        if (data->searchx == -1 || data->searchy == -1) {
                data->searchx = data->cx;
                data->searchy = data->cy;
                data->searcho = data->oy;
-       } else if (ss != NULL && strcmp(arg1, ss) != 0) {
+       } else if (ss != NULL && strcmp(arg0, ss) != 0) {
                data->cx = data->searchx;
                data->cy = data->searchy;
                data->oy = data->searcho;
                action = WINDOW_COPY_CMD_REDRAW;
        }
-       if (*arg1 == '\0') {
+       if (*arg0 == '\0') {
                window_copy_clear_marks(wme);
                return (WINDOW_COPY_CMD_REDRAW);
        }
@@ -2417,7 +2430,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
                data->searchtype = WINDOW_COPY_SEARCHUP;
                data->searchregex = 0;
                free(data->searchstr);
-               data->searchstr = xstrdup(arg1);
+               data->searchstr = xstrdup(arg0);
                if (!window_copy_search_up(wme, 0)) {
                        window_copy_clear_marks(wme);
                        return (WINDOW_COPY_CMD_REDRAW);
@@ -2427,7 +2440,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
                data->searchtype = WINDOW_COPY_SEARCHDOWN;
                data->searchregex = 0;
                free(data->searchstr);
-               data->searchstr = xstrdup(arg1);
+               data->searchstr = xstrdup(arg0);
                if (!window_copy_search_down(wme, 0)) {
                        window_copy_clear_marks(wme);
                        return (WINDOW_COPY_CMD_REDRAW);
@@ -2442,27 +2455,27 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
 {
        struct window_mode_entry        *wme = cs->wme;
        struct window_copy_mode_data    *data = wme->data;
-       const char                      *arg1 = args_string(cs->args, 1);
+       const char                      *arg0 = args_string(cs->wargs, 0);
        const char                      *ss = data->searchstr;
        char                             prefix;
        enum window_copy_cmd_action      action = WINDOW_COPY_CMD_NOTHING;
 
        data->timeout = 0;
 
-       log_debug("%s: %s", __func__, arg1);
+       log_debug("%s: %s", __func__, arg0);
 
-       prefix = *arg1++;
+       prefix = *arg0++;
        if (data->searchx == -1 || data->searchy == -1) {
                data->searchx = data->cx;
                data->searchy = data->cy;
                data->searcho = data->oy;
-       } else if (ss != NULL && strcmp(arg1, ss) != 0) {
+       } else if (ss != NULL && strcmp(arg0, ss) != 0) {
                data->cx = data->searchx;
                data->cy = data->searchy;
                data->oy = data->searcho;
                action = WINDOW_COPY_CMD_REDRAW;
        }
-       if (*arg1 == '\0') {
+       if (*arg0 == '\0') {
                window_copy_clear_marks(wme);
                return (WINDOW_COPY_CMD_REDRAW);
        }
@@ -2472,7 +2485,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
                data->searchtype = WINDOW_COPY_SEARCHDOWN;
                data->searchregex = 0;
                free(data->searchstr);
-               data->searchstr = xstrdup(arg1);
+               data->searchstr = xstrdup(arg0);
                if (!window_copy_search_down(wme, 0)) {
                        window_copy_clear_marks(wme);
                        return (WINDOW_COPY_CMD_REDRAW);
@@ -2482,7 +2495,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
                data->searchtype = WINDOW_COPY_SEARCHUP;
                data->searchregex = 0;
                free(data->searchstr);
-               data->searchstr = xstrdup(arg1);
+               data->searchstr = xstrdup(arg0);
                if (!window_copy_search_up(wme, 0)) {
                        window_copy_clear_marks(wme);
                        return (WINDOW_COPY_CMD_REDRAW);
@@ -2514,516 +2527,432 @@ static const struct {
        const char                       *command;
        u_int                             minargs;
        u_int                             maxargs;
+       struct args_parse                 args;
        enum window_copy_cmd_clear        clear;
        enum window_copy_cmd_action     (*f)(struct window_copy_cmd_state *);
 } window_copy_cmd_table[] = {
        { .command = "append-selection",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_append_selection
        },
        { .command = "append-selection-and-cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_append_selection_and_cancel
        },
        { .command = "back-to-indentation",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_back_to_indentation
        },
        { .command = "begin-selection",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_begin_selection
        },
        { .command = "bottom-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_bottom_line
        },
        { .command = "cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_cancel
        },
        { .command = "clear-selection",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_clear_selection
        },
        { .command = "copy-end-of-line",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_end_of_line
        },
        { .command = "copy-end-of-line-and-cancel",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_end_of_line_and_cancel
        },
        { .command = "copy-pipe-end-of-line",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe_end_of_line
        },
        { .command = "copy-pipe-end-of-line-and-cancel",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel
        },
        { .command = "copy-line",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_line
        },
        { .command = "copy-line-and-cancel",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_line_and_cancel
        },
        { .command = "copy-pipe-line",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe_line
        },
        { .command = "copy-pipe-line-and-cancel",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe_line_and_cancel
        },
        { .command = "copy-pipe-no-clear",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
          .f = window_copy_cmd_copy_pipe_no_clear
        },
        { .command = "copy-pipe",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe
        },
        { .command = "copy-pipe-and-cancel",
-         .minargs = 0,
-         .maxargs = 2,
+         .args = { "CP", 0, 2, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_pipe_and_cancel
        },
        { .command = "copy-selection-no-clear",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
          .f = window_copy_cmd_copy_selection_no_clear
        },
        { .command = "copy-selection",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_selection
        },
        { .command = "copy-selection-and-cancel",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "CP", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_copy_selection_and_cancel
        },
        { .command = "cursor-down",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_cursor_down
        },
        { .command = "cursor-down-and-cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_cursor_down_and_cancel
        },
        { .command = "cursor-left",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_cursor_left
        },
        { .command = "cursor-right",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_cursor_right
        },
        { .command = "cursor-up",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_cursor_up
        },
        { .command = "end-of-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_end_of_line
        },
        { .command = "goto-line",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_goto_line
        },
        { .command = "halfpage-down",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_halfpage_down
        },
        { .command = "halfpage-down-and-cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_halfpage_down_and_cancel
        },
        { .command = "halfpage-up",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_halfpage_up
        },
        { .command = "history-bottom",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_history_bottom
        },
        { .command = "history-top",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_history_top
        },
        { .command = "jump-again",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_again
        },
        { .command = "jump-backward",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_backward
        },
        { .command = "jump-forward",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_forward
        },
        { .command = "jump-reverse",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_reverse
        },
        { .command = "jump-to-backward",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_to_backward
        },
        { .command = "jump-to-forward",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_jump_to_forward
        },
        { .command = "jump-to-mark",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_jump_to_mark
        },
        { .command = "next-prompt",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "o", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_next_prompt
        },
        { .command = "previous-prompt",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "o", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_previous_prompt
        },
        { .command = "middle-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_middle_line
        },
        { .command = "next-matching-bracket",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_next_matching_bracket
        },
        { .command = "next-paragraph",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_next_paragraph
        },
        { .command = "next-space",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_next_space
        },
        { .command = "next-space-end",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_next_space_end
        },
        { .command = "next-word",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_next_word
        },
        { .command = "next-word-end",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_next_word_end
        },
        { .command = "other-end",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_other_end
        },
        { .command = "page-down",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_page_down
        },
        { .command = "page-down-and-cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_page_down_and_cancel
        },
        { .command = "page-up",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_page_up
        },
        { .command = "pipe-no-clear",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
          .f = window_copy_cmd_pipe_no_clear
        },
        { .command = "pipe",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_pipe
        },
        { .command = "pipe-and-cancel",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_pipe_and_cancel
        },
        { .command = "previous-matching-bracket",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_previous_matching_bracket
        },
        { .command = "previous-paragraph",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_previous_paragraph
        },
        { .command = "previous-space",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_previous_space
        },
        { .command = "previous-word",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_previous_word
        },
        { .command = "rectangle-on",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_rectangle_on
        },
        { .command = "rectangle-off",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_rectangle_off
        },
        { .command = "rectangle-toggle",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_rectangle_toggle
        },
        { .command = "refresh-from-pane",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_refresh_from_pane
        },
        { .command = "scroll-bottom",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_scroll_bottom
        },
        { .command = "scroll-down",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_scroll_down
        },
        { .command = "scroll-down-and-cancel",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_scroll_down_and_cancel
        },
        { .command = "scroll-middle",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_scroll_middle
        },
        { .command = "scroll-top",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_scroll_top
        },
        { .command = "scroll-up",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_scroll_up
        },
        { .command = "search-again",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_again
        },
        { .command = "search-backward",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_backward
        },
        { .command = "search-backward-text",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_backward_text
        },
        { .command = "search-backward-incremental",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_backward_incremental
        },
        { .command = "search-forward",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_forward
        },
        { .command = "search-forward-text",
-         .minargs = 0,
-         .maxargs = 1,
+         .args = { "", 0, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_forward_text
        },
        { .command = "search-forward-incremental",
-         .minargs = 1,
-         .maxargs = 1,
+         .args = { "", 1, 1, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_forward_incremental
        },
        { .command = "search-reverse",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_search_reverse
        },
        { .command = "select-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_select_line
        },
        { .command = "select-word",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_select_word
        },
        { .command = "set-mark",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_set_mark
        },
        { .command = "start-of-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_start_of_line
        },
        { .command = "stop-selection",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
          .f = window_copy_cmd_stop_selection
        },
        { .command = "toggle-position",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
          .f = window_copy_cmd_toggle_position
        },
        { .command = "top-line",
-         .minargs = 0,
-         .maxargs = 0,
+         .args = { "", 0, 0, NULL },
          .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
          .f = window_copy_cmd_top_line
        }
@@ -3035,12 +2964,14 @@ window_copy_command(struct window_mode_entry *wme, struct client *c,
     struct mouse_event *m)
 {
        struct window_copy_mode_data    *data = wme->data;
+       struct window_pane              *wp = wme->wp;
        struct window_copy_cmd_state     cs;
        enum window_copy_cmd_action      action;
        enum window_copy_cmd_clear       clear = WINDOW_COPY_CMD_CLEAR_NEVER;
        const char                      *command;
        u_int                            i, count = args_count(args);
        int                              keys;
+       char                            *error = NULL;
 
        if (count == 0)
                return;
@@ -3051,6 +2982,7 @@ window_copy_command(struct window_mode_entry *wme, struct client *c,
 
        cs.wme = wme;
        cs.args = args;
+       cs.wargs = NULL;
        cs.m = m;
 
        cs.c = c;
@@ -3060,17 +2992,26 @@ window_copy_command(struct window_mode_entry *wme, struct client *c,
        action = WINDOW_COPY_CMD_NOTHING;
        for (i = 0; i < nitems(window_copy_cmd_table); i++) {
                if (strcmp(window_copy_cmd_table[i].command, command) == 0) {
-                       if (count - 1 < window_copy_cmd_table[i].minargs ||
-                           count - 1 > window_copy_cmd_table[i].maxargs)
+                       cs.wargs = args_parse(&window_copy_cmd_table[i].args,
+                           args_values(args), count, &error);
+
+                       if (error != NULL) {
+                               free(error);
+                               error = NULL;
+                       }
+                       if (cs.wargs == NULL)
                                break;
+
                        clear = window_copy_cmd_table[i].clear;
                        action = window_copy_cmd_table[i].f(&cs);
+                       args_free(cs.wargs);
+                       cs.wargs = NULL;
                        break;
                }
        }
 
        if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) {
-               keys = options_get_number(wme->wp->window->options, "mode-keys");
+               keys = options_get_number(wp->window->options, "mode-keys");
                if (clear == WINDOW_COPY_CMD_CLEAR_EMACS_ONLY &&
                    keys == MODEKEY_VI)
                        clear = WINDOW_COPY_CMD_CLEAR_NEVER;
@@ -3084,7 +3025,7 @@ window_copy_command(struct window_mode_entry *wme, struct client *c,
        wme->prefix = 1;
 
        if (action == WINDOW_COPY_CMD_CANCEL)
-               window_pane_reset_mode(wme->wp);
+               window_pane_reset_mode(wp);
        else if (action == WINDOW_COPY_CMD_REDRAW)
                window_copy_redraw_screen(wme);
 }
@@ -4727,19 +4668,20 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
 
 static void
 window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
-    void *buf, size_t len)
+    void *buf, size_t len, int set_paste, int set_clip)
 {
        struct window_pane      *wp = wme->wp;
        struct screen_write_ctx  ctx;
 
-       if (options_get_number(global_options, "set-clipboard") != 0) {
+       if (set_clip && options_get_number(global_options, "set-clipboard") != 0) {
                screen_write_start_pane(&ctx, wp, NULL);
                screen_write_setselection(&ctx, "", buf, len);
                screen_write_stop(&ctx);
                notify_pane("pane-set-clipboard", wp);
        }
 
-       paste_add(prefix, buf, len);
+       if (set_paste)
+               paste_add(prefix, buf, len);
 }
 
 static void *
@@ -4771,25 +4713,28 @@ window_copy_pipe(struct window_mode_entry *wme, struct session *s,
 
 static void
 window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s,
-    const char *prefix, const char *cmd)
+    const char *prefix, const char *cmd, int set_paste, int set_clip)
 {
        void    *buf;
        size_t   len;
 
        buf = window_copy_pipe_run(wme, s, cmd, &len);
        if (buf != NULL)
-               window_copy_copy_buffer(wme, prefix, buf, len);
+               window_copy_copy_buffer(wme, prefix, buf, len, set_paste,
+                   set_clip);
 }
 
 static void
-window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix)
+window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix,
+    int set_paste, int set_clip)
 {
        char    *buf;
        size_t   len;
 
        buf = window_copy_get_selection(wme, &len);
        if (buf != NULL)
-               window_copy_copy_buffer(wme, prefix, buf, len);
+               window_copy_copy_buffer(wme, prefix, buf, len, set_paste,
+                   set_clip);
 }
 
 static void
@@ -5436,7 +5381,7 @@ window_copy_cursor_previous_word(struct window_mode_entry *wme,
 
 static void
 window_copy_cursor_prompt(struct window_mode_entry *wme, int direction,
-    const char *args)
+    int start_output)
 {
        struct window_copy_mode_data    *data = wme->data;
        struct screen                   *s = data->backing;
@@ -5445,7 +5390,7 @@ window_copy_cursor_prompt(struct window_mode_entry *wme, int direction,
        u_int                            line = gd->hsize - data->oy + data->cy;
        int                              add, line_flag;
 
-       if (args != NULL && strcmp(args, "-o") == 0)
+       if (start_output)
                line_flag = GRID_LINE_START_OUTPUT;
        else
                line_flag = GRID_LINE_START_PROMPT;