On 2014-05-09 14:19 +0100, Nicholas Marriott wrote:
> Well, in practice we need to have both copy mode and mouse support for
> it, and if that is the case we might as well have mouse support work
> well. IIRC xtmux already has code to do this, I looked at it before
> but never got round to applying it.

This got me thinking. I'd actually like to have this feature! :)

So I've looked into xtmux and it seems to have small annoyances. Like
when you double click on the first letter of the word, then it actually
starts from the previous word. Or when select a word/line via
double/triple click and then drag the mouse the line above then the
actual word you selected won't be in the selection.

So inspired by xtmux's patches[1,2,3] I've created a slightly more
complicated patch for myself. It would be nice to see the official
version also supporting this. I believe this patch covers more edge
cases than xtmux and feels more natural like the behavior in modern
editors.

I've attached the patch and it contains the description of the new
behavior. I'll avoid repeating it here. Let me know if you want things
changed in case you are considering applying this.

Thanks!

[1] https://github.com/dylex/xtmux/commit/14ad1
[2] https://github.com/dylex/xtmux/commit/32f78
[3] https://github.com/dylex/xtmux/commit/2dd72

-- 
Balazs
>From b0a4d4820bd281a70520a588c6890f4f70b30931 Mon Sep 17 00:00:00 2001
From: Balazs Kezes <rlblas...@gmail.com>
Date: Sat, 17 May 2014 10:47:33 +0100
Subject: [PATCH] Implement new mouse selection mechanism

Left button starts the selection. You can double/triple click in order to select
full words/lines. Right button will extend the selection from the original
starting point to the mouse position. You can quit the copy mode via the middle
button which will copy the current selection. Note that the word separating
characters are coming from the "word-separators" option.
---
 CHANGES       |   6 +++
 input-keys.c  |  22 ++++++---
 tmux.1        |   6 +++
 window-copy.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
 4 files changed, 165 insertions(+), 24 deletions(-)

diff --git a/CHANGES b/CHANGES
index abd1ac0..3bfc8bd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,12 @@ Normal Changes
 
 * Fix crash due to uninitialized lastwp member of layout_cell
 * Fix -fg/-bg/-style with 256 colour terminals.  
+* New mouse selection mechanism: Left button starts the selection. You can
+  double/triple click in order to select full words/lines. Right button will
+  extend the selection from the original starting point to the mouse position.
+  You can quit the copy mode via the middle button which will copy the current
+  selection. Note that the word separating characters are coming from the
+  "word-separators" option.
 
 CHANGES FROM 1.8 to 1.9, 20 February 2014
 
diff --git a/input-keys.c b/input-keys.c
index 7531f22..e83050f 100644
--- a/input-keys.c
+++ b/input-keys.c
@@ -205,6 +205,7 @@ input_mouse(struct window_pane *wp, struct session *s, struct mouse_event *m)
 	size_t			 len;
 	struct paste_buffer	*pb;
 	u_int			 i;
