[BACK]Return to window-copy.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tmux

File: [local] / src / usr.bin / tmux / window-copy.c (download)

Revision 1.343, Fri Sep 1 14:29:11 2023 UTC (9 months, 1 week ago) by nicm
Branch: MAIN
Changes since 1.342: +3 -5 lines

Rewrite combined character handling to be more consistent and to support
newer Unicode combined characters (which we have to "know" are combined
since they are not width zero). GitHub issue 3600.

/* $OpenBSD: window-copy.c,v 1.343 2023/09/01 14:29:11 nicm Exp $ */

/*
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <ctype.h>
#include <regex.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "tmux.h"

struct window_copy_mode_data;

static const char *window_copy_key_table(struct window_mode_entry *);
static void	window_copy_command(struct window_mode_entry *, struct client *,
		    struct session *, struct winlink *, struct args *,
		    struct mouse_event *);
static struct screen *window_copy_init(struct window_mode_entry *,
		    struct cmd_find_state *, struct args *);
static struct screen *window_copy_view_init(struct window_mode_entry *,
		    struct cmd_find_state *, struct args *);
static void	window_copy_free(struct window_mode_entry *);
static void	window_copy_resize(struct window_mode_entry *, u_int, u_int);
static void	window_copy_formats(struct window_mode_entry *,
		    struct format_tree *);
static void	window_copy_pageup1(struct window_mode_entry *, int);
static int	window_copy_pagedown(struct window_mode_entry *, int, int);
static void	window_copy_next_paragraph(struct window_mode_entry *);
static void	window_copy_previous_paragraph(struct window_mode_entry *);
static void	window_copy_redraw_selection(struct window_mode_entry *, u_int);
static void	window_copy_redraw_lines(struct window_mode_entry *, u_int,
		    u_int);
static void	window_copy_redraw_screen(struct window_mode_entry *);
static void	window_copy_write_line(struct window_mode_entry *,
		    struct screen_write_ctx *, u_int);
static void	window_copy_write_lines(struct window_mode_entry *,
		    struct screen_write_ctx *, u_int, u_int);
static char    *window_copy_match_at_cursor(struct window_copy_mode_data *);
static void	window_copy_scroll_to(struct window_mode_entry *, u_int, u_int,
		    int);
static int	window_copy_search_compare(struct grid *, u_int, u_int,
		    struct grid *, u_int, int);
static int	window_copy_search_lr(struct grid *, struct grid *, u_int *,
		    u_int, u_int, u_int, int);
static int	window_copy_search_rl(struct grid *, struct grid *, u_int *,
		    u_int, u_int, u_int, int);
static int	window_copy_last_regex(struct grid *, u_int, u_int, u_int,
		    u_int, u_int *, u_int *, const char *, const regex_t *,
		    int);
static int	window_copy_search_mark_at(struct window_copy_mode_data *,
		    u_int, u_int, u_int *);
static char    *window_copy_stringify(struct grid *, u_int, u_int, u_int,
		    char *, u_int *);
static void	window_copy_cstrtocellpos(struct grid *, u_int, u_int *,
		    u_int *, const char *);
static int	window_copy_search_marks(struct window_mode_entry *,
		    struct screen *, int, int);
static void	window_copy_clear_marks(struct window_mode_entry *);
static int	window_copy_is_lowercase(const char *);
static void	window_copy_search_back_overlap(struct grid *, regex_t *,
		    u_int *, u_int *, u_int *, u_int);
static int	window_copy_search_jump(struct window_mode_entry *,
		    struct grid *, struct grid *, u_int, u_int, u_int, int, int,
		    int, int);
static int	window_copy_search(struct window_mode_entry *, int, int);
static int	window_copy_search_up(struct window_mode_entry *, int);
static int	window_copy_search_down(struct window_mode_entry *, int);
static void	window_copy_goto_line(struct window_mode_entry *, const char *);
static void	window_copy_update_cursor(struct window_mode_entry *, u_int,
		    u_int);
static void	window_copy_start_selection(struct window_mode_entry *);
static int	window_copy_adjust_selection(struct window_mode_entry *,
		    u_int *, u_int *);
static int	window_copy_set_selection(struct window_mode_entry *, int, int);
static int	window_copy_update_selection(struct window_mode_entry *, int,
		    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);
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 *);
static void	window_copy_copy_selection(struct window_mode_entry *,
		    const char *);
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 **,
		    size_t *, u_int, u_int, u_int);
static int	window_copy_in_set(struct window_mode_entry *, u_int, u_int,
		    const char *);
static u_int	window_copy_find_length(struct window_mode_entry *, u_int);
static void	window_copy_cursor_start_of_line(struct window_mode_entry *);
static void	window_copy_cursor_back_to_indentation(
		    struct window_mode_entry *);
static void	window_copy_cursor_end_of_line(struct window_mode_entry *);
static void	window_copy_other_end(struct window_mode_entry *);
static void	window_copy_cursor_left(struct window_mode_entry *);
static void	window_copy_cursor_right(struct window_mode_entry *, int);
static void	window_copy_cursor_up(struct window_mode_entry *, int);
static void	window_copy_cursor_down(struct window_mode_entry *, int);
static void	window_copy_cursor_jump(struct window_mode_entry *);
static void	window_copy_cursor_jump_back(struct window_mode_entry *);
static void	window_copy_cursor_jump_to(struct window_mode_entry *);
static void	window_copy_cursor_jump_to_back(struct window_mode_entry *);
static void	window_copy_cursor_next_word(struct window_mode_entry *,
		    const char *);
static void	window_copy_cursor_next_word_end_pos(struct window_mode_entry *,
		    const char *, u_int *, u_int *);
static void	window_copy_cursor_next_word_end(struct window_mode_entry *,
		    const char *, int);
static void	window_copy_cursor_previous_word_pos(struct window_mode_entry *,
		    const char *, u_int *, u_int *);
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 *);
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);
static void	window_copy_move_mouse(struct mouse_event *);
static void	window_copy_drag_update(struct client *, struct mouse_event *);
static void	window_copy_drag_release(struct client *, struct mouse_event *);
static void	window_copy_jump_to_mark(struct window_mode_entry *);
static void	window_copy_acquire_cursor_up(struct window_mode_entry *,
		    u_int, u_int, u_int, u_int, u_int);
static void	window_copy_acquire_cursor_down(struct window_mode_entry *,
		    u_int, u_int, u_int, u_int, u_int, u_int, int);

const struct window_mode window_copy_mode = {
	.name = "copy-mode",

	.init = window_copy_init,
	.free = window_copy_free,
	.resize = window_copy_resize,
	.key_table = window_copy_key_table,
	.command = window_copy_command,
	.formats = window_copy_formats,
};

const struct window_mode window_view_mode = {
	.name = "view-mode",

	.init = window_copy_view_init,
	.free = window_copy_free,
	.resize = window_copy_resize,
	.key_table = window_copy_key_table,
	.command = window_copy_command,
	.formats = window_copy_formats,
};

enum {
	WINDOW_COPY_OFF,
	WINDOW_COPY_SEARCHUP,
	WINDOW_COPY_SEARCHDOWN,
	WINDOW_COPY_JUMPFORWARD,
	WINDOW_COPY_JUMPBACKWARD,
	WINDOW_COPY_JUMPTOFORWARD,
	WINDOW_COPY_JUMPTOBACKWARD,
};

enum {
	WINDOW_COPY_REL_POS_ABOVE,
	WINDOW_COPY_REL_POS_ON_SCREEN,
	WINDOW_COPY_REL_POS_BELOW,
};

enum window_copy_cmd_action {
	WINDOW_COPY_CMD_NOTHING,
	WINDOW_COPY_CMD_REDRAW,
	WINDOW_COPY_CMD_CANCEL,
};

enum window_copy_cmd_clear {
	WINDOW_COPY_CMD_CLEAR_ALWAYS,
	WINDOW_COPY_CMD_CLEAR_NEVER,
	WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
};

struct window_copy_cmd_state {
	struct window_mode_entry	*wme;
	struct args			*args;
	struct mouse_event		*m;

	struct client			*c;
	struct session			*s;
	struct winlink			*wl;
};

/*
 * Copy mode's visible screen (the "screen" field) is filled from one of two
 * sources: the original contents of the pane (used when we actually enter via
 * the "copy-mode" command, to copy the contents of the current pane), or else
 * a series of lines containing the output from an output-writing tmux command
 * (such as any of the "show-*" or "list-*" commands).
 *
 * In either case, the full content of the copy-mode grid is pointed at by the
 * "backing" field, and is copied into "screen" as needed (that is, when
 * scrolling occurs). When copy-mode is backed by a pane, backing points
 * directly at that pane's screen structure (&wp->base); when backed by a list
 * of output-lines from a command, it points at a newly-allocated screen
 * structure (which is deallocated when the mode ends).
 */
struct window_copy_mode_data {
	struct screen	 screen;

	struct screen	*backing;
	int		 backing_written; /* backing display started */
	struct screen	*writing;
	struct input_ctx *ictx;

	int		 viewmode;	/* view mode entered */

	u_int		 oy;		/* number of lines scrolled up */

	u_int		 selx;		/* beginning of selection */
	u_int		 sely;

	u_int		 endselx;	/* end of selection */
	u_int		 endsely;

	enum {
		CURSORDRAG_NONE,	/* selection is independent of cursor */
		CURSORDRAG_ENDSEL,	/* end is synchronized with cursor */
		CURSORDRAG_SEL,		/* start is synchronized with cursor */
	} cursordrag;

	int		 modekeys;
	enum {
		LINE_SEL_NONE,
		LINE_SEL_LEFT_RIGHT,
		LINE_SEL_RIGHT_LEFT,
	} lineflag;			/* line selection mode */
	int		 rectflag;	/* in rectangle copy mode? */
	int		 scroll_exit;	/* exit on scroll to end? */
	int		 hide_position;	/* hide position marker */

	enum {
		SEL_CHAR,		/* select one char at a time */
		SEL_WORD,		/* select one word at a time */
		SEL_LINE,		/* select one line at a time */
	} selflag;

	const char	*separators;	/* word separators */

	u_int		 dx;		/* drag start position */
	u_int		 dy;

	u_int		 selrx;		/* selection reset positions */
	u_int		 selry;
	u_int		 endselrx;
	u_int		 endselry;

	u_int		 cx;
	u_int		 cy;

	u_int		 lastcx; 	/* position in last line w/ content */
	u_int		 lastsx;	/* size of last line w/ content */

	u_int		 mx;		/* mark position */
	u_int		 my;
	int		 showmark;

	int		 searchtype;
	int		 searchdirection;
	int		 searchregex;
	char		*searchstr;
	u_char		*searchmark;
	int		 searchcount;
	int		 searchmore;
	int		 searchall;
	int		 searchx;
	int		 searchy;
	int		 searcho;
	u_char		 searchgen;

	int		 timeout;	/* search has timed out */
#define WINDOW_COPY_SEARCH_TIMEOUT 10000
#define WINDOW_COPY_SEARCH_ALL_TIMEOUT 200

	int			 jumptype;
	struct utf8_data	*jumpchar;

	struct event	 dragtimer;
#define WINDOW_COPY_DRAG_REPEAT_TIME 50000
};

static void
window_copy_scroll_timer(__unused int fd, __unused short events, void *arg)
{
	struct window_mode_entry	*wme = arg;
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct timeval			 tv = {
		.tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME
	};

	evtimer_del(&data->dragtimer);

	if (TAILQ_FIRST(&wp->modes) != wme)
		return;

	if (data->cy == 0) {
		evtimer_add(&data->dragtimer, &tv);
		window_copy_cursor_up(wme, 1);
	} else if (data->cy == screen_size_y(&data->screen) - 1) {
		evtimer_add(&data->dragtimer, &tv);
		window_copy_cursor_down(wme, 1);
	}
}

static struct screen *
window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx,
    u_int *cy, int trim)
{
	struct screen		*dst;
	const struct grid_line	*gl;
	u_int			 sy, wx, wy;
	int			 reflow;

	dst = xcalloc(1, sizeof *dst);

	sy = screen_hsize(src) + screen_size_y(src);
	if (trim) {
		while (sy > screen_hsize(src)) {
			gl = grid_peek_line(src->grid, sy - 1);
			if (gl->cellused != 0)
				break;
			sy--;
		}
	}
	log_debug("%s: target screen is %ux%u, source %ux%u", __func__,
	    screen_size_x(src), sy, screen_size_x(hint),
	    screen_hsize(src) + screen_size_y(src));
	screen_init(dst, screen_size_x(src), sy, screen_hlimit(src));

	/*
	 * Ensure history is on for the backing grid so lines are not deleted
	 * during resizing.
	 */
	dst->grid->flags |= GRID_HISTORY;
	grid_duplicate_lines(dst->grid, 0, src->grid, 0, sy);

	dst->grid->sy = sy - screen_hsize(src);
	dst->grid->hsize = screen_hsize(src);
	dst->grid->hscrolled = src->grid->hscrolled;
	if (src->cy > dst->grid->sy - 1) {
		dst->cx = 0;
		dst->cy = dst->grid->sy - 1;
	} else {
		dst->cx = src->cx;
		dst->cy = src->cy;
	}

	if (cx != NULL && cy != NULL) {
		*cx = dst->cx;
		*cy = screen_hsize(dst) + dst->cy;
		reflow = (screen_size_x(hint) != screen_size_x(dst));
	}
	else
		reflow = 0;
	if (reflow)
		grid_wrap_position(dst->grid, *cx, *cy, &wx, &wy);
	screen_resize_cursor(dst, screen_size_x(hint), screen_size_y(hint), 1,
	    0, 0);
	if (reflow)
		grid_unwrap_position(dst->grid, cx, cy, wx, wy);

	return (dst);
}

static struct window_copy_mode_data *
window_copy_common_init(struct window_mode_entry *wme)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data;
	struct screen			*base = &wp->base;

	wme->data = data = xcalloc(1, sizeof *data);

	data->cursordrag = CURSORDRAG_NONE;
	data->lineflag = LINE_SEL_NONE;
	data->selflag = SEL_CHAR;

	if (wp->searchstr != NULL) {
		data->searchtype = WINDOW_COPY_SEARCHUP;
		data->searchregex = wp->searchregex;
		data->searchstr = xstrdup(wp->searchstr);
	} else {
		data->searchtype = WINDOW_COPY_OFF;
		data->searchregex = 0;
		data->searchstr = NULL;
	}
	data->searchx = data->searchy = data->searcho = -1;
	data->searchall = 1;

	data->jumptype = WINDOW_COPY_OFF;
	data->jumpchar = NULL;

	screen_init(&data->screen, screen_size_x(base), screen_size_y(base), 0);
	data->modekeys = options_get_number(wp->window->options, "mode-keys");

	evtimer_set(&data->dragtimer, window_copy_scroll_timer, wme);

	return (data);
}

static struct screen *
window_copy_init(struct window_mode_entry *wme,
    __unused struct cmd_find_state *fs, struct args *args)
{
	struct window_pane		*wp = wme->swp;
	struct window_copy_mode_data	*data;
	struct screen			*base = &wp->base;
	struct screen_write_ctx		 ctx;
	u_int				 i, cx, cy;

	data = window_copy_common_init(wme);
	data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy,
	    wme->swp != wme->wp);

	data->cx = cx;
	if (cy < screen_hsize(data->backing)) {
		data->cy = 0;
		data->oy = screen_hsize(data->backing) - cy;
	} else {
		data->cy = cy - screen_hsize(data->backing);
		data->oy = 0;
	}

	data->scroll_exit = args_has(args, 'e');
	data->hide_position = args_has(args, 'H');

	data->screen.cx = data->cx;
	data->screen.cy = data->cy;
	data->mx = data->cx;
	data->my = screen_hsize(data->backing) + data->cy - data->oy;
	data->showmark = 0;

	screen_write_start(&ctx, &data->screen);
	for (i = 0; i < screen_size_y(&data->screen); i++)
		window_copy_write_line(wme, &ctx, i);
	screen_write_cursormove(&ctx, data->cx, data->cy, 0);
	screen_write_stop(&ctx);

	return (&data->screen);
}

static struct screen *
window_copy_view_init(struct window_mode_entry *wme,
    __unused struct cmd_find_state *fs, __unused struct args *args)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data;
	struct screen			*base = &wp->base;
	u_int				 sx = screen_size_x(base);

	data = window_copy_common_init(wme);
	data->viewmode = 1;

	data->backing = xmalloc(sizeof *data->backing);
	screen_init(data->backing, sx, screen_size_y(base), UINT_MAX);
	data->writing = xmalloc(sizeof *data->writing);
	screen_init(data->writing, sx, screen_size_y(base), 0);
	data->ictx = input_init(NULL, NULL, NULL);
	data->mx = data->cx;
	data->my = screen_hsize(data->backing) + data->cy - data->oy;
	data->showmark = 0;

	return (&data->screen);
}

