Skip to content

Commit b8aae0e

Browse files
committed
Mouse selection based on edges
Base mouse selection on the edges between cells rather than the cells themselves. For finding the start and end line in regular selection, the vertical cell index is used rather than the edge. However, the vertical edge is used in rectangular selection mode. Now allows selection of a single character. This changes the semantics of screen_start_selection and screen_update_selection to accept an edge index instead of cell index for x, and an edge or cell index for y depending on whether rectangular selection is enabled. Closes kovidgoyal#945
1 parent 0195e7f commit b8aae0e

File tree

5 files changed

+71
-27
lines changed

5 files changed

+71
-27
lines changed

kitty/mouse.c

+20-13
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ distance_to_window(Window *w, OSWindow *os_window) {
130130
static bool clamp_to_window = false;
131131

132132
static inline bool
133-
cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
133+
cell_for_pos(Window *w, unsigned int *x, unsigned int *y, unsigned int *edge_x, unsigned int *edge_y, OSWindow *os_window) {
134134
WindowGeometry *g = &w->geometry;
135135
Screen *screen = w->render_data.screen;
136136
if (!screen) return false;
137-
unsigned int qx = 0, qy = 0;
137+
double qx = 0, qy = 0;
138138
double mouse_x = global_state.callback_os_window->mouse_x;
139139
double mouse_y = global_state.callback_os_window->mouse_y;
140140
double left = window_left(w, os_window), top = window_top(w, os_window), right = window_right(w, os_window), bottom = window_bottom(w, os_window);
@@ -143,12 +143,15 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
143143
mouse_y = MIN(MAX(mouse_y, top), bottom);
144144
}
145145
if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
146-
if (mouse_x >= g->right) qx = screen->columns - 1;
147-
else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / os_window->fonts_data->cell_width);
148-
if (mouse_y >= g->bottom) qy = screen->lines - 1;
149-
else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / os_window->fonts_data->cell_height);
146+
if (mouse_x >= g->right) qx = screen->columns - 0.5;
147+
else if (mouse_x >= g->left) qx = ((double)(mouse_x - g->left) / os_window->fonts_data->cell_width);
148+
if (mouse_y >= g->bottom) qy = screen->lines - 0.5;
149+
else if (mouse_y >= g->top) qy = ((double)(mouse_y - g->top) / os_window->fonts_data->cell_height);
150150
if (qx < screen->columns && qy < screen->lines) {
151-
*x = qx; *y = qy;
151+
*x = (unsigned int)qx;
152+
*y = (unsigned int)qy;
153+
*edge_x = (unsigned int)(qx + 0.5);
154+
*edge_y = (unsigned int)(qy + 0.5);
152155
return true;
153156
}
154157
return false;
@@ -164,14 +167,15 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
164167
global_state.active_drag_in_window = 0;
165168
w->last_drag_scroll_at = 0;
166169
if (screen->selection.in_progress)
167-
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
170+
screen_update_selection(screen, w->mouse_edge_x, screen->selection.rectangle_select ? w->mouse_edge_y : w->mouse_cell_y, true);
168171
}
169172
else {
173+
bool rectangle_select = modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT);
170174
global_state.active_drag_in_window = w->id;
171-
screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
175+
screen_start_selection(screen, w->mouse_edge_x, rectangle_select ? w->mouse_edge_y : w->mouse_cell_y, rectangle_select, EXTEND_CELL);
172176
}
173177
} else if (screen->selection.in_progress) {
174-
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
178+
screen_update_selection(screen, w->mouse_edge_x, screen->selection.rectangle_select ? w->mouse_edge_y : w->mouse_cell_y, false);
175179
}
176180
}
177181

