Have right clicking to extend selection operate on the nearest selection boundary instead of the selection end. This matches both vim and xterm

This commit is contained in:
Kovid Goyal 2021-07-04 18:00:41 +05:30
parent 86a8b231f4
commit e732df46b8
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 145 additions and 58 deletions

View File

@ -246,7 +246,7 @@ static inline void
update_drag(Window *w) { update_drag(Window *w) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen && screen->selections.in_progress) { if (screen && screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){0});
} }
set_mouse_cursor_when_dragging(); set_mouse_cursor_when_dragging();
} }
@ -284,7 +284,8 @@ static inline void
extend_selection(Window *w, bool ended) { extend_selection(Window *w, bool ended) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen_has_selection(screen)) { if (screen_has_selection(screen)) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell,
(SelectionUpdate){.ended=ended, .set_as_nearest_extend=true});
} }
} }
@ -578,7 +579,7 @@ end_drag(Window *w) {
global_state.active_drag_button = -1; global_state.active_drag_button = -1;
w->last_drag_scroll_at = 0; w->last_drag_scroll_at = 0;
if (screen->selections.in_progress) { if (screen->selections.in_progress) {
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, false); screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=true});
} }
} }
@ -601,7 +602,7 @@ mouse_selection(Window *w, int code, int button) {
unsigned int y1, y2; unsigned int y1, y2;
#define S(mode) {\ #define S(mode) {\
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); \ screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode); \
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true); } screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.start_extended_selection=true}); }
switch((MouseSelectionType)code) { switch((MouseSelectionType)code) {
case MOUSE_SELECTION_NORMAL: case MOUSE_SELECTION_NORMAL:

View File

@ -1849,19 +1849,41 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
} }
static inline bool static bool
selection_boundary_less_than(SelectionBoundary *a, SelectionBoundary *b) { selection_boundary_less_than(const SelectionBoundary *a, const SelectionBoundary *b) {
if (a->y < b->y) return true; // y -values must be absolutized (aka adjusted with scrolled_by)
if (a->y > b->y) return false; // this means the oldest line has the highest value and is thus the least
if (a->y > b->y) return true;
if (a->y < b->y) return false;
if (a->x < b->x) return true; if (a->x < b->x) return true;
if (a->x > b->x) return false; if (a->x > b->x) return false;
if (a->in_left_half_of_cell && !b->in_left_half_of_cell) return true; if (a->in_left_half_of_cell && !b->in_left_half_of_cell) return true;
return false; return false;
} }
static index_type
num_cells_between_selection_boundaries(const Screen *self, const SelectionBoundary *a, const SelectionBoundary *b) {
const SelectionBoundary *before, *after;
if (selection_boundary_less_than(a, b)) { before = a; after = b; }
else { before = b; after = a; }
index_type ans = 0;
if (before->y + 1 < after->y) ans += self->columns * (after->y - before->y - 1);
if (before->y == after->y) ans += after->x - before->x;
else ans += (self->columns - before->x) + after->x;
return ans;
}
static index_type
num_lines_between_selection_boundaries(const SelectionBoundary *a, const SelectionBoundary *b) {
const SelectionBoundary *before, *after;
if (selection_boundary_less_than(a, b)) { before = a; after = b; }
else { before = b; after = a; }
return before->y - after->y;
}
typedef Line*(linefunc_t)(Screen*, int); typedef Line*(linefunc_t)(Screen*, int);
static inline Line* static Line*
visual_line_(Screen *self, int y_) { visual_line_(Screen *self, int y_) {
index_type y = MAX(0, y_); index_type y = MAX(0, y_);
if (self->scrolled_by) { if (self->scrolled_by) {
@ -1875,7 +1897,7 @@ visual_line_(Screen *self, int y_) {
return self->linebuf->line; return self->linebuf->line;
} }
static inline Line* static Line*
range_line_(Screen *self, int y) { range_line_(Screen *self, int y) {
if (y < 0) { if (y < 0) {
historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line); historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line);
@ -1885,7 +1907,7 @@ range_line_(Screen *self, int y) {
return self->linebuf->line; return self->linebuf->line;
} }
static inline bool static bool
selection_is_left_to_right(const Selection *self) { selection_is_left_to_right(const Selection *self) {
return self->input_start.x < self->input_current.x || (self->input_start.x == self->input_current.x && self->input_start.in_left_half_of_cell); return self->input_start.x < self->input_current.x || (self->input_start.x == self->input_current.x && self->input_start.in_left_half_of_cell);
} }
@ -2477,9 +2499,9 @@ cursor_up(Screen *self, PyObject *args) {
static PyObject* static PyObject*
update_selection(Screen *self, PyObject *args) { update_selection(Screen *self, PyObject *args) {
unsigned int x, y; unsigned int x, y;
int in_left_half_of_cell = 0, ended = 1; int in_left_half_of_cell = 0, ended = 1, nearest = 0;
if (!PyArg_ParseTuple(args, "II|pp", &x, &y, &in_left_half_of_cell, &ended)) return NULL; if (!PyArg_ParseTuple(args, "II|ppp", &x, &y, &in_left_half_of_cell, &ended, &nearest)) return NULL;
screen_update_selection(self, x, y, in_left_half_of_cell, ended, false); screen_update_selection(self, x, y, in_left_half_of_cell, (SelectionUpdate){.ended = ended, .set_as_nearest_extend=nearest});
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -2779,68 +2801,124 @@ screen_mark_hyperlink(Screen *self, index_type x, index_type y) {
return id; return id;
} }
static index_type
continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) {
while (top_line > 0 && visual_line_(self, top_line)->continued) {
if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break;
top_line--;
}
return top_line;
}
static index_type
continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) {
while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) {
if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break;
bottom_line++;
}
return bottom_line;
}
void void
screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool ended, bool start_extended_selection) { screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, SelectionUpdate upd) {
if (!self->selections.count) return; if (!self->selections.count) return;
self->selections.in_progress = !ended; self->selections.in_progress = !upd.ended;
Selection *s = self->selections.items; Selection *s = self->selections.items;
s->input_current.x = x; s->input_current.y = y; s->input_current.x = x; s->input_current.y = y;
s->input_current.in_left_half_of_cell = in_left_half_of_cell; s->input_current.in_left_half_of_cell = in_left_half_of_cell;
s->end_scrolled_by = self->scrolled_by; SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input;
SelectionBoundary start, end, *a, *b; #define absy(b, scrolled_by) b.y = scrolled_by + self->lines - 1 - b.y
a = &s->start, b = &s->end; abs_start = s->start; absy(abs_start, s->start_scrolled_by);
abs_end = s->end; absy(abs_end, s->end_scrolled_by);
abs_current_input = s->input_current; absy(abs_current_input, self->scrolled_by);
#undef absy
if (upd.set_as_nearest_extend) {
bool start_is_nearer = false;
if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
if (s->start.y == s->end.y) start_is_nearer = selection_boundary_less_than(&abs_start, &abs_end) ? (abs_current_input.y > abs_start.y) : (abs_current_input.y < abs_end.y);
else start_is_nearer = num_lines_between_selection_boundaries(&abs_start, &abs_current_input) < num_lines_between_selection_boundaries(&abs_end, &abs_current_input);
} else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input);
if (start_is_nearer) s->adjusting_start = true;
}
bool adjusted_boundary_is_before;
if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end);
else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); }
switch(self->selections.extend_mode) { switch(self->selections.extend_mode) {
case EXTEND_WORD: { case EXTEND_WORD: {
SelectionBoundary *before = &s->input_start, *after = &s->input_current; if (!s->adjusting_start) { a = &s->end; b = &s->start; }
if (selection_boundary_less_than(after, before)) { before = after; after = &s->input_start; } const bool word_found_at_cursor = screen_selection_range_for_word(self, s->input_current.x, s->input_current.y, &start.y, &end.y, &start.x, &end.x, true);
bool found_at_start = screen_selection_range_for_word(self, before->x, before->y, &start.y, &end.y, &start.x, &end.x, true); bool adjust_both_ends = is_selection_empty(s);
if (found_at_start) { if (word_found_at_cursor) {
a->x = start.x; a->y = start.y; a->in_left_half_of_cell = true; if (adjusted_boundary_is_before) {
b->x = end.x; b->y = end.y; b->in_left_half_of_cell = false; *a = start; a->in_left_half_of_cell = true;
if (adjust_both_ends) { *b = end; b->in_left_half_of_cell = false; }
} else {
*a = end; a->in_left_half_of_cell = false;
if (adjust_both_ends) { *b = start; b->in_left_half_of_cell = true; }
}
if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by;
if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by;
} else { } else {
a->x = before->x; a->y = before->y; a->in_left_half_of_cell = before->in_left_half_of_cell; *a = s->input_current;
b->x = a->x; b->y = a->y; b->in_left_half_of_cell = a->in_left_half_of_cell; if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by;
} }
bool found_at_end = screen_selection_range_for_word(self, after->x, after->y, &start.y, &end.y, &start.x, &end.x, false);
if (found_at_end) { b->x = end.x; b->y = end.y; b->in_left_half_of_cell = false; }
break; break;
} }
case EXTEND_LINE_FROM_POINT: case EXTEND_LINE_FROM_POINT:
case EXTEND_LINE: { case EXTEND_LINE: {
bool adjust_both_ends = is_selection_empty(s);
if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by;
if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by;
index_type top_line, bottom_line; index_type top_line, bottom_line;
if (start_extended_selection || y == s->start.y) { SelectionBoundary up_start, up_end, down_start, down_end;
top_line = y; bottom_line = y; if (adjust_both_ends) {
} else if (y < s->start.y) { // empty initial selection
top_line = y; bottom_line = s->start.y; top_line = s->input_current.y; bottom_line = s->input_current.y;
a = &s->end; b = &s->start; if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) {
} else if (y > s->start.y) { #define S \
bottom_line = y; top_line = s->start.y; s->start.y = top_line; s->end.y = bottom_line; \
} else break; s->start.in_left_half_of_cell = true; s->end.in_left_half_of_cell = false; \
while (top_line > 0 && visual_line_(self, top_line)->continued) { s->start.x = up_start.x; s->end.x = bottom_line == top_line ? up_end.x : down_end.x;
if (!screen_selection_range_for_line(self, top_line - 1, &start.x, &end.x)) break; down_start = up_start; down_end = up_end;
top_line--; bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end);
if (self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
if (x <= up_end.x) {
S; s->start.x = MAX(x, up_start.x);
}
} else {
top_line = continue_line_upwards(self, top_line, &up_start, &up_end);
S;
}
}
#undef S
} else {
// extending an existing selection
top_line = s->input_current.y; bottom_line = s->input_current.y;
if (screen_selection_range_for_line(self, top_line, &up_start.x, &up_end.x)) {
down_start = up_start; down_end = up_end;
top_line = continue_line_upwards(self, top_line, &up_start, &up_end);
bottom_line = continue_line_downwards(self, bottom_line, &down_start, &down_end);
if (!s->adjusting_start) { a = &s->end; b = &s->start; }
if (adjusted_boundary_is_before) {
a->in_left_half_of_cell = true; a->x = up_start.x; a->y = top_line;
} else {
a->in_left_half_of_cell = false; a->x = down_end.x; a->y = bottom_line;
}
}
} }
while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) {
if (!screen_selection_range_for_line(self, bottom_line + 1, &start.x, &end.x)) break;
bottom_line++;
}
if (screen_selection_range_for_line(self, top_line, &start.x, &start.y) && screen_selection_range_for_line(self, bottom_line, &end.x, &end.y)) {
bool multiline = top_line != bottom_line;
if (self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
if (x > end.y) break;
a->x = x;
} else a->x = multiline ? 0 : start.x;
a->y = top_line; a->in_left_half_of_cell = true;
b->x = end.y; b->y = bottom_line; b->in_left_half_of_cell = false;
}
break;
} }
break;
case EXTEND_CELL: case EXTEND_CELL:
if (s->adjusting_start) b = &s->start;
b->x = x; b->y = y; b->in_left_half_of_cell = in_left_half_of_cell; b->x = x; b->y = y; b->in_left_half_of_cell = in_left_half_of_cell;
if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by;
break; break;
} }
if (!self->selections.in_progress) call_boss(set_primary_selection, NULL); if (!self->selections.in_progress) {
s->adjusting_start = false;
call_boss(set_primary_selection, NULL);
}
} }
static PyObject* static PyObject*