static void
window_copy_free(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;

	evtimer_del(&data->dragtimer);

	free(data->searchmark);
	free(data->searchstr);
	free(data->jumpchar);

	if (data->writing != NULL) {
		screen_free(data->writing);
		free(data->writing);
	}
	if (data->ictx != NULL)
		input_free(data->ictx);
	screen_free(data->backing);
	free(data->backing);

	screen_free(&data->screen);
	free(data);
}

void
window_copy_add(struct window_pane *wp, int parse, const char *fmt, ...)
{
	va_list	ap;

	va_start(ap, fmt);
	window_copy_vadd(wp, parse, fmt, ap);
	va_end(ap);
}

static void
window_copy_init_ctx_cb(__unused struct screen_write_ctx *ctx,
    struct tty_ctx *ttyctx)
{
	memcpy(&ttyctx->defaults, &grid_default_cell, sizeof ttyctx->defaults);
	ttyctx->palette = NULL;
	ttyctx->redraw_cb = NULL;
	ttyctx->set_client_cb = NULL;
	ttyctx->arg = NULL;
}

void
window_copy_vadd(struct window_pane *wp, int parse, const char *fmt, va_list ap)
{
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*backing = data->backing;
	struct screen			*writing = data->writing;
	struct screen_write_ctx	 	 writing_ctx, backing_ctx, ctx;
	struct grid_cell		 gc;
	u_int				 old_hsize, old_cy;
	u_int				 sx = screen_size_x(backing);
	char				*text;

	if (parse) {
		vasprintf(&text, fmt, ap);
		screen_write_start(&writing_ctx, writing);
		screen_write_reset(&writing_ctx);
		input_parse_screen(data->ictx, writing, window_copy_init_ctx_cb,
		    data, text, strlen(text));
		free(text);
	}

	old_hsize = screen_hsize(data->backing);
	screen_write_start(&backing_ctx, backing);
	if (data->backing_written) {
		/*
		 * On the second or later line, do a CRLF before writing
		 * (so it's on a new line).
		 */
		screen_write_carriagereturn(&backing_ctx);
		screen_write_linefeed(&backing_ctx, 0, 8);
	} else
		data->backing_written = 1;
	old_cy = backing->cy;
	if (parse)
		screen_write_fast_copy(&backing_ctx, writing, 0, 0, sx, 1);
	else {
		memcpy(&gc, &grid_default_cell, sizeof gc);
		screen_write_vnputs(&backing_ctx, 0, &gc, fmt, ap);
	}
	screen_write_stop(&backing_ctx);

	data->oy += screen_hsize(data->backing) - old_hsize;

	screen_write_start_pane(&ctx, wp, &data->screen);

	/*
	 * If the history has changed, draw the top line.
	 * (If there's any history at all, it has changed.)
	 */
	if (screen_hsize(data->backing))
		window_copy_redraw_lines(wme, 0, 1);

	/* Write the new lines. */
	window_copy_redraw_lines(wme, old_cy, backing->cy - old_cy + 1);

	screen_write_stop(&ctx);
}

void
window_copy_pageup(struct window_pane *wp, int half_page)
{
	window_copy_pageup1(TAILQ_FIRST(&wp->modes), half_page);
}

static void
window_copy_pageup1(struct window_mode_entry *wme, int half_page)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 n, ox, oy, px, py;

	oy = screen_hsize(data->backing) + data->cy - data->oy;
	ox = window_copy_find_length(wme, oy);

	if (data->cx != ox) {
		data->lastcx = data->cx;
		data->lastsx = ox;
	}
	data->cx = data->lastcx;

	n = 1;
	if (screen_size_y(s) > 2) {
		if (half_page)
			n = screen_size_y(s) / 2;
		else
			n = screen_size_y(s) - 2;
	}

	if (data->oy + n > screen_hsize(data->backing)) {
		data->oy = screen_hsize(data->backing);
		if (data->cy < n)
			data->cy = 0;
		else
			data->cy -= n;
	} else
		data->oy += n;

	if (data->screen.sel == NULL || !data->rectflag) {
		py = screen_hsize(data->backing) + data->cy - data->oy;
		px = window_copy_find_length(wme, py);
		if ((data->cx >= data->lastsx && data->cx != px) ||
		    data->cx > px)
			window_copy_cursor_end_of_line(wme);
	}

	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 1, 0);
	window_copy_redraw_screen(wme);
}

static int
window_copy_pagedown(struct window_mode_entry *wme, int half_page,
    int scroll_exit)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 n, ox, oy, px, py;

	oy = screen_hsize(data->backing) + data->cy - data->oy;
	ox = window_copy_find_length(wme, oy);

	if (data->cx != ox) {
		data->lastcx = data->cx;
		data->lastsx = ox;
	}
	data->cx = data->lastcx;

	n = 1;
	if (screen_size_y(s) > 2) {
		if (half_page)
			n = screen_size_y(s) / 2;
		else
			n = screen_size_y(s) - 2;
	}

	if (data->oy < n) {
		data->oy = 0;
		if (data->cy + (n - data->oy) >= screen_size_y(data->backing))
			data->cy = screen_size_y(data->backing) - 1;
		else
			data->cy += n - data->oy;
	} else
		data->oy -= n;

	if (data->screen.sel == NULL || !data->rectflag) {
		py = screen_hsize(data->backing) + data->cy - data->oy;
		px = window_copy_find_length(wme, py);
		if ((data->cx >= data->lastsx && data->cx != px) ||
		    data->cx > px)
			window_copy_cursor_end_of_line(wme);
	}

	if (scroll_exit && data->oy == 0)
		return (1);
	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 1, 0);
	window_copy_redraw_screen(wme);
	return (0);
}

static void
window_copy_previous_paragraph(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	u_int				 oy;

	oy = screen_hsize(data->backing) + data->cy - data->oy;

	while (oy > 0 && window_copy_find_length(wme, oy) == 0)
		oy--;

	while (oy > 0 && window_copy_find_length(wme, oy) > 0)
		oy--;

	window_copy_scroll_to(wme, 0, oy, 0);
}

static void
window_copy_next_paragraph(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 maxy, ox, oy;

	oy = screen_hsize(data->backing) + data->cy - data->oy;
	maxy = screen_hsize(data->backing) + screen_size_y(s) - 1;

	while (oy < maxy && window_copy_find_length(wme, oy) == 0)
		oy++;

	while (oy < maxy && window_copy_find_length(wme, oy) > 0)
		oy++;

	ox = window_copy_find_length(wme, oy);
	window_copy_scroll_to(wme, ox, oy, 0);
}

char *
window_copy_get_word(struct window_pane *wp, u_int x, u_int y)
{
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->screen.grid;

	return (format_grid_word(gd, x, gd->hsize + y));
}

char *
window_copy_get_line(struct window_pane *wp, u_int y)
{
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->screen.grid;

	return (format_grid_line(gd, gd->hsize + y));
}

static void *
window_copy_cursor_word_cb(struct format_tree *ft)
{
	struct window_pane		*wp = format_get_pane(ft);
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;

	return (window_copy_get_word(wp, data->cx, data->cy));
}

static void *
window_copy_cursor_line_cb(struct format_tree *ft)
{
	struct window_pane		*wp = format_get_pane(ft);
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;

	return (window_copy_get_line(wp, data->cy));
}

static void *
window_copy_search_match_cb(struct format_tree *ft)
{
	struct window_pane		*wp = format_get_pane(ft);
	struct window_mode_entry	*wme = TAILQ_FIRST(&wp->modes);
	struct window_copy_mode_data	*data = wme->data;

	return (window_copy_match_at_cursor(data));
}

static void
window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft)
{
	struct window_copy_mode_data	*data = wme->data;

	format_add(ft, "scroll_position", "%d", data->oy);
	format_add(ft, "rectangle_toggle", "%d", data->rectflag);

	format_add(ft, "copy_cursor_x", "%d", data->cx);
	format_add(ft, "copy_cursor_y", "%d", data->cy);

	format_add(ft, "selection_present", "%d", data->screen.sel != NULL);
	if (data->screen.sel != NULL) {
		format_add(ft, "selection_start_x", "%d", data->selx);
		format_add(ft, "selection_start_y", "%d", data->sely);
		format_add(ft, "selection_end_x", "%d", data->endselx);
		format_add(ft, "selection_end_y", "%d", data->endsely);
		format_add(ft, "selection_active", "%d",
		    data->cursordrag != CURSORDRAG_NONE);
	} else
		format_add(ft, "selection_active", "%d", 0);

	format_add(ft, "search_present", "%d", data->searchmark != NULL);
	format_add_cb(ft, "search_match", window_copy_search_match_cb);

	format_add_cb(ft, "copy_cursor_word", window_copy_cursor_word_cb);
	format_add_cb(ft, "copy_cursor_line", window_copy_cursor_line_cb);
}

static void
window_copy_size_changed(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct screen_write_ctx		 ctx;
	int				 search = (data->searchmark != NULL);

	window_copy_clear_selection(wme);
	window_copy_clear_marks(wme);

	screen_write_start(&ctx, s);
	window_copy_write_lines(wme, &ctx, 0, screen_size_y(s));
	screen_write_stop(&ctx);

	if (search && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 0);
	data->searchx = data->cx;
	data->searchy = data->cy;
	data->searcho = data->oy;
}

static void
window_copy_resize(struct window_mode_entry *wme, u_int sx, u_int sy)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct grid			*gd = data->backing->grid;
	u_int				 cx, cy, wx, wy;
	int				 reflow;

	screen_resize(s, sx, sy, 0);
	cx = data->cx;
	cy = gd->hsize + data->cy - data->oy;
	reflow = (gd->sx != sx);
	if (reflow)
		grid_wrap_position(gd, cx, cy, &wx, &wy);
	screen_resize_cursor(data->backing, sx, sy, 1, 0, 0);
	if (reflow)
		grid_unwrap_position(gd, &cx, &cy, wx, wy);

	data->cx = cx;
	if (cy < gd->hsize) {
		data->cy = 0;
		data->oy = gd->hsize - cy;
	} else {
		data->cy = cy - gd->hsize;
		data->oy = 0;
	}

	window_copy_size_changed(wme);
	window_copy_redraw_screen(wme);
}

static const char *
window_copy_key_table(struct window_mode_entry *wme)
{
	struct window_pane	*wp = wme->wp;

	if (options_get_number(wp->window->options, "mode-keys") == MODEKEY_VI)
		return ("copy-mode-vi");
	return ("copy-mode");
}

static int
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);
	char				*expanded;

	if (ss == NULL || *ss == '\0')
		return (0);

	if (args_has(cs->args, 'F')) {
		expanded = format_single(NULL, ss, NULL, NULL, NULL, wme->wp);
		if (*expanded == '\0') {
			free(expanded);
			return (0);
		}
		free(data->searchstr);
		data->searchstr = expanded;
	} else {
		free(data->searchstr);
		data->searchstr = xstrdup(ss);
	}
	return (1);
}

static enum window_copy_cmd_action
window_copy_cmd_append_selection(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct session			*s = cs->s;

	if (s != NULL)
		window_copy_append_selection(wme);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_append_selection_and_cancel(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct session			*s = cs->s;

	if (s != NULL)
		window_copy_append_selection(wme);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_CANCEL);
}