@@ -266,18 +270,21 @@ detect_url(Screen *screen, unsigned int x, unsigned int y) {
266270

267271

268272
HANDLER(handle_move_event) {
269-
unsigned int x = 0, y = 0;
273+
unsigned int x = 0, y = 0, edge_x = 0, edge_y = 0;
270274
if (OPT(focus_follows_mouse)) {
271275
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
272276
if (window_idx != t->active_window) {
273277
call_boss(switch_focus_to, "I", window_idx);
274278
}
275279
}
276-
if (!cell_for_pos(w, &x, &y, global_state.callback_os_window)) return;
280+
if (!cell_for_pos(w, &x, &y, &edge_x, &edge_y, global_state.callback_os_window)) return;
281+
277282
Screen *screen = w->render_data.screen;
278283
detect_url(screen, x, y);
279284
bool mouse_cell_changed = x != w->mouse_cell_x || y != w->mouse_cell_y;
285+
bool mouse_edge_changed = edge_x != w->mouse_edge_x || edge_y != w->mouse_edge_y;
280286
w->mouse_cell_x = x; w->mouse_cell_y = y;
287+
w->mouse_edge_x = edge_x; w->mouse_edge_y = edge_y;
281288
bool handle_in_kitty = (
282289
(screen->modes.mouse_tracking_mode == ANY_MODE ||
283290
(screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)) &&
@@ -286,7 +293,7 @@ HANDLER(handle_move_event) {
286293
if (handle_in_kitty) {
287294
if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) {
288295
double now = monotonic();
289-
if ((now - w->last_drag_scroll_at) >= 0.02 || mouse_cell_changed) {
296+
if ((now - w->last_drag_scroll_at) >= 0.02 || mouse_edge_changed || mouse_cell_changed) {
290297
update_drag(false, w, false, 0);
291298
w->last_drag_scroll_at = monotonic();
292299
}

kitty/screen.c

+48-12
Original file line numberDiff line numberDiff line change
@@ -1490,7 +1490,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
14901490

14911491
static inline bool
14921492
is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) {
1493-
return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false;
1493+
return start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines;
14941494
}
14951495

14961496
static inline void
@@ -2019,7 +2019,14 @@ screen_is_selection_dirty(Screen *self) {
20192019
void
20202020
screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select, SelectionExtendMode extend_mode) {
20212021
#define A(attr, val) self->selection.attr = val;
2022-
A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
2022+
A(anchor_x, x);
2023+
A(anchor_y, y);
2024+
A(start_x, UINT_MAX);
2025+
A(start_y, UINT_MAX);
2026+
A(end_x, UINT_MAX);
2027+
A(end_y, UINT_MAX);
2028+
A(start_scrolled_by, self->scrolled_by);
2029+
A(end_scrolled_by, self->scrolled_by);
20232030
A(in_progress, true); A(rectangle_select, rectangle_select); A(extend_mode, extend_mode);
20242031
#undef A
20252032
}
@@ -2033,27 +2040,55 @@ screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type
20332040

20342041
void
20352042
screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
2036-
self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by;
20372043
if (ended) self->selection.in_progress = false;
2038-
index_type start, end;
2039-
bool found = false;
2040-
bool extending_leftwards = self->selection.end_y < self->selection.start_y || (self->selection.end_y == self->selection.start_y && self->selection.end_x < self->selection.start_x);
2041-
switch(self->selection.extend_mode) {
2044+
bool empty = false;
2045+
bool extending_leftwards = y < self->selection.anchor_y || (y == self->selection.anchor_y && x <= self->selection.anchor_x);
2046+
if (self->selection.rectangle_select) {
2047+
if (x == self->selection.anchor_x || y == self->selection.anchor_y) {
2048+
empty = true;
2049+
} else {
2050+
self->selection.start_x = MIN(self->selection.anchor_x, x);
2051+
self->selection.end_x = MAX(self->selection.anchor_x, x) - 1;
2052+
self->selection.start_y = MIN(self->selection.anchor_y, y);
2053+
self->selection.end_y = MAX(self->selection.anchor_y, y) - 1;
2054+
}
2055+
} else {
2056+
if (self->selection.extend_mode == EXTEND_CELL && x == self->selection.anchor_x && y == self->selection.anchor_y) {
2057+
empty = true;
2058+
} else if (extending_leftwards) {
2059+
self->selection.start_x = x;
2060+
self->selection.start_y = y;
2061+
self->selection.end_x = self->selection.anchor_x - 1;
2062+
self->selection.end_y = self->selection.anchor_y;
2063+
} else {
2064+
self->selection.start_x = self->selection.anchor_x;
2065+
self->selection.start_y = self->selection.anchor_y;
2066+
self->selection.end_x = x - 1;
2067+
self->selection.end_y = y;
2068+
}
2069+
}
2070+
2071+
if (empty) {
2072+
self->selection.start_x = self->selection.start_y = self->selection.end_x = self->selection.end_y = UINT_MAX;
2073+
} else {
2074+
index_type start, end;
2075+
bool found = false;
2076+
switch(self->selection.extend_mode) {
20422077
case EXTEND_WORD: {
20432078
index_type y1 = y, y2;
2044-
found = screen_selection_range_for_word(self, x, &y1, &y2, &start, &end);
2079+
found = screen_selection_range_for_word(self, x - (extending_leftwards ? 0 : 1), &y1, &y2, &start, &end);
20452080
if (found) {
20462081
if (extending_leftwards) {
20472082
self->selection.end_x = start; self->selection.end_y = y1;
2048-
y1 = self->selection.start_y;
2049-
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
2083+
y1 = self->selection.anchor_y;
2084+
found = screen_selection_range_for_word(self, self->selection.anchor_x, &y1, &y2, &start, &end);
20502085
if (found) {
20512086
self->selection.start_x = end; self->selection.start_y = y2;
20522087
}
20532088
} else {
20542089
self->selection.end_x = end; self->selection.end_y = y2;
2055-
y1 = self->selection.start_y;
2056-
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
2090+
y1 = self->selection.anchor_y;
2091+
found = screen_selection_range_for_word(self, self->selection.anchor_x, &y1, &y2, &start, &end);
20572092
if (found) {
20582093
self->selection.start_x = start; self->selection.start_y = y1;
20592094
}
@@ -2087,6 +2122,7 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
20872122
}
20882123
case EXTEND_CELL:
20892124
break;
2125+
}
20902126
}
20912127
call_boss(set_primary_selection, NULL);
20922128
}

kitty/screen.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ typedef struct {
2525
typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE } SelectionExtendMode;
2626

2727
typedef struct {
28-
unsigned int start_x, start_y, start_scrolled_by, end_x, end_y, end_scrolled_by;
28+
unsigned int anchor_x, anchor_y, start_x, start_y, start_scrolled_by, end_x, end_y, end_scrolled_by;
2929
bool in_progress, rectangle_select;
3030
SelectionExtendMode extend_mode;
3131
} Selection;

kitty/state.h

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typedef struct {
6666
PyObject *title;
6767
ScreenRenderData render_data;
6868
unsigned int mouse_cell_x, mouse_cell_y;
69+
unsigned int mouse_edge_x, mouse_edge_y;
6970
WindowGeometry geometry;
7071
ClickQueue click_queue;
7172
double last_drag_scroll_at;

kitty_tests/screen.py

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ def test_selection_as_text(self):
423423
s.carriage_return(), s.linefeed()
424424
s.draw(str(i) * s.columns)
425425
s.start_selection(0, 0, False)
426-
s.update_selection(4, 4, True)
426+
s.update_selection(5, 4, True)
427427
expected = ('55555', '\n66666', '\n77777', '\n88888', '\n99999')
428428
self.ae(s.text_for_selection(), expected)
429429
s.scroll(2, True)

0 commit comments

Comments
 (0)