View File

@ -39,7 +39,7 @@ typedef struct {
typedef struct { typedef struct {
SelectionBoundary start, end, input_start, input_current; SelectionBoundary start, end, input_start, input_current;
unsigned int start_scrolled_by, end_scrolled_by; unsigned int start_scrolled_by, end_scrolled_by;
bool rectangle_select; bool rectangle_select, adjusting_start;
IterationData last_rendered; IterationData last_rendered;
int sort_y, sort_x; int sort_y, sort_x;
} Selection; } Selection;
@ -212,7 +212,10 @@ bool screen_is_cursor_visible(Screen *self);
bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end); bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end);
bool screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *, index_type *, index_type *start, index_type *end, bool); bool screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *, index_type *, index_type *start, index_type *end, bool);
void screen_start_selection(Screen *self, index_type x, index_type y, bool, bool, SelectionExtendMode); void screen_start_selection(Screen *self, index_type x, index_type y, bool, bool, SelectionExtendMode);
void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, bool ended, bool start_extended_selection); typedef struct SelectionUpdate {
bool ended, start_extended_selection, set_as_nearest_extend;
} SelectionUpdate;
void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, SelectionUpdate upd);
bool screen_history_scroll(Screen *self, int amt, bool upwards); bool screen_history_scroll(Screen *self, int amt, bool upwards);
Line* screen_visual_line(Screen *self, index_type y); Line* screen_visual_line(Screen *self, index_type y);
unsigned long screen_current_char_width(Screen *self); unsigned long screen_current_char_width(Screen *self);

View File

@ -177,7 +177,7 @@ class TestMouse(BaseTest):
multi_click(x=1, count=3) multi_click(x=1, count=3)
self.ae(sel(), '123') self.ae(sel(), '123')
move(x=2, y=1) move(x=2, y=1)
self.ae(sel(), ' 123\n 456') self.ae(sel(), '123\n 456')
release() release()
# Rectangle select # Rectangle select
@ -218,3 +218,8 @@ class TestMouse(BaseTest):
move(x=2, y=1, q='1234567') move(x=2, y=1, q='1234567')
release(x=3, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) release(x=3, y=1, button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), '12345678') self.ae(sel(), '12345678')
init()
press(y=2)
move(x=3.6, y=2, q='abcd')
press(x=3, y=0, button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), '4567890abcd')