static enum window_copy_cmd_action
window_copy_cmd_back_to_indentation(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cursor_back_to_indentation(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_begin_selection(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct client			*c = cs->c;
	struct mouse_event		*m = cs->m;
	struct window_copy_mode_data	*data = wme->data;

	if (m != NULL) {
		window_copy_start_drag(c, m);
		return (WINDOW_COPY_CMD_NOTHING);
	}

	data->lineflag = LINE_SEL_NONE;
	data->selflag = SEL_CHAR;
	window_copy_start_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_stop_selection(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->cursordrag = CURSORDRAG_NONE;
	data->lineflag = LINE_SEL_NONE;
	data->selflag = SEL_CHAR;
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_bottom_line(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->cx = 0;
	data->cy = screen_size_y(&data->screen) - 1;

	window_copy_update_selection(wme, 1, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_cancel(__unused struct window_copy_cmd_state *cs)
{
	return (WINDOW_COPY_CMD_CANCEL);
}

static enum window_copy_cmd_action
window_copy_cmd_clear_selection(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_do_copy_end_of_line(struct window_copy_cmd_state *cs, int pipe,
    int cancel)
{
	struct window_mode_entry	 *wme = cs->wme;
	struct client			 *c = cs->c;
	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				  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);

	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);
	}

	ocx = data->cx;
	ocy = data->cy;
	ooy = data->oy;

	window_copy_start_selection(wme);
	for (; np > 1; np--)
		window_copy_cursor_down(wme, 0);
	window_copy_cursor_end_of_line(wme);

	if (s != NULL) {
		if (pipe)
			window_copy_copy_pipe(wme, s, prefix, command);
		else
			window_copy_copy_selection(wme, prefix);

		if (cancel) {
			free(prefix);
			free(command);
			return (WINDOW_COPY_CMD_CANCEL);
		}
	}
	window_copy_clear_selection(wme);

	data->cx = ocx;
	data->cy = ocy;
	data->oy = ooy;

	free(prefix);
	free(command);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_end_of_line(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_end_of_line(cs, 0, 0));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_end_of_line_and_cancel(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_end_of_line(cs, 0, 1));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_end_of_line(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_end_of_line(cs, 1, 0));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_end_of_line_and_cancel(
    struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_end_of_line(cs, 1, 1));
}

static enum window_copy_cmd_action
window_copy_do_copy_line(struct window_copy_cmd_state *cs, int pipe, int cancel)
{
	struct window_mode_entry	 *wme = cs->wme;
	struct client			 *c = cs->c;
	struct session			 *s = cs->s;
	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				  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);

	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);
	}

	ocx = data->cx;
	ocy = data->cy;
	ooy = data->oy;

	data->selflag = SEL_CHAR;
	window_copy_cursor_start_of_line(wme);
	window_copy_start_selection(wme);
	for (; np > 1; np--)
		window_copy_cursor_down(wme, 0);
	window_copy_cursor_end_of_line(wme);

	if (s != NULL) {
		if (pipe)
			window_copy_copy_pipe(wme, s, prefix, command);
		else
			window_copy_copy_selection(wme, prefix);

		if (cancel) {
			free(prefix);
			free(command);
			return (WINDOW_COPY_CMD_CANCEL);
		}
	}
	window_copy_clear_selection(wme);

	data->cx = ocx;
	data->cy = ocy;
	data->oy = ooy;

	free(prefix);
	free(command);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_line(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_line(cs, 0, 0));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_line_and_cancel(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_line(cs, 0, 1));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_line(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_line(cs, 1, 0));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_line_and_cancel(struct window_copy_cmd_state *cs)
{
	return (window_copy_do_copy_line(cs, 1, 1));
}

static enum window_copy_cmd_action
window_copy_cmd_copy_selection_no_clear(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct client			*c = cs->c;
	struct session			*s = cs->s;
	struct winlink			*wl = cs->wl;
	struct window_pane		*wp = wme->wp;
	char				*prefix = NULL;
	const char			*arg1 = args_string(cs->args, 1);

	if (arg1 != NULL)
		prefix = format_single(NULL, arg1, c, s, wl, wp);

	if (s != NULL)
		window_copy_copy_selection(wme, prefix);

	free(prefix);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_selection(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_copy_selection_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_selection_and_cancel(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_copy_selection_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_CANCEL);
}

static enum window_copy_cmd_action
window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_down(wme, 0);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_cursor_down_and_cancel(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, cy;

	cy = data->cy;
	for (; np != 0; np--)
		window_copy_cursor_down(wme, 0);
	if (cy == data->cy && data->oy == 0)
		return (WINDOW_COPY_CMD_CANCEL);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_cursor_left(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_left(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_cursor_right(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;

	for (; np != 0; np--) {
		window_copy_cursor_right(wme, data->screen.sel != NULL &&
		    data->rectflag);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

/* Scroll line containing the cursor to the given position. */
static enum window_copy_cmd_action
window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;
	u_int				 oy, delta;
	int				 scroll_up; /* >0 up, <0 down */

	scroll_up = data->cy - to;
	delta = abs(scroll_up);
	oy = screen_hsize(data->backing) - data->oy;

	/*
	 * oy is the maximum scroll down amount, while data->oy is the maximum
	 * scroll up amount.
	 */
	if (scroll_up > 0 && data->oy >= delta) {
		window_copy_scroll_up(wme, delta);
		data->cy -= delta;
	} else if (scroll_up < 0 && oy >= delta) {
		window_copy_scroll_down(wme, delta);
		data->cy += delta;
	}

	window_copy_update_selection(wme, 0, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

/* Scroll line containing the cursor to the bottom. */
static enum window_copy_cmd_action
window_copy_cmd_scroll_bottom(struct window_copy_cmd_state *cs)
{
	struct window_copy_mode_data	*data = cs->wme->data;
	u_int				 bottom;

	bottom = screen_size_y(&data->screen) - 1;
	return (window_copy_cmd_scroll_to(cs, bottom));
}

/* Scroll line containing the cursor to the middle. */
static enum window_copy_cmd_action
window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs)
{
	struct window_copy_mode_data	*data = cs->wme->data;
	u_int				 mid_value;

	mid_value = (screen_size_y(&data->screen) - 1) / 2;
	return (window_copy_cmd_scroll_to(cs, mid_value));
}

/* Scroll line containing the cursor to the top. */
static enum window_copy_cmd_action
window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs)
{
	return (window_copy_cmd_scroll_to(cs, 0));
}

static enum window_copy_cmd_action
window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_up(wme, 0);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_end_of_line(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cursor_end_of_line(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_halfpage_down(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;

	for (; np != 0; np--) {
		if (window_copy_pagedown(wme, 1, data->scroll_exit))
			return (WINDOW_COPY_CMD_CANCEL);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_halfpage_down_and_cancel(struct window_copy_cmd_state *cs)
{

	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--) {
		if (window_copy_pagedown(wme, 1, 1))
			return (WINDOW_COPY_CMD_CANCEL);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_halfpage_up(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_pageup1(wme, 1);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_toggle_position(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->hide_position = !data->hide_position;
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_history_bottom(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing;
	u_int				 oy;

	oy = screen_hsize(s) + data->cy - data->oy;
	if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely)
		window_copy_other_end(wme);

	data->cy = screen_size_y(&data->screen) - 1;
	data->cx = window_copy_find_length(wme, screen_hsize(s) + data->cy);
	data->oy = 0;

	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 1, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_history_top(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;
	u_int				 oy;

	oy = screen_hsize(data->backing) + data->cy - data->oy;
	if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely)
		window_copy_other_end(wme);

	data->cy = 0;
	data->cx = 0;
	data->oy = screen_hsize(data->backing);

	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 1, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_jump_again(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;

	switch (data->jumptype) {
	case WINDOW_COPY_JUMPFORWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump(wme);
		break;
	case WINDOW_COPY_JUMPBACKWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_back(wme);
		break;
	case WINDOW_COPY_JUMPTOFORWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_to(wme);
		break;
	case WINDOW_COPY_JUMPTOBACKWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_to_back(wme);
		break;
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_jump_reverse(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;

	switch (data->jumptype) {
	case WINDOW_COPY_JUMPFORWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_back(wme);
		break;
	case WINDOW_COPY_JUMPBACKWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump(wme);
		break;
	case WINDOW_COPY_JUMPTOFORWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_to_back(wme);
		break;
	case WINDOW_COPY_JUMPTOBACKWARD:
		for (; np != 0; np--)
			window_copy_cursor_jump_to(wme);
		break;
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_middle_line(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->cx = 0;
	data->cy = (screen_size_y(&data->screen) - 1) / 2;

	window_copy_update_selection(wme, 1, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_previous_matching_bracket(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing;
	char				 open[] = "{[(", close[] = "}])";
	char				 tried, found, start, *cp;
	u_int				 px, py, xx, n;
	struct grid_cell		 gc;
	int				 failed;

	for (; np != 0; np--) {
		/* Get cursor position and line length. */
		px = data->cx;
		py = screen_hsize(s) + data->cy - data->oy;
		xx = window_copy_find_length(wme, py);
		if (xx == 0)
			break;

		/*
		 * Get the current character. If not on a bracket, try the
		 * previous. If still not, then behave like previous-word.
		 */
		tried = 0;
	retry:
		grid_get_cell(s->grid, px, py, &gc);
		if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING))
			cp = NULL;
		else {
			found = *gc.data.data;
			cp = strchr(close, found);
		}
		if (cp == NULL) {
			if (data->modekeys == MODEKEY_EMACS) {
				if (!tried && px > 0) {
					px--;
					tried = 1;
					goto retry;
				}
				window_copy_cursor_previous_word(wme, close, 1);
			}
			continue;
		}
		start = open[cp - close];

		/* Walk backward until the matching bracket is reached. */
		n = 1;
		failed = 0;
		do {
			if (px == 0) {
				if (py == 0) {
					failed = 1;
					break;
				}
				do {
					py--;
					xx = window_copy_find_length(wme, py);
				} while (xx == 0 && py > 0);
				if (xx == 0 && py == 0) {
					failed = 1;
					break;
				}
				px = xx - 1;
			} else
				px--;

			grid_get_cell(s->grid, px, py, &gc);
			if (gc.data.size == 1 &&
			    (~gc.flags & GRID_FLAG_PADDING)) {
				if (*gc.data.data == found)
					n++;
				else if (*gc.data.data == start)
					n--;
			}
		} while (n != 0);

		/* Move the cursor to the found location if any. */
		if (!failed)
			window_copy_scroll_to(wme, px, py, 0);
	}

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_matching_bracket(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing;
	char				 open[] = "{[(", close[] = "}])";
	char				 tried, found, end, *cp;
	u_int				 px, py, xx, yy, sx, sy, n;
	struct grid_cell		 gc;
	int				 failed;
	struct grid_line		*gl;

	for (; np != 0; np--) {
		/* Get cursor position and line length. */
		px = data->cx;
		py = screen_hsize(s) + data->cy - data->oy;
		xx = window_copy_find_length(wme, py);
		yy = screen_hsize(s) + screen_size_y(s) - 1;
		if (xx == 0)
			break;

		/*
		 * Get the current character. If not on a bracket, try the
		 * next. If still not, then behave like next-word.
		 */
		tried = 0;
	retry:
		grid_get_cell(s->grid, px, py, &gc);
		if (gc.data.size != 1 || (gc.flags & GRID_FLAG_PADDING))
			cp = NULL;
		else {
			found = *gc.data.data;

			/*
			 * In vi mode, attempt to move to previous bracket if a
			 * closing bracket is found first. If this fails,
			 * return to the original cursor position.
			 */
			cp = strchr(close, found);
			if (cp != NULL && data->modekeys == MODEKEY_VI) {
				sx = data->cx;
				sy = screen_hsize(s) + data->cy - data->oy;

				window_copy_scroll_to(wme, px, py, 0);
				window_copy_cmd_previous_matching_bracket(cs);

				px = data->cx;
				py = screen_hsize(s) + data->cy - data->oy;
				grid_get_cell(s->grid, px, py, &gc);
				if (gc.data.size == 1 &&
				    (~gc.flags & GRID_FLAG_PADDING) &&
				    strchr(close, *gc.data.data) != NULL)
					window_copy_scroll_to(wme, sx, sy, 0);
				break;
			}

			cp = strchr(open, found);
		}
		if (cp == NULL) {
			if (data->modekeys == MODEKEY_EMACS) {
				if (!tried && px <= xx) {
					px++;
					tried = 1;
					goto retry;
				}
				window_copy_cursor_next_word_end(wme, open, 0);
				continue;
			}
			/* For vi, continue searching for bracket until EOL. */
			if (px > xx) {
				if (py == yy)
					continue;
				gl = grid_get_line(s->grid, py);
				if (~gl->flags & GRID_LINE_WRAPPED)
					continue;
				if (gl->cellsize > s->grid->sx)
					continue;
				px = 0;
				py++;
				xx = window_copy_find_length(wme, py);
			} else
				px++;
			goto retry;
		}
		end = close[cp - open];

		/* Walk forward until the matching bracket is reached. */
		n = 1;
		failed = 0;
		do {
			if (px > xx) {
				if (py == yy) {
					failed = 1;
					break;
				}
				px = 0;
				py++;
				xx = window_copy_find_length(wme, py);
			} else
				px++;

			grid_get_cell(s->grid, px, py, &gc);
			if (gc.data.size == 1 &&
			    (~gc.flags & GRID_FLAG_PADDING)) {
				if (*gc.data.data == found)
					n++;
				else if (*gc.data.data == end)
					n--;
			}
		} while (n != 0);

		/* Move the cursor to the found location if any. */
		if (!failed)
			window_copy_scroll_to(wme, px, py, 0);
	}

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_paragraph(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_next_paragraph(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_space(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_next_word(wme, "");
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_space_end(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_next_word_end(wme, "", 0);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_word(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	const char			*separators;

	separators = options_get_string(cs->s->options, "word-separators");

	for (; np != 0; np--)
		window_copy_cursor_next_word(wme, separators);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_next_word_end(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	const char			*separators;

	separators = options_get_string(cs->s->options, "word-separators");

	for (; np != 0; np--)
		window_copy_cursor_next_word_end(wme, separators, 0);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_other_end(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	struct window_copy_mode_data	*data = wme->data;

	data->selflag = SEL_CHAR;
	if ((np % 2) != 0)
		window_copy_other_end(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_page_down(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;

	for (; np != 0; np--) {
		if (window_copy_pagedown(wme, 0, data->scroll_exit))
			return (WINDOW_COPY_CMD_CANCEL);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_page_down_and_cancel(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--) {
		if (window_copy_pagedown(wme, 0, 1))
			return (WINDOW_COPY_CMD_CANCEL);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_page_up(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_pageup1(wme, 0);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_previous_paragraph(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_previous_paragraph(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_previous_space(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_previous_word(wme, "", 1);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_previous_word(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;
	const char			*separators;

	separators = options_get_string(cs->s->options, "word-separators");

	for (; np != 0; np--)
		window_copy_cursor_previous_word(wme, separators, 1);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_rectangle_on(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->lineflag = LINE_SEL_NONE;
	window_copy_rectangle_set(wme, 1);

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_rectangle_off(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->lineflag = LINE_SEL_NONE;
	window_copy_rectangle_set(wme, 0);

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_rectangle_toggle(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->lineflag = LINE_SEL_NONE;
	window_copy_rectangle_set(wme, !data->rectflag);

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_scroll_down(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;

	for (; np != 0; np--)
		window_copy_cursor_down(wme, 1);
	if (data->scroll_exit && data->oy == 0)
		return (WINDOW_COPY_CMD_CANCEL);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_scroll_down_and_cancel(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;

	for (; np != 0; np--)
		window_copy_cursor_down(wme, 1);
	if (data->oy == 0)
		return (WINDOW_COPY_CMD_CANCEL);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_scroll_up(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	u_int				 np = wme->prefix;

	for (; np != 0; np--)
		window_copy_cursor_up(wme, 1);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_again(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;

	if (data->searchtype == WINDOW_COPY_SEARCHUP) {
		for (; np != 0; np--)
			window_copy_search_up(wme, data->searchregex);
	} else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
		for (; np != 0; np--)
			window_copy_search_down(wme, data->searchregex);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_reverse(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;

	if (data->searchtype == WINDOW_COPY_SEARCHUP) {
		for (; np != 0; np--)
			window_copy_search_down(wme, data->searchregex);
	} else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
		for (; np != 0; np--)
			window_copy_search_up(wme, data->searchregex);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_select_line(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;

	data->lineflag = LINE_SEL_LEFT_RIGHT;
	data->rectflag = 0;
	data->selflag = SEL_LINE;
	data->dx = data->cx;
	data->dy = screen_hsize(data->backing) + data->cy - data->oy;

	window_copy_cursor_start_of_line(wme);
	data->selrx = data->cx;
	data->selry = screen_hsize(data->backing) + data->cy - data->oy;
	data->endselry = data->selry;
	window_copy_start_selection(wme);
	window_copy_cursor_end_of_line(wme);
	data->endselry = screen_hsize(data->backing) + data->cy - data->oy;
	data->endselrx = window_copy_find_length(wme, data->endselry);
	for (; np > 1; np--) {
		window_copy_cursor_down(wme, 0);
		window_copy_cursor_end_of_line(wme);
	}

	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_select_word(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct options			*session_options = cs->s->options;
	struct window_copy_mode_data	*data = wme->data;
	u_int				 px, py, nextx, nexty;

	data->lineflag = LINE_SEL_LEFT_RIGHT;
	data->rectflag = 0;
	data->selflag = SEL_WORD;
	data->dx = data->cx;
	data->dy = screen_hsize(data->backing) + data->cy - data->oy;

	data->separators = options_get_string(session_options,
	    "word-separators");
	window_copy_cursor_previous_word(wme, data->separators, 0);
	px = data->cx;
	py = screen_hsize(data->backing) + data->cy - data->oy;
	data->selrx = px;
	data->selry = py;
	window_copy_start_selection(wme);

	/* Handle single character words. */
	nextx = px + 1;
	nexty = py;
	if (grid_get_line(data->backing->grid, nexty)->flags &
	    GRID_LINE_WRAPPED && nextx > screen_size_x(data->backing) - 1) {
		nextx = 0;
		nexty++;
	}
	if (px >= window_copy_find_length(wme, py) ||
	    !window_copy_in_set(wme, nextx, nexty, WHITESPACE))
		window_copy_cursor_next_word_end(wme, data->separators, 1);
	else {
		window_copy_update_cursor(wme, px, data->cy);
		if (window_copy_update_selection(wme, 1, 1))
			window_copy_redraw_lines(wme, data->cy, 1);
	}
	data->endselrx = data->cx;
	data->endselry = screen_hsize(data->backing) + data->cy - data->oy;
	if (data->dy > data->endselry) {
		data->dy = data->endselry;
		data->dx = data->endselrx;
	} else if (data->dx > data->endselrx)
		data->dx = data->endselrx;

	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_set_mark(struct window_copy_cmd_state *cs)
{
	struct window_copy_mode_data	*data = cs->wme->data;

	data->mx = data->cx;
	data->my = screen_hsize(data->backing) + data->cy - data->oy;
	data->showmark = 1;
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_start_of_line(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cursor_start_of_line(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_top_line(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_copy_mode_data	*data = wme->data;

	data->cx = 0;
	data->cy = 0;

	window_copy_update_selection(wme, 1, 0);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_no_clear(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct client			*c = cs->c;
	struct session			*s = cs->s;
	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);

	if (arg2 != NULL)
		prefix = format_single(NULL, arg2, 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);
	free(command);

	free(prefix);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_copy_pipe_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_copy_pipe_and_cancel(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_copy_pipe_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_CANCEL);
}

static enum window_copy_cmd_action
window_copy_cmd_pipe_no_clear(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct client			*c = cs->c;
	struct session			*s = cs->s;
	struct winlink			*wl = cs->wl;
	struct window_pane		*wp = wme->wp;
	char				*command = NULL;
	const char			*arg1 = args_string(cs->args, 1);

	if (s != NULL && arg1 != NULL && *arg1 != '\0')
		command = format_single(NULL, arg1, c, s, wl, wp);
	window_copy_pipe(wme, s, command);
	free(command);

	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_pipe(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_pipe_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static enum window_copy_cmd_action
window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_cmd_pipe_no_clear(cs);
	window_copy_clear_selection(wme);
	return (WINDOW_COPY_CMD_CANCEL);
}

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);

	if (*arg1 != '\0')
		window_copy_goto_line(wme, arg1);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
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);

	if (*arg1 != '\0') {
		data->jumptype = WINDOW_COPY_JUMPBACKWARD;
		free(data->jumpchar);
		data->jumpchar = utf8_fromcstr(arg1);
		for (; np != 0; np--)
			window_copy_cursor_jump_back(wme);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
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);

	if (*arg1 != '\0') {
		data->jumptype = WINDOW_COPY_JUMPFORWARD;
		free(data->jumpchar);
		data->jumpchar = utf8_fromcstr(arg1);
		for (; np != 0; np--)
			window_copy_cursor_jump(wme);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
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);

	if (*arg1 != '\0') {
		data->jumptype = WINDOW_COPY_JUMPTOBACKWARD;
		free(data->jumpchar);
		data->jumpchar = utf8_fromcstr(arg1);
		for (; np != 0; np--)
			window_copy_cursor_jump_to_back(wme);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
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);

	if (*arg1 != '\0') {
		data->jumptype = WINDOW_COPY_JUMPTOFORWARD;
		free(data->jumpchar);
		data->jumpchar = utf8_fromcstr(arg1);
		for (; np != 0; np--)
			window_copy_cursor_jump_to(wme);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_jump_to_mark(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;

	window_copy_jump_to_mark(wme);
	return (WINDOW_COPY_CMD_NOTHING);
}

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);
	return (WINDOW_COPY_CMD_NOTHING);
}

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);
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_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;

	if (!window_copy_expand_search_string(cs))
		return (WINDOW_COPY_CMD_NOTHING);

	if (data->searchstr != NULL) {
		data->searchtype = WINDOW_COPY_SEARCHUP;
		data->searchregex = 1;
		data->timeout = 0;
		for (; np != 0; np--)
			window_copy_search_up(wme, 1);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_backward_text(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;

	if (!window_copy_expand_search_string(cs))
		return (WINDOW_COPY_CMD_NOTHING);

	if (data->searchstr != NULL) {
		data->searchtype = WINDOW_COPY_SEARCHUP;
		data->searchregex = 0;
		data->timeout = 0;
		for (; np != 0; np--)
			window_copy_search_up(wme, 0);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_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;

	if (!window_copy_expand_search_string(cs))
		return (WINDOW_COPY_CMD_NOTHING);

	if (data->searchstr != NULL) {
		data->searchtype = WINDOW_COPY_SEARCHDOWN;
		data->searchregex = 1;
		data->timeout = 0;
		for (; np != 0; np--)
			window_copy_search_down(wme, 1);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
window_copy_cmd_search_forward_text(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;

	if (!window_copy_expand_search_string(cs))
		return (WINDOW_COPY_CMD_NOTHING);

	if (data->searchstr != NULL) {
		data->searchtype = WINDOW_COPY_SEARCHDOWN;
		data->searchregex = 0;
		data->timeout = 0;
		for (; np != 0; np--)
			window_copy_search_down(wme, 0);
	}
	return (WINDOW_COPY_CMD_NOTHING);
}

static enum window_copy_cmd_action
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			*ss = data->searchstr;
	char				 prefix;
	enum window_copy_cmd_action	 action = WINDOW_COPY_CMD_NOTHING;

	data->timeout = 0;

	log_debug("%s: %s", __func__, arg1);

	prefix = *arg1++;
	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) {
		data->cx = data->searchx;
		data->cy = data->searchy;
		data->oy = data->searcho;
		action = WINDOW_COPY_CMD_REDRAW;
	}
	if (*arg1 == '\0') {
		window_copy_clear_marks(wme);
		return (WINDOW_COPY_CMD_REDRAW);
	}
	switch (prefix) {
	case '=':
	case '-':
		data->searchtype = WINDOW_COPY_SEARCHUP;
		data->searchregex = 0;
		free(data->searchstr);
		data->searchstr = xstrdup(arg1);
		if (!window_copy_search_up(wme, 0)) {
			window_copy_clear_marks(wme);
			return (WINDOW_COPY_CMD_REDRAW);
		}
		break;
	case '+':
		data->searchtype = WINDOW_COPY_SEARCHDOWN;
		data->searchregex = 0;
		free(data->searchstr);
		data->searchstr = xstrdup(arg1);
		if (!window_copy_search_down(wme, 0)) {
			window_copy_clear_marks(wme);
			return (WINDOW_COPY_CMD_REDRAW);
		}
		break;
	}
	return (action);
}

static enum window_copy_cmd_action
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			*ss = data->searchstr;
	char				 prefix;
	enum window_copy_cmd_action	 action = WINDOW_COPY_CMD_NOTHING;

	data->timeout = 0;

	log_debug("%s: %s", __func__, arg1);

	prefix = *arg1++;
	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) {
		data->cx = data->searchx;
		data->cy = data->searchy;
		data->oy = data->searcho;
		action = WINDOW_COPY_CMD_REDRAW;
	}
	if (*arg1 == '\0') {
		window_copy_clear_marks(wme);
		return (WINDOW_COPY_CMD_REDRAW);
	}
	switch (prefix) {
	case '=':
	case '+':
		data->searchtype = WINDOW_COPY_SEARCHDOWN;
		data->searchregex = 0;
		free(data->searchstr);
		data->searchstr = xstrdup(arg1);
		if (!window_copy_search_down(wme, 0)) {
			window_copy_clear_marks(wme);
			return (WINDOW_COPY_CMD_REDRAW);
		}
		break;
	case '-':
		data->searchtype = WINDOW_COPY_SEARCHUP;
		data->searchregex = 0;
		free(data->searchstr);
		data->searchstr = xstrdup(arg1);
		if (!window_copy_search_up(wme, 0)) {
			window_copy_clear_marks(wme);
			return (WINDOW_COPY_CMD_REDRAW);
		}
	}
	return (action);
}

static enum window_copy_cmd_action
window_copy_cmd_refresh_from_pane(struct window_copy_cmd_state *cs)
{
	struct window_mode_entry	*wme = cs->wme;
	struct window_pane		*wp = wme->swp;
	struct window_copy_mode_data	*data = wme->data;

	if (data->viewmode)
		return (WINDOW_COPY_CMD_NOTHING);

	screen_free(data->backing);
	free(data->backing);
	data->backing = window_copy_clone_screen(&wp->base, &data->screen, NULL,   NULL, wme->swp != wme->wp);

	window_copy_size_changed(wme);
	return (WINDOW_COPY_CMD_REDRAW);
}

static const struct {
	const char			 *command;
	u_int				  minargs;
	u_int				  maxargs;
	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,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_append_selection
	},
	{ .command = "append-selection-and-cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_append_selection_and_cancel
	},
	{ .command = "back-to-indentation",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_back_to_indentation
	},
	{ .command = "begin-selection",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_begin_selection
	},
	{ .command = "bottom-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_bottom_line
	},
	{ .command = "cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_cancel
	},
	{ .command = "clear-selection",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_clear_selection
	},
	{ .command = "copy-end-of-line",
	  .minargs = 0,
	  .maxargs = 1,
	  .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,
	  .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,
	  .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,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel
	},
	{ .command = "copy-line",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_line
	},
	{ .command = "copy-line-and-cancel",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_line_and_cancel
	},
	{ .command = "copy-pipe-line",
	  .minargs = 0,
	  .maxargs = 2,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_pipe_line
	},
	{ .command = "copy-pipe-line-and-cancel",
	  .minargs = 0,
	  .maxargs = 2,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_pipe_line_and_cancel
	},
	{ .command = "copy-pipe-no-clear",
	  .minargs = 0,
	  .maxargs = 2,
	  .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
	  .f = window_copy_cmd_copy_pipe_no_clear
	},
	{ .command = "copy-pipe",
	  .minargs = 0,
	  .maxargs = 2,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_pipe
	},
	{ .command = "copy-pipe-and-cancel",
	  .minargs = 0,
	  .maxargs = 2,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_pipe_and_cancel
	},
	{ .command = "copy-selection-no-clear",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
	  .f = window_copy_cmd_copy_selection_no_clear
	},
	{ .command = "copy-selection",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_selection
	},
	{ .command = "copy-selection-and-cancel",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_copy_selection_and_cancel
	},
	{ .command = "cursor-down",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_cursor_down
	},
	{ .command = "cursor-down-and-cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_cursor_down_and_cancel
	},
	{ .command = "cursor-left",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_cursor_left
	},
	{ .command = "cursor-right",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_cursor_right
	},
	{ .command = "cursor-up",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_cursor_up
	},
	{ .command = "end-of-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_end_of_line
	},
	{ .command = "goto-line",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_goto_line
	},
	{ .command = "halfpage-down",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_halfpage_down
	},
	{ .command = "halfpage-down-and-cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_halfpage_down_and_cancel
	},
	{ .command = "halfpage-up",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_halfpage_up
	},
	{ .command = "history-bottom",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_history_bottom
	},
	{ .command = "history-top",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_history_top
	},
	{ .command = "jump-again",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_again
	},
	{ .command = "jump-backward",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_backward
	},
	{ .command = "jump-forward",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_forward
	},
	{ .command = "jump-reverse",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_reverse
	},
	{ .command = "jump-to-backward",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_to_backward
	},
	{ .command = "jump-to-forward",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_jump_to_forward
	},
	{ .command = "jump-to-mark",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_jump_to_mark
	},
	{ .command = "next-prompt",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_next_prompt
	},
	{ .command = "previous-prompt",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_previous_prompt
	},
	{ .command = "middle-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_middle_line
	},
	{ .command = "next-matching-bracket",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_next_matching_bracket
	},
	{ .command = "next-paragraph",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_next_paragraph
	},
	{ .command = "next-space",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_next_space
	},
	{ .command = "next-space-end",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_next_space_end
	},
	{ .command = "next-word",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_next_word
	},
	{ .command = "next-word-end",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_next_word_end
	},
	{ .command = "other-end",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_other_end
	},
	{ .command = "page-down",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_page_down
	},
	{ .command = "page-down-and-cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_page_down_and_cancel
	},
	{ .command = "page-up",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_page_up
	},
	{ .command = "pipe-no-clear",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
	  .f = window_copy_cmd_pipe_no_clear
	},
	{ .command = "pipe",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_pipe
	},
	{ .command = "pipe-and-cancel",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_pipe_and_cancel
	},
	{ .command = "previous-matching-bracket",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_previous_matching_bracket
	},
	{ .command = "previous-paragraph",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_previous_paragraph
	},
	{ .command = "previous-space",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_previous_space
	},
	{ .command = "previous-word",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_previous_word
	},
	{ .command = "rectangle-on",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_rectangle_on
	},
	{ .command = "rectangle-off",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_rectangle_off
	},
	{ .command = "rectangle-toggle",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_rectangle_toggle
	},
	{ .command = "refresh-from-pane",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_refresh_from_pane
	},
	{ .command = "scroll-bottom",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_scroll_bottom
	},
	{ .command = "scroll-down",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_scroll_down
	},
	{ .command = "scroll-down-and-cancel",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_scroll_down_and_cancel
	},
	{ .command = "scroll-middle",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_scroll_middle
	},
	{ .command = "scroll-top",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_scroll_top
	},
	{ .command = "scroll-up",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_scroll_up
	},
	{ .command = "search-again",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_again
	},
	{ .command = "search-backward",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_backward
	},
	{ .command = "search-backward-text",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_backward_text
	},
	{ .command = "search-backward-incremental",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_backward_incremental
	},
	{ .command = "search-forward",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_forward
	},
	{ .command = "search-forward-text",
	  .minargs = 0,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_forward_text
	},
	{ .command = "search-forward-incremental",
	  .minargs = 1,
	  .maxargs = 1,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_forward_incremental
	},
	{ .command = "search-reverse",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_search_reverse
	},
	{ .command = "select-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_select_line
	},
	{ .command = "select-word",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_select_word
	},
	{ .command = "set-mark",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_set_mark
	},
	{ .command = "start-of-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_start_of_line
	},
	{ .command = "stop-selection",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
	  .f = window_copy_cmd_stop_selection
	},
	{ .command = "toggle-position",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_NEVER,
	  .f = window_copy_cmd_toggle_position
	},
	{ .command = "top-line",
	  .minargs = 0,
	  .maxargs = 0,
	  .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY,
	  .f = window_copy_cmd_top_line
	}
};

static void
window_copy_command(struct window_mode_entry *wme, struct client *c,
    struct session *s, struct winlink *wl, struct args *args,
    struct mouse_event *m)
{
	struct window_copy_mode_data	*data = wme->data;
	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;

	if (count == 0)
		return;
	command = args_string(args, 0);

	if (m != NULL && m->valid && !MOUSE_WHEEL(m->b))
		window_copy_move_mouse(m);

	cs.wme = wme;
	cs.args = args;
	cs.m = m;

	cs.c = c;
	cs.s = s;
	cs.wl = wl;

	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)
				break;
			clear = window_copy_cmd_table[i].clear;
			action = window_copy_cmd_table[i].f(&cs);
			break;
		}
	}

	if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) {
		keys = options_get_number(wme->wp->window->options, "mode-keys");
		if (clear == WINDOW_COPY_CMD_CLEAR_EMACS_ONLY &&
		    keys == MODEKEY_VI)
			clear = WINDOW_COPY_CMD_CLEAR_NEVER;
		if (clear != WINDOW_COPY_CMD_CLEAR_NEVER) {
			window_copy_clear_marks(wme);
			data->searchx = data->searchy = -1;
		}
		if (action == WINDOW_COPY_CMD_NOTHING)
			action = WINDOW_COPY_CMD_REDRAW;
	}
	wme->prefix = 1;

	if (action == WINDOW_COPY_CMD_CANCEL)
		window_pane_reset_mode(wme->wp);
	else if (action == WINDOW_COPY_CMD_REDRAW)
		window_copy_redraw_screen(wme);
}

static void
window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py,
    int no_redraw)
{
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->backing->grid;
	u_int				 offset, gap;

	data->cx = px;

	if (py >= gd->hsize - data->oy && py < gd->hsize - data->oy + gd->sy)
		data->cy = py - (gd->hsize - data->oy);
	else {
		gap = gd->sy / 4;
		if (py < gd->sy) {
			offset = 0;
			data->cy = py;
		} else if (py > gd->hsize + gd->sy - gap) {
			offset = gd->hsize;
			data->cy = py - gd->hsize;
		} else {
			offset = py + gap - gd->sy;
			data->cy = py - offset;
		}
		data->oy = gd->hsize - offset;
	}

	if (!no_redraw && data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 1, 0);
	if (!no_redraw)
		window_copy_redraw_screen(wme);
}

static int
window_copy_search_compare(struct grid *gd, u_int px, u_int py,
    struct grid *sgd, u_int spx, int cis)
{
	struct grid_cell	 gc, sgc;
	const struct utf8_data	*ud, *sud;

	grid_get_cell(gd, px, py, &gc);
	ud = &gc.data;
	grid_get_cell(sgd, spx, 0, &sgc);
	sud = &sgc.data;

	if (ud->size != sud->size || ud->width != sud->width)
		return (0);

	if (cis && ud->size == 1)
		return (tolower(ud->data[0]) == sud->data[0]);

	return (memcmp(ud->data, sud->data, ud->size) == 0);
}

static int
window_copy_search_lr(struct grid *gd, struct grid *sgd, u_int *ppx, u_int py,
    u_int first, u_int last, int cis)
{
	u_int			 ax, bx, px, pywrap, endline;
	int			 matched;
	struct grid_line	*gl;

	endline = gd->hsize + gd->sy - 1;
	for (ax = first; ax < last; ax++) {
		for (bx = 0; bx < sgd->sx; bx++) {
			px = ax + bx;
			pywrap = py;
			/* Wrap line. */
			while (px >= gd->sx && pywrap < endline) {
				gl = grid_get_line(gd, pywrap);
				if (~gl->flags & GRID_LINE_WRAPPED)
					break;
				px -= gd->sx;
				pywrap++;
			}
			/* We have run off the end of the grid. */
			if (px >= gd->sx)
				break;
			matched = window_copy_search_compare(gd, px, pywrap,
			    sgd, bx, cis);
			if (!matched)
				break;
		}
		if (bx == sgd->sx) {
			*ppx = ax;
			return (1);
		}
	}
	return (0);
}

static int
window_copy_search_rl(struct grid *gd,
    struct grid *sgd, u_int *ppx, u_int py, u_int first, u_int last, int cis)
{
	u_int			 ax, bx, px, pywrap, endline;
	int			 matched;
	struct grid_line	*gl;

	endline = gd->hsize + gd->sy - 1;
	for (ax = last; ax > first; ax--) {
		for (bx = 0; bx < sgd->sx; bx++) {
			px = ax - 1 + bx;
			pywrap = py;
			/* Wrap line. */
			while (px >= gd->sx && pywrap < endline) {
				gl = grid_get_line(gd, pywrap);
				if (~gl->flags & GRID_LINE_WRAPPED)
					break;
				px -= gd->sx;
				pywrap++;
			}
			/* We have run off the end of the grid. */
			if (px >= gd->sx)
				break;
			matched = window_copy_search_compare(gd, px, pywrap,
			    sgd, bx, cis);
			if (!matched)
				break;
		}
		if (bx == sgd->sx) {
			*ppx = ax - 1;
			return (1);
		}
	}
	return (0);
}

static int
window_copy_search_lr_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
    u_int first, u_int last, regex_t *reg)
{
	int			eflags = 0;
	u_int			endline, foundx, foundy, len, pywrap, size = 1;
	char		       *buf;
	regmatch_t		regmatch;
	struct grid_line       *gl;

	/*
	 * This can happen during search if the last match was the last
	 * character on a line.
	 */
	if (first >= last)
		return (0);

	/* Set flags for regex search. */
	if (first != 0)
		eflags |= REG_NOTBOL;

	/* Need to look at the entire string. */
	buf = xmalloc(size);
	buf[0] = '\0';
	buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size);
	len = gd->sx - first;
	endline = gd->hsize + gd->sy - 1;
	pywrap = py;
	while (buf != NULL && pywrap <= endline) {
		gl = grid_get_line(gd, pywrap);
		if (~gl->flags & GRID_LINE_WRAPPED)
			break;
		pywrap++;
		buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size);
		len += gd->sx;
	}

	if (regexec(reg, buf, 1, &regmatch, eflags) == 0 &&
	    regmatch.rm_so != regmatch.rm_eo) {
		foundx = first;
		foundy = py;
		window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
		    buf + regmatch.rm_so);
		if (foundy == py && foundx < last) {
			*ppx = foundx;
			len -= foundx - first;
			window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
			    buf + regmatch.rm_eo);
			*psx = foundx;
			while (foundy > py) {
				*psx += gd->sx;
				foundy--;
			}
			*psx -= *ppx;
			free(buf);
			return (1);
		}
	}

	free(buf);
	*ppx = 0;
	*psx = 0;
	return (0);
}

static int
window_copy_search_rl_regex(struct grid *gd, u_int *ppx, u_int *psx, u_int py,
    u_int first, u_int last, regex_t *reg)
{
	int			eflags = 0;
	u_int			endline, len, pywrap, size = 1;
	char		       *buf;
	struct grid_line       *gl;

	/* Set flags for regex search. */
	if (first != 0)
		eflags |= REG_NOTBOL;

	/* Need to look at the entire string. */
	buf = xmalloc(size);
	buf[0] = '\0';
	buf = window_copy_stringify(gd, py, first, gd->sx, buf, &size);
	len = gd->sx - first;
	endline = gd->hsize + gd->sy - 1;
	pywrap = py;
	while (buf != NULL && (pywrap <= endline)) {
		gl = grid_get_line(gd, pywrap);
		if (~gl->flags & GRID_LINE_WRAPPED)
			break;
		pywrap++;
		buf = window_copy_stringify(gd, pywrap, 0, gd->sx, buf, &size);
		len += gd->sx;
	}

	if (window_copy_last_regex(gd, py, first, last, len, ppx, psx, buf,
	    reg, eflags))
	{
		free(buf);
		return (1);
	}

	free(buf);
	*ppx = 0;
	*psx = 0;
	return (0);
}

static const char *
window_copy_cellstring(const struct grid_line *gl, u_int px, size_t *size,
    int *allocated)
{
	static struct utf8_data	 ud;
	struct grid_cell_entry	*gce;
	char			*copy;

	if (px >= gl->cellsize) {
		*size = 1;
		*allocated = 0;
		return (" ");
	}

	gce = &gl->celldata[px];
	if (gce->flags & GRID_FLAG_PADDING) {
		*size = 0;
		*allocated = 0;
		return (NULL);
	}
	if (~gce->flags & GRID_FLAG_EXTENDED) {
		*size = 1;
		*allocated = 0;
		return (&gce->data.data);
	}

	utf8_to_data(gl->extddata[gce->offset].data, &ud);
	if (ud.size == 0) {
		*size = 0;
		*allocated = 0;
		return (NULL);
	}
	*size = ud.size;
	*allocated = 1;

	copy = xmalloc(ud.size);
	memcpy(copy, ud.data, ud.size);
	return (copy);
}

/* Find last match in given range. */
static int
window_copy_last_regex(struct grid *gd, u_int py, u_int first, u_int last,
    u_int len, u_int *ppx, u_int *psx, const char *buf, const regex_t *preg,
    int eflags)
{
	u_int		foundx, foundy, oldx, px = 0, savepx, savesx = 0;
	regmatch_t	regmatch;

	foundx = first;
	foundy = py;
	oldx = first;
	while (regexec(preg, buf + px, 1, &regmatch, eflags) == 0) {
		if (regmatch.rm_so == regmatch.rm_eo)
			break;
		window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
		    buf + px + regmatch.rm_so);
		if (foundy > py || foundx >= last)
			break;
		len -= foundx - oldx;
		savepx = foundx;
		window_copy_cstrtocellpos(gd, len, &foundx, &foundy,
		    buf + px + regmatch.rm_eo);
		if (foundy > py || foundx >= last) {
			*ppx = savepx;
			*psx = foundx;
			while (foundy > py) {
				*psx += gd->sx;
				foundy--;
			}
			*psx -= *ppx;
			return (1);
		} else {
			savesx = foundx - savepx;
			len -= savesx;
			oldx = foundx;
		}
		px += regmatch.rm_eo;
	}

	if (savesx > 0) {
		*ppx = savepx;
		*psx = savesx;
		return (1);
	} else {
		*ppx = 0;
		*psx = 0;
		return (0);
	}
}

/* Stringify line and append to input buffer. Caller frees. */
static char *
window_copy_stringify(struct grid *gd, u_int py, u_int first, u_int last,
    char *buf, u_int *size)
{
	u_int			 ax, bx, newsize = *size;
	const struct grid_line	*gl;
	const char		*d;
	size_t			 bufsize = 1024, dlen;
	int			 allocated;

	while (bufsize < newsize)
		bufsize *= 2;
	buf = xrealloc(buf, bufsize);

	gl = grid_peek_line(gd, py);
	bx = *size - 1;
	for (ax = first; ax < last; ax++) {
		d = window_copy_cellstring(gl, ax, &dlen, &allocated);
		newsize += dlen;
		while (bufsize < newsize) {
			bufsize *= 2;
			buf = xrealloc(buf, bufsize);
		}
		if (dlen == 1)
			buf[bx++] = *d;
		else {
			memcpy(buf + bx, d, dlen);
			bx += dlen;
		}
		if (allocated)
			free((void *)d);
	}
	buf[newsize - 1] = '\0';

	*size = newsize;
	return (buf);
}

/* Map start of C string containing UTF-8 data to grid cell position. */
static void
window_copy_cstrtocellpos(struct grid *gd, u_int ncells, u_int *ppx, u_int *ppy,
    const char *str)
{
	u_int			 cell, ccell, px, pywrap, pos, len;
	int			 match;
	const struct grid_line	*gl;
	const char		*d;
	size_t			 dlen;
	struct {
		const char	*d;
		size_t		 dlen;
		int		 allocated;
	} *cells;

	/* Populate the array of cell data. */
	cells = xreallocarray(NULL, ncells, sizeof cells[0]);
	cell = 0;
	px = *ppx;
	pywrap = *ppy;
	gl = grid_peek_line(gd, pywrap);
	while (cell < ncells) {
		cells[cell].d = window_copy_cellstring(gl, px,
		    &cells[cell].dlen, &cells[cell].allocated);
		cell++;
		px++;
		if (px == gd->sx) {
			px = 0;
			pywrap++;
			gl = grid_peek_line(gd, pywrap);
		}
	}

	/* Locate starting cell. */
	cell = 0;
	len = strlen(str);
	while (cell < ncells) {
		ccell = cell;
		pos = 0;
		match = 1;
		while (ccell < ncells) {
			if (str[pos] == '\0') {
				match = 0;
				break;
			}
			d = cells[ccell].d;
			dlen = cells[ccell].dlen;
			if (dlen == 1) {
				if (str[pos] != *d) {
					match = 0;
					break;
				}
				pos++;
			} else {
				if (dlen > len - pos)
					dlen = len - pos;
				if (memcmp(str + pos, d, dlen) != 0) {
					match = 0;
					break;
				}
				pos += dlen;
			}
			ccell++;
		}
		if (match)
			break;
		cell++;
	}

	/* If not found this will be one past the end. */
	px = *ppx + cell;
	pywrap = *ppy;
	while (px >= gd->sx) {
		px -= gd->sx;
		pywrap++;
	}

	*ppx = px;
	*ppy = pywrap;

	/* Free cell data. */
	for (cell = 0; cell < ncells; cell++) {
		if (cells[cell].allocated)
			free((void *)cells[cell].d);
	}
	free(cells);
}

static void
window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
{
	if (*fx == 0) {	/* left */
		if (*fy == 0) { /* top */
			if (wrapflag) {
				*fx = screen_size_x(s) - 1;
				*fy = screen_hsize(s) + screen_size_y(s) - 1;
			}
			return;
		}
		*fx = screen_size_x(s) - 1;
		*fy = *fy - 1;
	} else
		*fx = *fx - 1;
}

static void
window_copy_move_right(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
{
	if (*fx == screen_size_x(s) - 1) { /* right */
		if (*fy == screen_hsize(s) + screen_size_y(s) - 1) { /* bottom */
			if (wrapflag) {
				*fx = 0;
				*fy = 0;
			}
			return;
		}
		*fx = 0;
		*fy = *fy + 1;
	} else
		*fx = *fx + 1;
}

static int
window_copy_is_lowercase(const char *ptr)
{
	while (*ptr != '\0') {
		if (*ptr != tolower((u_char)*ptr))
			return (0);
		++ptr;
	}
	return (1);
}

/*
 * Handle backward wrapped regex searches with overlapping matches. In this case
 * find the longest overlapping match from previous wrapped lines.
 */
static void
window_copy_search_back_overlap(struct grid *gd, regex_t *preg, u_int *ppx,
    u_int *psx, u_int *ppy, u_int endline)
{
	u_int	endx, endy, oldendx, oldendy, px, py, sx;
	int	found = 1;

	oldendx = *ppx + *psx;
	oldendy = *ppy - 1;
	while (oldendx > gd->sx - 1) {
		oldendx -= gd->sx;
		oldendy++;
	}
	endx = oldendx;
	endy = oldendy;
	px = *ppx;
	py = *ppy;
	while (found && px == 0 && py - 1 > endline &&
	       grid_get_line(gd, py - 2)->flags & GRID_LINE_WRAPPED &&
	       endx == oldendx && endy == oldendy) {
		py--;
		found = window_copy_search_rl_regex(gd, &px, &sx, py - 1, 0,
		    gd->sx, preg);
		if (found) {
			endx = px + sx;
			endy = py - 1;
			while (endx > gd->sx - 1) {
				endx -= gd->sx;
				endy++;
			}
			if (endx == oldendx && endy == oldendy) {
				*ppx = px;
				*ppy = py;
			}
		}
	}
}

/*
 * Search for text stored in sgd starting from position fx,fy up to endline. If
 * found, jump to it. If cis then ignore case. The direction is 0 for searching
 * up, down otherwise. If wrap then go to begin/end of grid and try again if
 * not found.
 */
static int
window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd,
    struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap,
    int direction, int regex)
{
	u_int	 i, px, sx, ssize = 1;
	int	 found = 0, cflags = REG_EXTENDED;
	char	*sbuf;
	regex_t	 reg;

	if (regex) {
		sbuf = xmalloc(ssize);
		sbuf[0] = '\0';
		sbuf = window_copy_stringify(sgd, 0, 0, sgd->sx, sbuf, &ssize);
		if (cis)
			cflags |= REG_ICASE;
		if (regcomp(&reg, sbuf, cflags) != 0) {
			free(sbuf);
			return (0);
		}
		free(sbuf);
	}

	if (direction) {
		for (i = fy; i <= endline; i++) {
			if (regex) {
				found = window_copy_search_lr_regex(gd,
				    &px, &sx, i, fx, gd->sx, &reg);
			} else {
				found = window_copy_search_lr(gd, sgd,
				    &px, i, fx, gd->sx, cis);
			}
			if (found)
				break;
			fx = 0;
		}
	} else {
		for (i = fy + 1; endline < i; i--) {
			if (regex) {
				found = window_copy_search_rl_regex(gd,
				    &px, &sx, i - 1, 0, fx + 1, &reg);
				if (found) {
					window_copy_search_back_overlap(gd,
					    &reg, &px, &sx, &i, endline);
				}
			} else {
				found = window_copy_search_rl(gd, sgd,
				    &px, i - 1, 0, fx + 1, cis);
			}
			if (found) {
				i--;
				break;
			}
			fx = gd->sx - 1;
		}
	}
	if (regex)
		regfree(&reg);

	if (found) {
		window_copy_scroll_to(wme, px, i, 1);
		return (1);
	}
	if (wrap) {
		return (window_copy_search_jump(wme, gd, sgd,
		    direction ? 0 : gd->sx - 1,
		    direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0,
		    direction, regex));
	}
	return (0);
}

static void
window_copy_move_after_search_mark(struct window_copy_mode_data *data,
    u_int *fx, u_int *fy, int wrapflag)
{
	struct screen  *s = data->backing;
	u_int		at, start;

	if (window_copy_search_mark_at(data, *fx, *fy, &start) == 0 &&
	    data->searchmark[start] != 0) {
		while (window_copy_search_mark_at(data, *fx, *fy, &at) == 0) {
			if (data->searchmark[at] != data->searchmark[start])
				break;
			/* Stop if not wrapping and at the end of the grid. */
			if (!wrapflag &&
			    *fx == screen_size_x(s) - 1 &&
			    *fy == screen_hsize(s) + screen_size_y(s) - 1)
				break;

			window_copy_move_right(s, fx, fy, wrapflag);
		}
	}
}

/*
 * Search in for text searchstr. If direction is 0 then search up, otherwise
 * down.
 */
static int
window_copy_search(struct window_mode_entry *wme, int direction, int regex)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing, ss;
	struct screen_write_ctx		 ctx;
	struct grid			*gd = s->grid;
	const char			*str = data->searchstr;
	u_int				 at, endline, fx, fy, start;
	int				 cis, found, keys, visible_only;
	int				 wrapflag;

	if (regex && str[strcspn(str, "^$*+()?[].\\")] == '\0')
		regex = 0;

	data->searchdirection = direction;

	if (data->timeout)
		return (0);

	if (data->searchall || wp->searchstr == NULL ||
	    wp->searchregex != regex) {
		visible_only = 0;
		data->searchall = 0;
	} else
		visible_only = (strcmp(wp->searchstr, str) == 0);
	if (visible_only == 0 && data->searchmark != NULL)
		window_copy_clear_marks(wme);
	free(wp->searchstr);
	wp->searchstr = xstrdup(str);
	wp->searchregex = regex;

	fx = data->cx;
	fy = screen_hsize(data->backing) - data->oy + data->cy;

	screen_init(&ss, screen_write_strlen("%s", str), 1, 0);
	screen_write_start(&ctx, &ss);
	screen_write_nputs(&ctx, -1, &grid_default_cell, "%s", str);
	screen_write_stop(&ctx);

	wrapflag = options_get_number(wp->window->options, "wrap-search");
	cis = window_copy_is_lowercase(str);

	keys = options_get_number(wp->window->options, "mode-keys");

	if (direction) {
		/*
		 * Behave according to mode-keys. If it is emacs, search forward
		 * leaves the cursor after the match. If it is vi, the cursor
		 * remains at the beginning of the match, regardless of
		 * direction, which means that we need to start the next search
		 * after the term the cursor is currently on when searching
		 * forward.
		 */
		if (keys == MODEKEY_VI) {
			if (data->searchmark != NULL)
				window_copy_move_after_search_mark(data, &fx,
				    &fy, wrapflag);
			else {
				/*
				 * When there are no search marks, start the
				 * search after the current cursor position.
				 */
				window_copy_move_right(s, &fx, &fy, wrapflag);
			}
		}
		endline = gd->hsize + gd->sy - 1;
	} else {
		window_copy_move_left(s, &fx, &fy, wrapflag);
		endline = 0;
	}

	found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis,
	    wrapflag, direction, regex);
	if (found) {
		window_copy_search_marks(wme, &ss, regex, visible_only);
		fx = data->cx;
		fy = screen_hsize(data->backing) - data->oy + data->cy;

		/*
		 * When searching forward, if the cursor is not at the beginning
		 * of the mark, search again.
		 */
		if (direction &&
		    window_copy_search_mark_at(data, fx, fy, &at) == 0 &&
		    at > 0 &&
		    data->searchmark != NULL &&
		    data->searchmark[at] == data->searchmark[at - 1]) {
			window_copy_move_after_search_mark(data, &fx, &fy,
			    wrapflag);
			window_copy_search_jump(wme, gd, ss.grid, fx,
			    fy, endline, cis, wrapflag, direction,
			    regex);
			fx = data->cx;
			fy = screen_hsize(data->backing) - data->oy + data->cy;
		}

		if (direction) {
			/*
			 * When in Emacs mode, position the cursor just after
			 * the mark.
			 */
			if (keys == MODEKEY_EMACS) {
				window_copy_move_after_search_mark(data, &fx,
				    &fy, wrapflag);
				data->cx = fx;
				data->cy = fy - screen_hsize(data->backing) +
				    data-> oy;
			}
		} else {
			/*
			 * When searching backward, position the cursor at the
			 * beginning of the mark.
			 */
			if (window_copy_search_mark_at(data, fx, fy,
			        &start) == 0) {
				while (window_copy_search_mark_at(data, fx, fy,
				           &at) == 0 &&
				       data->searchmark != NULL &&
				       data->searchmark[at] ==
				           data->searchmark[start]) {
					data->cx = fx;
					data->cy = fy -
					    screen_hsize(data->backing) +
					    data-> oy;
					if (at == 0)
						break;

					window_copy_move_left(s, &fx, &fy, 0);
				}
			}
		}
	}
	window_copy_redraw_screen(wme);

	screen_free(&ss);
	return (found);
}

static void
window_copy_visible_lines(struct window_copy_mode_data *data, u_int *start,
    u_int *end)
{
	struct grid		*gd = data->backing->grid;
	const struct grid_line	*gl;

	for (*start = gd->hsize - data->oy; *start > 0; (*start)--) {
		gl = grid_peek_line(gd, (*start) - 1);
		if (~gl->flags & GRID_LINE_WRAPPED)
			break;
	}
	*end = gd->hsize - data->oy + gd->sy;
}

static int
window_copy_search_mark_at(struct window_copy_mode_data *data, u_int px,
    u_int py, u_int *at)
{
	struct screen	*s = data->backing;
	struct grid	*gd = s->grid;

	if (py < gd->hsize - data->oy)
		return (-1);
	if (py > gd->hsize - data->oy + gd->sy - 1)
		return (-1);
	*at = ((py - (gd->hsize - data->oy)) * gd->sx) + px;
	return (0);
}

static int
window_copy_search_marks(struct window_mode_entry *wme, struct screen *ssp,
    int regex, int visible_only)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing, ss;
	struct screen_write_ctx		 ctx;
	struct grid			*gd = s->grid;
	int				 found, cis, stopped = 0;
	int				 cflags = REG_EXTENDED;
	u_int				 px, py, i, b, nfound = 0, width;
	u_int				 ssize = 1, start, end;
	char				*sbuf;
	regex_t				 reg;
	uint64_t			 stop = 0, tstart, t;

	if (ssp == NULL) {
		width = screen_write_strlen("%s", data->searchstr);
		screen_init(&ss, width, 1, 0);
		screen_write_start(&ctx, &ss);
		screen_write_nputs(&ctx, -1, &grid_default_cell, "%s",
		    data->searchstr);
		screen_write_stop(&ctx);
		ssp = &ss;
	} else
		width = screen_size_x(ssp);

	cis = window_copy_is_lowercase(data->searchstr);

	if (regex) {
		sbuf = xmalloc(ssize);
		sbuf[0] = '\0';
		sbuf = window_copy_stringify(ssp->grid, 0, 0, ssp->grid->sx,
		    sbuf, &ssize);
		if (cis)
			cflags |= REG_ICASE;
		if (regcomp(&reg, sbuf, cflags) != 0) {
			free(sbuf);
			return (0);
		}
		free(sbuf);
	}
	tstart = get_timer();

	if (visible_only)
		window_copy_visible_lines(data, &start, &end);
	else {
		start = 0;
		end = gd->hsize + gd->sy;
		stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT;
	}

again:
	free(data->searchmark);
	data->searchmark = xcalloc(gd->sx, gd->sy);
	data->searchgen = 1;

	for (py = start; py < end; py++) {
		px = 0;
		for (;;) {
			if (regex) {
				found = window_copy_search_lr_regex(gd,
				    &px, &width, py, px, gd->sx, &reg);
				if (!found)
					break;
			} else {
				found = window_copy_search_lr(gd, ssp->grid,
				    &px, py, px, gd->sx, cis);
				if (!found)
					break;
			}
			nfound++;

			if (window_copy_search_mark_at(data, px, py, &b) == 0) {
				if (b + width > gd->sx * gd->sy)
					width = (gd->sx * gd->sy) - b;
				for (i = b; i < b + width; i++) {
					if (data->searchmark[i] != 0)
						continue;
					data->searchmark[i] = data->searchgen;
				}
				if (data->searchgen == UCHAR_MAX)
					data->searchgen = 1;
				else
					data->searchgen++;
			}
			px += width;
		}

		t = get_timer();
		if (t - tstart > WINDOW_COPY_SEARCH_TIMEOUT) {
			data->timeout = 1;
			break;
		}
		if (stop != 0 && t > stop) {
			stopped = 1;
			break;
		}
	}
	if (data->timeout) {
		window_copy_clear_marks(wme);
		goto out;
	}

	if (stopped && stop != 0) {
		/* Try again but just the visible context. */
		window_copy_visible_lines(data, &start, &end);
		stop = 0;
		goto again;
	}

	if (!visible_only) {
		if (stopped) {
			if (nfound > 1000)
				data->searchcount = 1000;
			else if (nfound > 100)
				data->searchcount = 100;
			else if (nfound > 10)
				data->searchcount = 10;
			else
				data->searchcount = -1;
			data->searchmore = 1;
		} else {
			data->searchcount = nfound;
			data->searchmore = 0;
		}
	}

out:
	if (ssp == &ss)
		screen_free(&ss);
	if (regex)
		regfree(&reg);
	return (1);
}

static void
window_copy_clear_marks(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;

	free(data->searchmark);
	data->searchmark = NULL;
}

static int
window_copy_search_up(struct window_mode_entry *wme, int regex)
{
	return (window_copy_search(wme, 0, regex));
}

static int
window_copy_search_down(struct window_mode_entry *wme, int regex)
{
	return (window_copy_search(wme, 1, regex));
}

static void
window_copy_goto_line(struct window_mode_entry *wme, const char *linestr)
{
	struct window_copy_mode_data	*data = wme->data;
	const char			*errstr;
	int				 lineno;

	lineno = strtonum(linestr, -1, INT_MAX, &errstr);
	if (errstr != NULL)
		return;
	if (lineno < 0 || (u_int)lineno > screen_hsize(data->backing))
		lineno = screen_hsize(data->backing);

	data->oy = lineno;
	window_copy_update_selection(wme, 1, 0);
	window_copy_redraw_screen(wme);
}

static void
window_copy_match_start_end(struct window_copy_mode_data *data, u_int at,
    u_int *start, u_int *end)
{
	struct grid	*gd = data->backing->grid;
	u_int		 last = (gd->sy * gd->sx) - 1;
	u_char		 mark = data->searchmark[at];

	*start = *end = at;
	while (*start != 0 && data->searchmark[*start] == mark)
		(*start)--;
	if (data->searchmark[*start] != mark)
		(*start)++;
	while (*end != last && data->searchmark[*end] == mark)
		(*end)++;
	if (data->searchmark[*end] != mark)
		(*end)--;
}

static char *
window_copy_match_at_cursor(struct window_copy_mode_data *data)
{
	struct grid	*gd = data->backing->grid;
	struct grid_cell gc;
	u_int		 at, start, end, cy, px, py;
	u_int		 sx = screen_size_x(data->backing);
	char		*buf = NULL;
	size_t		 len = 0;

	if (data->searchmark == NULL)
		return (NULL);

	cy = screen_hsize(data->backing) - data->oy + data->cy;
	if (window_copy_search_mark_at(data, data->cx, cy, &at) != 0)
		return (NULL);
	if (data->searchmark[at] == 0) {
		/* Allow one position after the match. */
		if (at == 0 || data->searchmark[--at] == 0)
			return (NULL);
	}
	window_copy_match_start_end(data, at, &start, &end);

	/*
	 * Cells will not be set in the marked array unless they are valid text
	 * and wrapping will be taken care of, so we can just copy.
 	 */
	for (at = start; at <= end; at++) {
		py = at / sx;
		px = at - (py * sx);

		grid_get_cell(gd, px, gd->hsize + py - data->oy, &gc);
		buf = xrealloc(buf, len + gc.data.size + 1);
		memcpy(buf + len, gc.data.data, gc.data.size);
		len += gc.data.size;
	}
	if (len != 0)
		buf[len] = '\0';
	return (buf);
}

static void
window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy,
    struct grid_cell *gc, const struct grid_cell *mgc,
    const struct grid_cell *cgc, const struct grid_cell *mkgc)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	u_int				 mark, start, end, cy, cursor, current;
	int				 inv = 0, found = 0;
	int				 keys;

	if (data->showmark && fy == data->my) {
		gc->attr = mkgc->attr;
		if (fx == data->mx)
			inv = 1;
		if (inv) {
			gc->fg = mkgc->bg;
			gc->bg = mkgc->fg;
		}
		else {
			gc->fg = mkgc->fg;
			gc->bg = mkgc->bg;
		}
	}

	if (data->searchmark == NULL)
		return;

	if (window_copy_search_mark_at(data, fx, fy, &current) != 0)
		return;
	mark = data->searchmark[current];
	if (mark == 0)
		return;

	cy = screen_hsize(data->backing) - data->oy + data->cy;
	if (window_copy_search_mark_at(data, data->cx, cy, &cursor) == 0) {
		keys = options_get_number(wp->window->options, "mode-keys");
		if (cursor != 0 &&
		    keys == MODEKEY_EMACS &&
		    data->searchdirection) {
			if (data->searchmark[cursor - 1] == mark) {
				cursor--;
				found = 1;
			}
		} else if (data->searchmark[cursor] == mark)
			found = 1;
		if (found) {
			window_copy_match_start_end(data, cursor, &start, &end);
			if (current >= start && current <= end) {
				gc->attr = cgc->attr;
				if (inv) {
					gc->fg = cgc->bg;
					gc->bg = cgc->fg;
				}
				else {
					gc->fg = cgc->fg;
					gc->bg = cgc->bg;
				}
				return;
			}
		}
	}

	gc->attr = mgc->attr;
	if (inv) {
		gc->fg = mgc->bg;
		gc->bg = mgc->fg;
	}
	else {
		gc->fg = mgc->fg;
		gc->bg = mgc->bg;
	}
}

static void
window_copy_write_one(struct window_mode_entry *wme,
    struct screen_write_ctx *ctx, u_int py, u_int fy, u_int nx,
    const struct grid_cell *mgc, const struct grid_cell *cgc,
    const struct grid_cell *mkgc)
{
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->backing->grid;
	struct grid_cell		 gc;
	u_int		 		 fx;

	screen_write_cursormove(ctx, 0, py, 0);
	for (fx = 0; fx < nx; fx++) {
		grid_get_cell(gd, fx, fy, &gc);
		if (fx + gc.data.width <= nx) {
			window_copy_update_style(wme, fx, fy, &gc, mgc, cgc,
			    mkgc);
			screen_write_cell(ctx, &gc);
		}
	}
}

static void
window_copy_write_line(struct window_mode_entry *wme,
    struct screen_write_ctx *ctx, u_int py)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct options			*oo = wp->window->options;
	struct grid_line		*gl;
	struct grid_cell		 gc, mgc, cgc, mkgc;
	char				 hdr[512], tmp[256], *t;
	size_t				 size = 0;
	u_int				 hsize = screen_hsize(data->backing);

	style_apply(&gc, oo, "mode-style", NULL);
	gc.flags |= GRID_FLAG_NOPALETTE;
	style_apply(&mgc, oo, "copy-mode-match-style", NULL);
	mgc.flags |= GRID_FLAG_NOPALETTE;
	style_apply(&cgc, oo, "copy-mode-current-match-style", NULL);
	cgc.flags |= GRID_FLAG_NOPALETTE;
	style_apply(&mkgc, oo, "copy-mode-mark-style", NULL);
	mkgc.flags |= GRID_FLAG_NOPALETTE;

	if (py == 0 && s->rupper < s->rlower && !data->hide_position) {
		gl = grid_get_line(data->backing->grid, hsize - data->oy);
		if (gl->time == 0)
			xsnprintf(tmp, sizeof tmp, "[%u/%u]", data->oy, hsize);
		else {
			t = format_pretty_time(gl->time, 1);
			xsnprintf(tmp, sizeof tmp, "%s [%u/%u]", t, data->oy,
			    hsize);
			free(t);
		}

		if (data->searchmark == NULL) {
			if (data->timeout) {
				size = xsnprintf(hdr, sizeof hdr,
				    "(timed out) %s", tmp);
			} else
				size = xsnprintf(hdr, sizeof hdr, "%s", tmp);
		} else {
			if (data->searchcount == -1)
				size = xsnprintf(hdr, sizeof hdr, "%s", tmp);
			else {
				size = xsnprintf(hdr, sizeof hdr,
				    "(%d%s results) %s", data->searchcount,
				    data->searchmore ? "+" : "", tmp);
			}
		}
		if (size > screen_size_x(s))
			size = screen_size_x(s);
		screen_write_cursormove(ctx, screen_size_x(s) - size, 0, 0);
		screen_write_puts(ctx, &gc, "%s", hdr);
	} else
		size = 0;

	if (size < screen_size_x(s)) {
		window_copy_write_one(wme, ctx, py, hsize - data->oy + py,
		    screen_size_x(s) - size, &mgc, &cgc, &mkgc);
	}

	if (py == data->cy && data->cx == screen_size_x(s)) {
		screen_write_cursormove(ctx, screen_size_x(s) - 1, py, 0);
		screen_write_putc(ctx, &grid_default_cell, '$');
	}
}

static void
window_copy_write_lines(struct window_mode_entry *wme,
    struct screen_write_ctx *ctx, u_int py, u_int ny)
{
	u_int	yy;

	for (yy = py; yy < py + ny; yy++)
		window_copy_write_line(wme, ctx, py);
}

static void
window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y)
{
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->backing->grid;
	u_int				 new_y, start, end;

	new_y = data->cy;
	if (old_y <= new_y) {
		start = old_y;
		end = new_y;
	} else {
		start = new_y;
		end = old_y;
	}

	/*
	 * In word selection mode the first word on the line below the cursor
	 * might be selected, so add this line to the redraw area.
	 */
	if (data->selflag == SEL_WORD) {
		/* Last grid line in data coordinates. */
		if (end < gd->sy + data->oy - 1)
			end++;
	}
	window_copy_redraw_lines(wme, start, end - start + 1);
}

static void
window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen_write_ctx	 	 ctx;
	u_int				 i;

	screen_write_start_pane(&ctx, wp, NULL);
	for (i = py; i < py + ny; i++)
		window_copy_write_line(wme, &ctx, i);
	screen_write_cursormove(&ctx, data->cx, data->cy, 0);
	screen_write_stop(&ctx);
}

static void
window_copy_redraw_screen(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;

	window_copy_redraw_lines(wme, 0, screen_size_y(&data->screen));
}

static void
window_copy_synchronize_cursor_end(struct window_mode_entry *wme, int begin,
    int no_reset)
{
	struct window_copy_mode_data	*data = wme->data;
	u_int				 xx, yy;

	xx = data->cx;
	yy = screen_hsize(data->backing) + data->cy - data->oy;
	switch (data->selflag) {
	case SEL_WORD:
		if (no_reset)
			break;
		begin = 0;
		if (data->dy > yy || (data->dy == yy && data->dx > xx)) {
			/* Right to left selection. */
			window_copy_cursor_previous_word_pos(wme,
			    data->separators, &xx, &yy);
			begin = 1;

			/* Reset the end. */
			data->endselx = data->endselrx;
			data->endsely = data->endselry;
		} else {
			/* Left to right selection. */
			if (xx >= window_copy_find_length(wme, yy) ||
			    !window_copy_in_set(wme, xx + 1, yy, WHITESPACE)) {
				window_copy_cursor_next_word_end_pos(wme,
				    data->separators, &xx, &yy);
			}

			/* Reset the start. */
			data->selx = data->selrx;
			data->sely = data->selry;
		}
		break;
	case SEL_LINE:
		if (no_reset)
			break;
		begin = 0;
		if (data->dy > yy) {
			/* Right to left selection. */
			xx = 0;
			begin = 1;

			/* Reset the end. */
			data->endselx = data->endselrx;
			data->endsely = data->endselry;
		} else {
			/* Left to right selection. */
			if (yy < data->endselry)
				yy = data->endselry;
			xx = window_copy_find_length(wme, yy);

			/* Reset the start. */
			data->selx = data->selrx;
			data->sely = data->selry;
		}
		break;
	case SEL_CHAR:
		break;
	}
	if (begin) {
		data->selx = xx;
		data->sely = yy;
	} else {
		data->endselx = xx;
		data->endsely = yy;
	}
}

static void
window_copy_synchronize_cursor(struct window_mode_entry *wme, int no_reset)
{
	struct window_copy_mode_data	*data = wme->data;

	switch (data->cursordrag) {
	case CURSORDRAG_ENDSEL:
		window_copy_synchronize_cursor_end(wme, 0, no_reset);
		break;
	case CURSORDRAG_SEL:
		window_copy_synchronize_cursor_end(wme, 1, no_reset);
		break;
	case CURSORDRAG_NONE:
		break;
	}
}

static void
window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct screen_write_ctx		 ctx;
	u_int				 old_cx, old_cy;

	old_cx = data->cx; old_cy = data->cy;
	data->cx = cx; data->cy = cy;
	if (old_cx == screen_size_x(s))
		window_copy_redraw_lines(wme, old_cy, 1);
	if (data->cx == screen_size_x(s))
		window_copy_redraw_lines(wme, data->cy, 1);
	else {
		screen_write_start_pane(&ctx, wp, NULL);
		screen_write_cursormove(&ctx, data->cx, data->cy, 0);
		screen_write_stop(&ctx);
	}
}

static void
window_copy_start_selection(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;

	data->selx = data->cx;
	data->sely = screen_hsize(data->backing) + data->cy - data->oy;

	data->endselx = data->selx;
	data->endsely = data->sely;

	data->cursordrag = CURSORDRAG_ENDSEL;

	window_copy_set_selection(wme, 1, 0);
}

static int
window_copy_adjust_selection(struct window_mode_entry *wme, u_int *selx,
    u_int *sely)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int 				 sx, sy, ty;
	int				 relpos;

	sx = *selx;
	sy = *sely;

	ty = screen_hsize(data->backing) - data->oy;
	if (sy < ty) {
		relpos = WINDOW_COPY_REL_POS_ABOVE;
		if (!data->rectflag)
			sx = 0;
		sy = 0;
	} else if (sy > ty + screen_size_y(s) - 1) {
		relpos = WINDOW_COPY_REL_POS_BELOW;
		if (!data->rectflag)
			sx = screen_size_x(s) - 1;
		sy = screen_size_y(s) - 1;
	} else {
		relpos = WINDOW_COPY_REL_POS_ON_SCREEN;
		sy -= ty;
	}

	*selx = sx;
	*sely = sy;
	return (relpos);
}

static int
window_copy_update_selection(struct window_mode_entry *wme, int may_redraw,
    int no_reset)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;

	if (s->sel == NULL && data->lineflag == LINE_SEL_NONE)
		return (0);
	return (window_copy_set_selection(wme, may_redraw, no_reset));
}

static int
window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
    int no_reset)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct options			*oo = wp->window->options;
	struct grid_cell		 gc;
	u_int				 sx, sy, cy, endsx, endsy;
	int				 startrelpos, endrelpos;

	window_copy_synchronize_cursor(wme, no_reset);

	/* Adjust the selection. */
	sx = data->selx;
	sy = data->sely;
	startrelpos = window_copy_adjust_selection(wme, &sx, &sy);

	/* Adjust the end of selection. */
	endsx = data->endselx;
	endsy = data->endsely;
	endrelpos = window_copy_adjust_selection(wme, &endsx, &endsy);

	/* Selection is outside of the current screen */
	if (startrelpos == endrelpos &&
	    startrelpos != WINDOW_COPY_REL_POS_ON_SCREEN) {
		screen_hide_selection(s);
		return (0);
	}

	/* Set colours and selection. */
	style_apply(&gc, oo, "mode-style", NULL);
	gc.flags |= GRID_FLAG_NOPALETTE;
	screen_set_selection(s, sx, sy, endsx, endsy, data->rectflag,
	    data->modekeys, &gc);

	if (data->rectflag && may_redraw) {
		/*
		 * Can't rely on the caller to redraw the right lines for
		 * rectangle selection - find the highest line and the number
		 * of lines, and redraw just past that in both directions
		 */
		cy = data->cy;
		if (data->cursordrag == CURSORDRAG_ENDSEL) {
			if (sy < cy)
				window_copy_redraw_lines(wme, sy, cy - sy + 1);
			else
				window_copy_redraw_lines(wme, cy, sy - cy + 1);
		} else {
			if (endsy < cy) {
				window_copy_redraw_lines(wme, endsy,
				    cy - endsy + 1);
			} else {
				window_copy_redraw_lines(wme, cy,
				    endsy - cy + 1);
			}
		}
	}

	return (1);
}

static void *
window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	char				*buf;
	size_t				 off;
	u_int				 i, xx, yy, sx, sy, ex, ey, ey_last;
	u_int				 firstsx, lastex, restex, restsx, selx;
	int				 keys;

	if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) {
		buf = window_copy_match_at_cursor(data);
		if (buf != NULL)
			*len = strlen(buf);
		else
			*len = 0;
		return (buf);
	}

	buf = xmalloc(1);
	off = 0;

	*buf = '\0';

	/*
	 * The selection extends from selx,sely to (adjusted) cx,cy on
	 * the base screen.
	 */

	/* Find start and end. */
	xx = data->endselx;
	yy = data->endsely;
	if (yy < data->sely || (yy == data->sely && xx < data->selx)) {
		sx = xx; sy = yy;
		ex = data->selx; ey = data->sely;
	} else {
		sx = data->selx; sy = data->sely;
		ex = xx; ey = yy;
	}

	/* Trim ex to end of line. */
	ey_last = window_copy_find_length(wme, ey);
	if (ex > ey_last)
		ex = ey_last;

	/*
	 * Deal with rectangle-copy if necessary; four situations: start of
	 * first line (firstsx), end of last line (lastex), start (restsx) and
	 * end (restex) of all other lines.
	 */
	xx = screen_size_x(s);

	/*
	 * Behave according to mode-keys. If it is emacs, copy like emacs,
	 * keeping the top-left-most character, and dropping the
	 * bottom-right-most, regardless of copy direction. If it is vi, also
	 * keep bottom-right-most character.
	 */
	keys = options_get_number(wp->window->options, "mode-keys");
	if (data->rectflag) {
		/*
		 * Need to ignore the column with the cursor in it, which for
		 * rectangular copy means knowing which side the cursor is on.
		 */
		if (data->cursordrag == CURSORDRAG_ENDSEL)
			selx = data->selx;
		else
			selx = data->endselx;
		if (selx < data->cx) {
			/* Selection start is on the left. */
			if (keys == MODEKEY_EMACS) {
				lastex = data->cx;
				restex = data->cx;
			}
			else {
				lastex = data->cx + 1;
				restex = data->cx + 1;
			}
			firstsx = selx;
			restsx = selx;
		} else {
			/* Cursor is on the left. */
			lastex = selx + 1;
			restex = selx + 1;
			firstsx = data->cx;
			restsx = data->cx;
		}
	} else {
		if (keys == MODEKEY_EMACS)
			lastex = ex;
		else
			lastex = ex + 1;
		restex = xx;
		firstsx = sx;
		restsx = 0;
	}

	/* Copy the lines. */
	for (i = sy; i <= ey; i++) {
		window_copy_copy_line(wme, &buf, &off, i,
		    (i == sy ? firstsx : restsx),
		    (i == ey ? lastex : restex));
	}

	/* Don't bother if no data. */
	if (off == 0) {
		free(buf);
		*len = 0;
		return (NULL);
	}
	 /* Remove final \n (unless at end in vi mode). */
	if (keys == MODEKEY_EMACS || lastex <= ey_last) {
		if (~grid_get_line(data->backing->grid, ey)->flags &
		    GRID_LINE_WRAPPED || lastex != ey_last)
		off -= 1;
	}
	*len = off;
	return (buf);
}

static void
window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
    void *buf, size_t len)
{
	struct window_pane	*wp = wme->wp;
	struct screen_write_ctx	 ctx;

	if (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);
}

static void *
window_copy_pipe_run(struct window_mode_entry *wme, struct session *s,
    const char *cmd, size_t *len)
{
	void		*buf;
	struct job	*job;

	buf = window_copy_get_selection(wme, len);
	if (cmd == NULL || *cmd == '\0')
		cmd = options_get_string(global_options, "copy-command");
	if (cmd != NULL && *cmd != '\0') {
		job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL,
		    NULL, JOB_NOWAIT, -1, -1);
		bufferevent_write(job_get_event(job), buf, *len);
	}
	return (buf);
}

static void
window_copy_pipe(struct window_mode_entry *wme, struct session *s,
    const char *cmd)
{
	size_t	len;

	window_copy_pipe_run(wme, s, cmd, &len);
}

static void
window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s,
    const char *prefix, const char *cmd)
{
	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);
}

static void
window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix)
{
	char	*buf;
	size_t	 len;

	buf = window_copy_get_selection(wme, &len);
	if (buf != NULL)
		window_copy_copy_buffer(wme, prefix, buf, len);
}

static void
window_copy_append_selection(struct window_mode_entry *wme)
{
	struct window_pane		*wp = wme->wp;
	char				*buf;
	struct paste_buffer		*pb;
	const char			*bufdata, *bufname = NULL;
	size_t				 len, bufsize;
	struct screen_write_ctx		 ctx;

	buf = window_copy_get_selection(wme, &len);
	if (buf == NULL)
		return;

	if (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);
	}

	pb = paste_get_top(&bufname);
	if (pb != NULL) {
		bufdata = paste_buffer_data(pb, &bufsize);
		buf = xrealloc(buf, len + bufsize);
		memmove(buf + bufsize, buf, len);
		memcpy(buf, bufdata, bufsize);
		len += bufsize;
	}
	if (paste_set(buf, len, bufname, NULL) != 0)
		free(buf);
}

static void
window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off,
    u_int sy, u_int sx, u_int ex)
{
	struct window_copy_mode_data	*data = wme->data;
	struct grid			*gd = data->backing->grid;
	struct grid_cell		 gc;
	struct grid_line		*gl;
	struct utf8_data		 ud;
	u_int				 i, xx, wrapped = 0;
	const char			*s;

	if (sx > ex)
		return;

	/*
	 * Work out if the line was wrapped at the screen edge and all of it is
	 * on screen.
	 */
	gl = grid_get_line(gd, sy);
	if (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx)
		wrapped = 1;

	/* If the line was wrapped, don't strip spaces (use the full length). */
	if (wrapped)
		xx = gl->cellsize;
	else
		xx = window_copy_find_length(wme, sy);
	if (ex > xx)
		ex = xx;
	if (sx > xx)
		sx = xx;

	if (sx < ex) {
		for (i = sx; i < ex; i++) {
			grid_get_cell(gd, i, sy, &gc);
			if (gc.flags & GRID_FLAG_PADDING)
				continue;
			utf8_copy(&ud, &gc.data);
			if (ud.size == 1 && (gc.attr & GRID_ATTR_CHARSET)) {
				s = tty_acs_get(NULL, ud.data[0]);
				if (s != NULL && strlen(s) <= sizeof ud.data) {
					ud.size = strlen(s);
					memcpy(ud.data, s, ud.size);
				}
			}

			*buf = xrealloc(*buf, (*off) + ud.size);
			memcpy(*buf + *off, ud.data, ud.size);
			*off += ud.size;
		}
	}

	/* Only add a newline if the line wasn't wrapped. */
	if (!wrapped || ex != xx) {
		*buf = xrealloc(*buf, (*off) + 1);
		(*buf)[(*off)++] = '\n';
	}
}

static void
window_copy_clear_selection(struct window_mode_entry *wme)
{
	struct window_copy_mode_data   *data = wme->data;
	u_int				px, py;

	screen_clear_selection(&data->screen);

	data->cursordrag = CURSORDRAG_NONE;
	data->lineflag = LINE_SEL_NONE;
	data->selflag = SEL_CHAR;

	py = screen_hsize(data->backing) + data->cy - data->oy;
	px = window_copy_find_length(wme, py);
	if (data->cx > px)
		window_copy_update_cursor(wme, px, data->cy);
}

static int
window_copy_in_set(struct window_mode_entry *wme, u_int px, u_int py,
    const char *set)
{
	struct window_copy_mode_data	*data = wme->data;
	struct grid_cell		 gc;

	grid_get_cell(data->backing->grid, px, py, &gc);
	if (gc.flags & GRID_FLAG_PADDING)
		return (0);
	return (utf8_cstrhas(set, &gc.data));
}

static u_int
window_copy_find_length(struct window_mode_entry *wme, u_int py)
{
	struct window_copy_mode_data	*data = wme->data;

	return (grid_line_length(data->backing->grid, py));
}

static void
window_copy_cursor_start_of_line(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_start_of_line(&gr, 1);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
}

static void
window_copy_cursor_back_to_indentation(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_back_to_indentation(&gr);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
}

static void
window_copy_cursor_end_of_line(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py =  hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	if (data->screen.sel != NULL && data->rectflag)
		grid_reader_cursor_end_of_line(&gr, 1, 1);
	else
		grid_reader_cursor_end_of_line(&gr, 1, 0);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
	    data->oy, oldy, px, py, 0);
}

static void
window_copy_other_end(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 selx, sely, cy, yy, hsize;

	if (s->sel == NULL && data->lineflag == LINE_SEL_NONE)
		return;

	if (data->lineflag == LINE_SEL_LEFT_RIGHT)
		data->lineflag = LINE_SEL_RIGHT_LEFT;
	else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
		data->lineflag = LINE_SEL_LEFT_RIGHT;

	switch (data->cursordrag) {
		case CURSORDRAG_NONE:
		case CURSORDRAG_SEL:
			data->cursordrag = CURSORDRAG_ENDSEL;
			break;
		case CURSORDRAG_ENDSEL:
			data->cursordrag = CURSORDRAG_SEL;
			break;
	}

	selx = data->endselx;
	sely = data->endsely;
	if (data->cursordrag == CURSORDRAG_SEL) {
		selx = data->selx;
		sely = data->sely;
	}

	cy = data->cy;
	yy = screen_hsize(data->backing) + data->cy - data->oy;

	data->cx = selx;

	hsize = screen_hsize(data->backing);
	if (sely < hsize - data->oy) { /* above */
		data->oy = hsize - sely;
		data->cy = 0;
	} else if (sely > hsize - data->oy + screen_size_y(s)) { /* below */
		data->oy = hsize - sely + screen_size_y(s) - 1;
		data->cy = screen_size_y(s) - 1;
	} else
		data->cy = cy + sely - yy;

	window_copy_update_selection(wme, 1, 1);
	window_copy_redraw_screen(wme);
}

static void
window_copy_cursor_left(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_left(&gr, 1);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
}

static void
window_copy_cursor_right(struct window_mode_entry *wme, int all)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_right(&gr, 1, all);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
	    data->oy, oldy, px, py, 0);
}

static void
window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 ox, oy, px, py;
	int				 norectsel;

	norectsel = data->screen.sel == NULL || !data->rectflag;
	oy = screen_hsize(data->backing) + data->cy - data->oy;
	ox = window_copy_find_length(wme, oy);
	if (norectsel && data->cx != ox) {
		data->lastcx = data->cx;
		data->lastsx = ox;
	}

	if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely)
		window_copy_other_end(wme);

	if (scroll_only || data->cy == 0) {
		if (norectsel)
			data->cx = data->lastcx;
		window_copy_scroll_down(wme, 1);
		if (scroll_only) {
			if (data->cy == screen_size_y(s) - 1)
				window_copy_redraw_lines(wme, data->cy, 1);
			else
				window_copy_redraw_lines(wme, data->cy, 2);
		}
	} else {
		if (norectsel) {
			window_copy_update_cursor(wme, data->lastcx,
			    data->cy - 1);
		} else
			window_copy_update_cursor(wme, data->cx, data->cy - 1);
		if (window_copy_update_selection(wme, 1, 0)) {
			if (data->cy == screen_size_y(s) - 1)
				window_copy_redraw_lines(wme, data->cy, 1);
			else
				window_copy_redraw_lines(wme, data->cy, 2);
		}
	}

	if (norectsel) {
		py = screen_hsize(data->backing) + data->cy - data->oy;
		px = window_copy_find_length(wme, py);
		if ((data->cx >= data->lastsx && data->cx != px) ||
		    data->cx > px)
		{
			window_copy_update_cursor(wme, px, data->cy);
			if (window_copy_update_selection(wme, 1, 0))
				window_copy_redraw_lines(wme, data->cy, 1);
		}
	}

	if (data->lineflag == LINE_SEL_LEFT_RIGHT)
	{
		py = screen_hsize(data->backing) + data->cy - data->oy;
		if (data->rectflag)
			px = screen_size_x(data->backing);
		else
			px = window_copy_find_length(wme, py);
		window_copy_update_cursor(wme, px, data->cy);
		if (window_copy_update_selection(wme, 1, 0))
			window_copy_redraw_lines(wme, data->cy, 1);
	}
	else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
	{
		window_copy_update_cursor(wme, 0, data->cy);
		if (window_copy_update_selection(wme, 1, 0))
			window_copy_redraw_lines(wme, data->cy, 1);
	}
}

static void
window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	u_int				 ox, oy, px, py;
	int				 norectsel;

	norectsel = data->screen.sel == NULL || !data->rectflag;
	oy = screen_hsize(data->backing) + data->cy - data->oy;
	ox = window_copy_find_length(wme, oy);
	if (norectsel && data->cx != ox) {
		data->lastcx = data->cx;
		data->lastsx = ox;
	}

	if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely)
		window_copy_other_end(wme);

	if (scroll_only || data->cy == screen_size_y(s) - 1) {
		if (norectsel)
			data->cx = data->lastcx;
		window_copy_scroll_up(wme, 1);
		if (scroll_only && data->cy > 0)
			window_copy_redraw_lines(wme, data->cy - 1, 2);
	} else {
		if (norectsel) {
			window_copy_update_cursor(wme, data->lastcx,
			    data->cy + 1);
		} else
			window_copy_update_cursor(wme, data->cx, data->cy + 1);
		if (window_copy_update_selection(wme, 1, 0))
			window_copy_redraw_lines(wme, data->cy - 1, 2);
	}

	if (norectsel) {
		py = screen_hsize(data->backing) + data->cy - data->oy;
		px = window_copy_find_length(wme, py);
		if ((data->cx >= data->lastsx && data->cx != px) ||
		    data->cx > px)
		{
			window_copy_update_cursor(wme, px, data->cy);
			if (window_copy_update_selection(wme, 1, 0))
				window_copy_redraw_lines(wme, data->cy, 1);
		}
	}

	if (data->lineflag == LINE_SEL_LEFT_RIGHT)
	{
		py = screen_hsize(data->backing) + data->cy - data->oy;
		if (data->rectflag)
			px = screen_size_x(data->backing);
		else
			px = window_copy_find_length(wme, py);
		window_copy_update_cursor(wme, px, data->cy);
		if (window_copy_update_selection(wme, 1, 0))
			window_copy_redraw_lines(wme, data->cy, 1);
	}
	else if (data->lineflag == LINE_SEL_RIGHT_LEFT)
	{
		window_copy_update_cursor(wme, 0, data->cy);
		if (window_copy_update_selection(wme, 1, 0))
			window_copy_redraw_lines(wme, data->cy, 1);
	}
}

static void
window_copy_cursor_jump(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx + 1;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	if (grid_reader_cursor_jump(&gr, data->jumpchar)) {
		grid_reader_get_cursor(&gr, &px, &py);
		window_copy_acquire_cursor_down(wme, hsize,
		    screen_size_y(back_s), data->oy, oldy, px, py, 0);
	}
}

static void
window_copy_cursor_jump_back(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_left(&gr, 0);
	if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
		grid_reader_get_cursor(&gr, &px, &py);
		window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px,
		    py);
	}
}

static void
window_copy_cursor_jump_to(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx + 2;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	if (grid_reader_cursor_jump(&gr, data->jumpchar)) {
		grid_reader_cursor_left(&gr, 1);
		grid_reader_get_cursor(&gr, &px, &py);
		window_copy_acquire_cursor_down(wme, hsize,
		    screen_size_y(back_s), data->oy, oldy, px, py, 0);
	}
}

static void
window_copy_cursor_jump_to_back(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_left(&gr, 0);
	grid_reader_cursor_left(&gr, 0);
	if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
		grid_reader_cursor_right(&gr, 1, 0);
		grid_reader_get_cursor(&gr, &px, &py);
		window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px,
		    py);
	}
}

static void
window_copy_cursor_next_word(struct window_mode_entry *wme,
    const char *separators)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py =  hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_next_word(&gr, separators);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
	    data->oy, oldy, px, py, 0);
}