+	int			 not_wheel;
 
 	/*
 	 * If the alternate screen is active and hasn't enabled the mouse, send
@@ -252,15 +253,20 @@ input_mouse(struct window_pane *wp, struct session *s, struct mouse_event *m)
 		return;
 	}
 
-	if (m->button == 1 && (m->event & MOUSE_EVENT_CLICK) &&
-	    options_get_number(&wp->window->options, "mode-mouse") == 1) {
-		pb = paste_get_top();
-		if (pb != NULL) {
-			paste_send_pane(pb, wp, "\r",
-			    wp->screen->mode & MODE_BRACKETPASTE);
+	if (options_get_number(&wp->window->options, "mode-mouse") != 1)
+		return;
+
+	/* m->button is not updated in case of wheel events. */
+	not_wheel = (~m->event & MOUSE_EVENT_WHEEL);
+	if (wp->mode == NULL && not_wheel && m->button == 1) {
+		if (m->event & MOUSE_EVENT_DOWN) {
+			pb = paste_get_top();
+			if (pb != NULL) {
+				paste_send_pane(pb, wp, "\r",
+					wp->screen->mode & MODE_BRACKETPASTE);
+			}
 		}
-	} else if (m->button != 1 &&
-	    options_get_number(&wp->window->options, "mode-mouse") == 1) {
+	} else {
 		if (window_pane_set_mode(wp, &window_copy_mode) == 0) {
 			window_copy_init_from_pane(wp);
 			if (wp->mode->mouse != NULL)
diff --git a/tmux.1 b/tmux.1
index 30447ed..5b537ff 100644
--- a/tmux.1
+++ b/tmux.1
@@ -2821,6 +2821,12 @@ If set to
 .Em copy-mode ,
 the mouse behaves as set to on, but cannot be used to enter copy
 mode.
+
+When in copy mode left button starts the selection. You can double/triple click
+in order to select full words/lines. Right button will extend the selection from
+the original starting point to the mouse position. You can quit the copy mode
+via the middle button which will copy the current selection. Note that the word
+separating characters are coming from the "word-separators" option.
 .Pp
 .It Ic mode-style Ar style
 Set window modes style.
diff --git a/window-copy.c b/window-copy.c
index ac29e6d..9c6db1a 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -154,6 +154,11 @@ struct window_copy_mode_data {
 
 	enum window_copy_input_type jumptype;
 	char		jumpchar;
+
+	int		mouse_clicks; /* number of clicks at selection start */
+	u_int		mouse_lx, mouse_ly; /* the last reported event */
+	u_int		mouse_sx, mouse_sy; /* the starting point of dragging */
+	u_int		mouse_x0, mouse_x1; /* possible starting points */
 };
 
 struct screen *
@@ -869,13 +874,65 @@ window_copy_key_numeric_prefix(struct window_pane *wp, int key)
 	return (0);
 }
 
+static void
+window_copy_select_via_mouse(
+	struct window_pane *wp, struct mouse_event *m, const char *sep)
+{
+	struct window_copy_mode_data	*data = wp->modedata;
+	int				 dir;
+	u_int				 my, by;
+
+	/*
+	 * Based on where the user clicked when they started the selection and
+	 * where is the current mouse position, extend the selection. Care needs
+	 * to be taken to ensure character/word/line modes are respected.
+	 */
+
+	/* Make sure the starting place is correct. */
+	window_copy_update_cursor(wp, m->x, m->y);
+	my = data->mouse_sy;
+	by = screen_hsize(data->backing) + data->cy - data->oy;
+	if (by < my || (by == my && data->cx < data->mouse_sx)) {
+		dir = -1;
+		data->selx = data->mouse_x1;
+	} else {
+		dir = +1;
+		data->selx = data->mouse_x0;
+	}
+
+	if (data->mouse_clicks == 1) {
+		window_copy_update_cursor(wp, m->x, m->y);
+		if (window_copy_update_selection(wp, 1))
+			window_copy_redraw_screen(wp);
+	} else if (data->mouse_clicks == 2) {
+		if (dir < 0) {
+			data->cx += 1; /* Handle first letter clicks. */
+			window_copy_cursor_previous_word(wp, sep);
+		} else {
+			if (data->cx > 0)
+				data->cx -= 1; /* Handle last letter clicks. */
+			window_copy_cursor_next_word_end(wp, sep);
+		}
+	} else if (data->mouse_clicks == 3) {
+		if (dir < 0)
+			window_copy_cursor_start_of_line(wp);
+		else
+			window_copy_cursor_end_of_line(wp);
+	}
+
+	if (window_copy_update_selection(wp, 1))
+		window_copy_redraw_screen(wp);
+}
+
 void
 window_copy_mouse(
     struct window_pane *wp, struct session *sess, struct mouse_event *m)
 {
 	struct window_copy_mode_data	*data = wp->modedata;
 	struct screen			*s = &data->screen;
-	u_int				 i;
+	u_int				 i, mouse_backing_y;
+	int				 clicked, released, dragged, moved;
+	const char			*sep;
 
 	if (m->x >= screen_size_x(s))
 		return;
@@ -901,35 +958,101 @@ window_copy_mouse(
 		return;
 	}
 
+	clicked = (m->event & MOUSE_EVENT_DOWN);
+	released = (m->event & MOUSE_EVENT_UP);
+	dragged = (m->event & MOUSE_EVENT_DRAG);
+	sep = options_get_string(&sess->options, "word-separators");
+
 	/*
-	 * If already reading motion, move the cursor while buttons are still
-	 * pressed, or stop the selection on their release.
+	 * We detect double/triple clicks by counting the clicks in the
+	 * selection start's cell. If the user moves his mouse from that
+	 * position then that is definitely not a double/triple click.
 	 */
-	if (s->mode & MODE_MOUSE_BUTTON) {
-		if (~m->event & MOUSE_EVENT_UP) {
-			window_copy_update_cursor(wp, m->x, m->y);
-			if (window_copy_update_selection(wp, 1))
-				window_copy_redraw_screen(wp);
-			return;
-		}
-		goto reset_mode;
-	}
+	mouse_backing_y = m->y + screen_hsize(data->backing) - data->oy;
+	moved = (m->x != data->mouse_lx || mouse_backing_y != data->mouse_ly);
+	data->mouse_lx = m->x;
+	data->mouse_ly = mouse_backing_y;;
 
-	/* Otherwise if other buttons pressed, start selection and motion. */
-	if (~m->event & MOUSE_EVENT_UP) {
+	if (!(s->mode & MODE_MOUSE_BUTTON) && clicked) {
 		s->mode &= ~MODE_MOUSE_STANDARD;
 		s->mode |= MODE_MOUSE_BUTTON;
+	} else if ((s->mode & MODE_MOUSE_BUTTON) && released) {
+		s->mode &= ~MODE_MOUSE_BUTTON;
+		s->mode |= MODE_MOUSE_STANDARD;
+	}
 
+	if (clicked && m->button == 1) {
+		goto reset_mode;
+	} else if (clicked && m->button == 0 && (!s->sel.flag || moved)) {
+		/*
+		 * We've clicked the left mouse button. We are either not in a
+		 * selection or we are in a selection but we aren't trying to do
+		 * double/triple clicks. Left click should enter the selection
+		 * mode, right click should just move the cursor around.
+		 */
 		window_copy_update_cursor(wp, m->x, m->y);
+		data->mouse_clicks = 1;
+		data->mouse_sx = data->mouse_lx;
+		data->mouse_sy = data->mouse_ly;
+		data->mouse_x0 = m->x;
+		data->mouse_x1 = m->x;
 		window_copy_start_selection(wp);
 		window_copy_redraw_screen(wp);
+	} else if (clicked && !moved && m->button == 0) {
+		/*
+		 * We are trying to do multiple left clicks which starts a
+		 * selection. Two clicks should enter word selection mode, three
+		 * clicks should enter line selection mode.
+		 */
+
+		data->mouse_clicks += 1;
+		if (data->mouse_clicks == 4)
+			data->mouse_clicks = 1;
+
+		window_copy_update_cursor(wp, m->x, m->y);
+		if (data->mouse_clicks == 1) {
+			window_copy_start_selection(wp);
+			data->mouse_x0 = m->x;
+			data->mouse_x1 = m->x;
+		} else if (data->mouse_clicks == 2) {
+			if (data->cx > 0)
+				data->cx -= 1; /* Handle last letter clicks. */
+			window_copy_cursor_next_word_end(wp, sep);
+			data->mouse_x1 = data->cx;
+			data->cx += 1; /* Handle single letter words. */
+			window_copy_cursor_previous_word(wp, sep);
+			data->mouse_x0 = data->cx;
+			window_copy_start_selection(wp);
+		} else if (data->mouse_clicks == 3) {
+			window_copy_cursor_end_of_line(wp);
+			data->mouse_x1 = data->cx;
+			window_copy_cursor_start_of_line(wp);
+			data->mouse_x0 = data->cx;
+			window_copy_start_selection(wp);
+		}
+		window_copy_select_via_mouse(wp, m, sep);
+	} else if (clicked && moved && m->button == 2) {
+		data->mouse_clicks = 1;
+		window_copy_select_via_mouse(wp, m, sep);
+	} else if (clicked && !moved && m->button == 2) {
+		/*
+		 * We are just changing the character/word/line mode for the
+		 * right click dragging.
+		 */
+
+		data->mouse_clicks += 1;
+		if (data->mouse_clicks == 4)
+			data->mouse_clicks = 1;
+
+		window_copy_select_via_mouse(wp, m, sep);
+	} else if (dragged) {
+		/* We are trying to extend the current selection. */
+		window_copy_select_via_mouse(wp, m, sep);
 	}
 
 	return;
 
 reset_mode:
-	s->mode &= ~MODE_MOUSE_BUTTON;
-	s->mode |= MODE_MOUSE_STANDARD;
 	if (sess != NULL) {
 		window_copy_copy_selection(wp, NULL);
 		window_pane_reset_mode(wp);
-- 
1.9.1.423.g4596e3a

------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users

Reply via email to