/* Compute the next place where a word ends. */
static void
window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme,
    const char *separators, u_int *ppx, u_int *ppy)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct options			*oo = wp->window->options;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py =  hsize + data->cy - data->oy;

	grid_reader_start(&gr, back_s->grid, px, py);
	if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
		if (!grid_reader_in_set(&gr, WHITESPACE))
			grid_reader_cursor_right(&gr, 0, 0);
		grid_reader_cursor_next_word_end(&gr, separators);
		grid_reader_cursor_left(&gr, 1);
	} else
		grid_reader_cursor_next_word_end(&gr, separators);
	grid_reader_get_cursor(&gr, &px, &py);
	*ppx = px;
	*ppy = py;
}

/* Move to the next place where a word ends. */
static void
window_copy_cursor_next_word_end(struct window_mode_entry *wme,
    const char *separators, int no_reset)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct options			*oo = wp->window->options;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py =  hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	if (options_get_number(oo, "mode-keys") == MODEKEY_VI) {
		if (!grid_reader_in_set(&gr, WHITESPACE))
			grid_reader_cursor_right(&gr, 0, 0);
		grid_reader_cursor_next_word_end(&gr, separators);
		grid_reader_cursor_left(&gr, 1);
	} else
		grid_reader_cursor_next_word_end(&gr, separators);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
	    data->oy, oldy, px, py, no_reset);
}

/* Compute the previous place where a word begins. */
static void
window_copy_cursor_previous_word_pos(struct window_mode_entry *wme,
    const char *separators, u_int *ppx, u_int *ppy)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, hsize;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_previous_word(&gr, separators, /* already= */ 0,
        /* stop_at_eol= */ 1);
	grid_reader_get_cursor(&gr, &px, &py);
	*ppx = px;
	*ppy = py;
}

/* Move to the previous place where a word begins. */
static void
window_copy_cursor_previous_word(struct window_mode_entry *wme,
    const char *separators, int already)
{
	struct window_copy_mode_data	*data = wme->data;
	struct window			*w = wme->wp->window;
	struct screen			*back_s = data->backing;
	struct grid_reader		 gr;
	u_int				 px, py, oldy, hsize;
	int				 stop_at_eol;

	if (options_get_number(w->options, "mode-keys") == MODEKEY_EMACS)
		stop_at_eol = 1;
	else
		stop_at_eol = 0;

	px = data->cx;
	hsize = screen_hsize(back_s);
	py = hsize + data->cy - data->oy;
	oldy = data->cy;

	grid_reader_start(&gr, back_s->grid, px, py);
	grid_reader_cursor_previous_word(&gr, separators, already, stop_at_eol);
	grid_reader_get_cursor(&gr, &px, &py);
	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
}

static void
window_copy_cursor_prompt(struct window_mode_entry *wme, int direction,
    const char *args)
{
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = data->backing;
	struct grid			*gd = s->grid;
	u_int				 end_line;
	u_int				 line = gd->hsize - data->oy + data->cy;
	int				 add, line_flag;

	if (args != NULL && strcmp(args, "-o") == 0)
		line_flag = GRID_LINE_START_OUTPUT;
	else
		line_flag = GRID_LINE_START_PROMPT;

	if (direction == 0) { /* up */
		add = -1;
		end_line = 0;
	} else { /* down */
		add = 1;
		end_line = gd->hsize + gd->sy - 1;
	}

	if (line == end_line)
		return;
	for (;;) {
		if (line == end_line)
			return;
		line += add;

		if (grid_get_line(gd, line)->flags & line_flag)
			break;
	}

	data->cx = 0;
	if (line > gd->hsize) {
		data->cy = line - gd->hsize;
		data->oy = 0;
	} else {
		data->cy = 0;
		data->oy = gd->hsize - line;
	}

	window_copy_update_selection(wme, 1, 0);
	window_copy_redraw_screen(wme);
}

static void
window_copy_scroll_up(struct window_mode_entry *wme, u_int ny)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct screen_write_ctx		 ctx;

	if (data->oy < ny)
		ny = data->oy;
	if (ny == 0)
		return;
	data->oy -= ny;

	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 0, 0);

	screen_write_start_pane(&ctx, wp, NULL);
	screen_write_cursormove(&ctx, 0, 0, 0);
	screen_write_deleteline(&ctx, ny, 8);
	window_copy_write_lines(wme, &ctx, screen_size_y(s) - ny, ny);
	window_copy_write_line(wme, &ctx, 0);
	if (screen_size_y(s) > 1)
		window_copy_write_line(wme, &ctx, 1);
	if (screen_size_y(s) > 3)
		window_copy_write_line(wme, &ctx, screen_size_y(s) - 2);
	if (s->sel != NULL && screen_size_y(s) > ny)
		window_copy_write_line(wme, &ctx, screen_size_y(s) - ny - 1);
	screen_write_cursormove(&ctx, data->cx, data->cy, 0);
	screen_write_stop(&ctx);
}

static void
window_copy_scroll_down(struct window_mode_entry *wme, u_int ny)
{
	struct window_pane		*wp = wme->wp;
	struct window_copy_mode_data	*data = wme->data;
	struct screen			*s = &data->screen;
	struct screen_write_ctx		 ctx;

	if (ny > screen_hsize(data->backing))
		return;

	if (data->oy > screen_hsize(data->backing) - ny)
		ny = screen_hsize(data->backing) - data->oy;
	if (ny == 0)
		return;
	data->oy += ny;

	if (data->searchmark != NULL && !data->timeout)
		window_copy_search_marks(wme, NULL, data->searchregex, 1);
	window_copy_update_selection(wme, 0, 0);

	screen_write_start_pane(&ctx, wp, NULL);
	screen_write_cursormove(&ctx, 0, 0, 0);
	screen_write_insertline(&ctx, ny, 8);
	window_copy_write_lines(wme, &ctx, 0, ny);
	if (s->sel != NULL && screen_size_y(s) > ny)
		window_copy_write_line(wme, &ctx, ny);
	else if (ny == 1) /* nuke position */
		window_copy_write_line(wme, &ctx, 1);
	screen_write_cursormove(&ctx, data->cx, data->cy, 0);
	screen_write_stop(&ctx);
}

static void
window_copy_rectangle_set(struct window_mode_entry *wme, int rectflag)
{
	struct window_copy_mode_data	*data = wme->data;
	u_int				 px, py;

	data->rectflag = rectflag;

	py = screen_hsize(data->backing) + data->cy - data->oy;
	px = window_copy_find_length(wme, py);
	if (data->cx > px)
		window_copy_update_cursor(wme, px, data->cy);

	window_copy_update_selection(wme, 1, 0);
	window_copy_redraw_screen(wme);
}

static void
window_copy_move_mouse(struct mouse_event *m)
{
	struct window_pane		*wp;
	struct window_mode_entry	*wme;
	u_int				 x, y;

	wp = cmd_mouse_pane(m, NULL, NULL);
	if (wp == NULL)
		return;
	wme = TAILQ_FIRST(&wp->modes);
	if (wme == NULL)
		return;
	if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
		return;

	if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
		return;

	window_copy_update_cursor(wme, x, y);
}

void
window_copy_start_drag(struct client *c, struct mouse_event *m)
{
	struct window_pane		*wp;
	struct window_mode_entry	*wme;
	struct window_copy_mode_data	*data;
	u_int				 x, y, yg;

	if (c == NULL)
		return;

	wp = cmd_mouse_pane(m, NULL, NULL);
	if (wp == NULL)
		return;
	wme = TAILQ_FIRST(&wp->modes);
	if (wme == NULL)
		return;
	if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
		return;

	if (cmd_mouse_at(wp, m, &x, &y, 1) != 0)
		return;

	c->tty.mouse_drag_update = window_copy_drag_update;
	c->tty.mouse_drag_release = window_copy_drag_release;

	data = wme->data;
	yg = screen_hsize(data->backing) + y - data->oy;
	if (x < data->selrx || x > data->endselrx || yg != data->selry)
		data->selflag = SEL_CHAR;
	switch (data->selflag) {
	case SEL_WORD:
		if (data->separators != NULL) {
			window_copy_update_cursor(wme, x, y);
			window_copy_cursor_previous_word_pos(wme,
			    data->separators, &x, &y);
			y -= screen_hsize(data->backing) - data->oy;
		}
		window_copy_update_cursor(wme, x, y);
		break;
	case SEL_LINE:
		window_copy_update_cursor(wme, 0, y);
		break;
	case SEL_CHAR:
		window_copy_update_cursor(wme, x, y);
		window_copy_start_selection(wme);
		break;
	}

	window_copy_redraw_screen(wme);
	window_copy_drag_update(c, m);
}

static void
window_copy_drag_update(struct client *c, struct mouse_event *m)
{
	struct window_pane		*wp;
	struct window_mode_entry	*wme;
	struct window_copy_mode_data	*data;
	u_int				 x, y, old_cx, old_cy;
	struct timeval			 tv = {
		.tv_usec = WINDOW_COPY_DRAG_REPEAT_TIME
	};

	if (c == NULL)
		return;

	wp = cmd_mouse_pane(m, NULL, NULL);
	if (wp == NULL)
		return;
	wme = TAILQ_FIRST(&wp->modes);
	if (wme == NULL)
		return;
	if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
		return;

	data = wme->data;
	evtimer_del(&data->dragtimer);

	if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
		return;
	old_cx = data->cx;
	old_cy = data->cy;

	window_copy_update_cursor(wme, x, y);
	if (window_copy_update_selection(wme, 1, 0))
		window_copy_redraw_selection(wme, old_cy);
	if (old_cy != data->cy || old_cx == data->cx) {
		if (y == 0) {
			evtimer_add(&data->dragtimer, &tv);
			window_copy_cursor_up(wme, 1);
		} else if (y == screen_size_y(&data->screen) - 1) {
			evtimer_add(&data->dragtimer, &tv);
			window_copy_cursor_down(wme, 1);
		}
	}
}

static void
window_copy_drag_release(struct client *c, struct mouse_event *m)
{
	struct window_pane		*wp;
	struct window_mode_entry	*wme;
	struct window_copy_mode_data	*data;

	if (c == NULL)
		return;

	wp = cmd_mouse_pane(m, NULL, NULL);
	if (wp == NULL)
		return;
	wme = TAILQ_FIRST(&wp->modes);
	if (wme == NULL)
		return;
	if (wme->mode != &window_copy_mode && wme->mode != &window_view_mode)
		return;

	data = wme->data;
	evtimer_del(&data->dragtimer);
}

static void
window_copy_jump_to_mark(struct window_mode_entry *wme)
{
	struct window_copy_mode_data	*data = wme->data;
	u_int				 tmx, tmy;

	tmx = data->cx;
	tmy = screen_hsize(data->backing) + data->cy - data->oy;
	data->cx = data->mx;
	if (data->my < screen_hsize(data->backing)) {
		data->cy = 0;
		data->oy = screen_hsize(data->backing) - data->my;
	} else {
		data->cy = data->my - screen_hsize(data->backing);
		data->oy = 0;
	}
	data->mx = tmx;
	data->my = tmy;
	data->showmark = 1;
	window_copy_update_selection(wme, 0, 0);
	window_copy_redraw_screen(wme);
}

/* Scroll up if the cursor went off the visible screen. */
static void
window_copy_acquire_cursor_up(struct window_mode_entry *wme, u_int hsize,
    u_int oy, u_int oldy, u_int px, u_int py)
{
	u_int	cy, yy, ny, nd;

	yy = hsize - oy;
	if (py < yy) {
		ny = yy - py;
		cy = 0;
		nd = 1;
	} else {
		ny = 0;
		cy = py - yy;
		nd = oldy - cy + 1;
	}
	while (ny > 0) {
		window_copy_cursor_up(wme, 1);
		ny--;
	}
	window_copy_update_cursor(wme, px, cy);
	if (window_copy_update_selection(wme, 1, 0))
		window_copy_redraw_lines(wme, cy, nd);
}

/* Scroll down if the cursor went off the visible screen. */
static void
window_copy_acquire_cursor_down(struct window_mode_entry *wme, u_int hsize,
    u_int sy, u_int oy, u_int oldy, u_int px, u_int py, int no_reset)
{
	u_int	cy, yy, ny, nd;

	cy = py - hsize + oy;
	yy = sy - 1;
	if (cy > yy) {
		ny = cy - yy;
		oldy = yy;
		nd = 1;
	} else {
		ny = 0;
		nd = cy - oldy + 1;
	}
	while (ny > 0) {
	  window_copy_cursor_down(wme, 1);
	  ny--;
	}
	if (cy > yy)
		window_copy_update_cursor(wme, px, yy);
	else
		window_copy_update_cursor(wme, px, cy);
	if (window_copy_update_selection(wme, 1, no_reset))
		window_copy_redraw_lines(wme, oldy, nd);
}