Track left to right for selections based on actual mouse positions

Cant use the selection boundaries as these can be programmatically
altered when extending selection by word or line.
This commit is contained in:
Kovid Goyal 2020-02-23 14:48:52 +05:30
parent 2f24588c66
commit 9c486f6e69
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 62 additions and 63 deletions

View File

@ -35,7 +35,6 @@
#define zero_at_i(array, idx) memset((array) + (idx), 0, sizeof((array)[0]))
#define zero_at_ptr(p) memset((p), 0, sizeof((p)[0]))
#define zero_at_ptr_count(p, count) memset((p), 0, (count) * sizeof((p)[0]))
#define memcpy_ptr(dest, src) memcpy((dest), (src), sizeof((src)[0]))
void log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }

View File

@ -332,25 +332,21 @@ static inline void
multi_click(Window *w, unsigned int count) {
Screen *screen = w->render_data.screen;
index_type start, end;
bool found_selection = false;
SelectionExtendMode mode = EXTEND_CELL;
unsigned int y1 = w->mouse_pos.cell_y, y2 = w->mouse_pos.cell_y;
bool start_in_left_half = w->mouse_pos.in_left_half_of_cell, end_in_left_half = w->mouse_pos.in_left_half_of_cell;
unsigned int y1, y2;
switch(count) {
case 2:
found_selection = screen_selection_range_for_word(screen, w->mouse_pos.cell_x, &y1, &y2, &start, &end, &start_in_left_half, &end_in_left_half, true);
mode = EXTEND_WORD;
if (screen_selection_range_for_word(screen, w->mouse_pos.cell_x, &y1, &y2, &start, &end, true)) mode = EXTEND_WORD;
break;
case 3:
found_selection = screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end, &start_in_left_half, &end_in_left_half);
mode = EXTEND_LINE;
if (screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end)) mode = EXTEND_LINE;
break;
default:
break;
}
if (found_selection) {
screen_start_selection(screen, start, y1, start_in_left_half, false, mode);
screen_update_selection(screen, end, y2, end_in_left_half, false);
if (mode != EXTEND_CELL) {
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, EXTEND_WORD);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false);
}
}

View File

@ -1619,9 +1619,11 @@ selection_boundary_less_than(SelectionBoundary *a, SelectionBoundary *b) {
else { *(left) = b; *(right) = a; } \
}
typedef Line*(linefunc_t)(Screen*, int);
static inline Line*
visual_line_(Screen *self, index_type y) {
visual_line_(Screen *self, int y_) {
index_type y = MAX(0, y_);
if (self->scrolled_by) {
if (y < self->scrolled_by) {
historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line);
@ -1664,13 +1666,19 @@ typedef struct {
XRange first, body, last;
} IterationData;
static inline bool
selection_is_left_to_right(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);
}
static inline IterationData
iteration_data(const Screen *self, SelectionBoundary *start, SelectionBoundary *end, const bool rectangle) {
iteration_data(const Screen *self, Selection *sel, const bool rectangle) {
IterationData ans = {0};
SelectionBoundary *start = &sel->start, *end = &sel->end;
// empty selection
if (start->x == end->x && start->y == end->y && start->in_left_half_of_cell == end->in_left_half_of_cell) return ans;
bool left_to_right = start->x < end->x || (start->x == end->x && start->in_left_half_of_cell && !end->in_left_half_of_cell);
bool left_to_right = selection_is_left_to_right(sel);
if (rectangle) {
// empty selection
if (start->x == end->x && (!start->in_left_half_of_cell || end->in_left_half_of_cell)) return ans;
@ -1726,8 +1734,8 @@ iteration_data(const Screen *self, SelectionBoundary *start, SelectionBoundary *
return ans;
}
#define iterate_over_region(screen, start, end, line_func, rectangular) { \
IterationData idata = iteration_data(screen, start, end, rectangular); \
#define iterate_over_selection(screen, sel, line_func, rectangular) { \
IterationData idata = iteration_data(screen, sel, rectangular); \
for (index_type y = idata.y; y < idata.y_limit; y++) { \
Line *line = line_func(self, y); \
index_type xlimit = xlimit_for_line(line), x_start = 0; \
@ -1744,10 +1752,9 @@ iteration_data(const Screen *self, SelectionBoundary *start, SelectionBoundary *
static inline void
apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask, bool rectangle_select) {
Selection s = { .start = *start, .end = *end };
if (is_selection_empty_or_out_of_bounds(self, &s)) return;
iterate_over_region(self, start, end, visual_line_, rectangle_select)
apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask, bool rectangle_select) {
if (is_selection_empty_or_out_of_bounds(self, s)) return;
iterate_over_selection(self, s, visual_line_, rectangle_select)
uint8_t *line_start = data + self->columns * y;
for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask;
}}
@ -1766,35 +1773,32 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
self->last_selection_scrolled_by = self->scrolled_by;
self->selection_updated_once = true;
selection_limits_(selection, &self->last_rendered.selection.start, &self->last_rendered.selection.end);
apply_selection(self, address, &self->last_rendered.selection.start, &self->last_rendered.selection.end, 1, self->selection.rectangle_select);
apply_selection(self, address, &self->last_rendered.selection, 1, self->selection.rectangle_select);
selection_limits_(url_range, &self->last_rendered.url.start, &self->last_rendered.url.end);
apply_selection(self, address, &self->last_rendered.url.start, &self->last_rendered.url.end, 2, false);
apply_selection(self, address, &self->last_rendered.url, 2, false);
}
#define text_for_range_action(ans, insert_newlines) { \
char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; \
PyObject *text = unicode_in_range(line, x_start, xlimit, true, leading_char); \
if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } \
PyTuple_SET_ITEM(ans, i++, text); \
static inline PyObject*
text_for_range(Screen *self, Selection *sel, bool rectangle_select, bool insert_newlines, linefunc_t line_func) {
int num_of_lines = sel->end.y - sel->start.y + 1, i = 0;
PyObject *ans = PyTuple_New(num_of_lines);
if (!ans) return NULL;
iterate_over_selection(self, sel, line_func, rectangle_select)
char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; \
PyObject *text = unicode_in_range(line, x_start, xlimit, true, leading_char); \
if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } \
PyTuple_SET_ITEM(ans, i++, text); \
}}
return ans;
}
#define text_for_range(screen, ans, start, end, rectangle_select, insert_newlines, line_func) { \
int num_of_lines = end.y - start.y + 1, i = 0; \
ans = PyTuple_New(num_of_lines); \
if (ans == NULL) return PyErr_NoMemory(); \
iterate_over_region(screen, (&start), (&end), line_func, rectangle_select) \
text_for_range_action(ans, insert_newlines); \
}} \
}
bool
screen_open_url(Screen *self) {
Selection s = {0};
selection_limits_(url_range, &s.start, &s.end);
if (is_selection_empty_or_out_of_bounds(self, &s)) return false;
PyObject *url;
text_for_range(self, url, s.start, s.end, false, false, visual_line_);
s.input_start = s.start; s.input_current = s.end;
PyObject *url = text_for_range(self, &s, false, false, visual_line_);
if (url) { call_boss(open_url_lines, "(O)", url); Py_CLEAR(url); }
else PyErr_Print();
return true;
@ -2131,26 +2135,21 @@ copy_colors_from(Screen *self, Screen *other) {
static PyObject*
text_for_selection(Screen *self, PyObject *a UNUSED) {
SelectionBoundary start, end;
full_selection_limits_(selection, &start, &end);
PyObject *ans = NULL;
start.y = clamp_for_range_line(self, start.y);
end.y = clamp_for_range_line(self, end.y);
if (start.y == end.y && start.x == end.x) ans = PyTuple_New(0);
else text_for_range(self, ans, start, end, self->selection.rectangle_select, true, range_line_);
return ans;
Selection s = self->selection;
full_selection_limits_(selection, &s.start, &s.end);
s.start.y = clamp_for_range_line(self, s.start.y);
s.end.y = clamp_for_range_line(self, s.end.y);
return text_for_range(self, &s, self->selection.rectangle_select, true, range_line_);
}
bool
screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end, bool* start_in_left_half, bool* end_in_left_half) {
screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) {
if (y >= self->lines) { return false; }
Line *line = visual_line_(self, y);
index_type xlimit = line->xnum, xstart = 0;
while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--;
while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++;
*start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0;
*start_in_left_half = true;
*end_in_left_half = false;
return true;
}
@ -2163,9 +2162,8 @@ is_opt_word_char(char_type ch) {
}
bool
screen_selection_range_for_word(Screen *self, index_type x, index_type *y1, index_type *y2, index_type *s, index_type *e, bool *start_in_left_half, bool *end_in_left_half, bool initial_selection) {
screen_selection_range_for_word(Screen *self, index_type x, index_type *y1, index_type *y2, index_type *s, index_type *e, bool initial_selection) {
if (*y1 >= self->lines || x >= self->columns) return false;
*start_in_left_half = true; *end_in_left_half = false;
index_type start, end;
Line *line = visual_line_(self, *y1);
*y2 = *y1;
@ -2249,6 +2247,8 @@ screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_ha
#define A(attr, val) self->selection.attr = val;
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);
A(in_progress, true); A(rectangle_select, rectangle_select); A(extend_mode, extend_mode); A(start.in_left_half_of_cell, in_left_half_of_cell); A(end.in_left_half_of_cell, in_left_half_of_cell);
A(input_start.x, x); A(input_start.in_left_half_of_cell, in_left_half_of_cell);
A(input_current.x, x); A(input_current.in_left_half_of_cell, in_left_half_of_cell);
#undef A
}
@ -2263,10 +2263,12 @@ screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type
void
screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool ended) {
if (ended) self->selection.in_progress = false;
self->selection.input_current.x = x; self->selection.input_current.y = y;
self->selection.input_current.in_left_half_of_cell = in_left_half_of_cell;
SelectionBoundary start, end, *a, *b;
a = &self->selection.end, b = &self->selection.start;
if (selection_boundary_less_than(a, b)) { a = &self->selection.start; b = &self->selection.end; }
bool left_to_right = self->selection.start.x < self->selection.end.x || (self->selection.start.x == self->selection.end.x && self->selection.start.in_left_half_of_cell && !self->selection.end.in_left_half_of_cell);
bool left_to_right = selection_is_left_to_right(&self->selection);
switch(self->selection.extend_mode) {
case EXTEND_WORD: {
@ -2275,10 +2277,10 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
if (left_to_right && in_left_half_of_cell && x) effective_x--;
else if(!left_to_right && !in_left_half_of_cell && x < self->columns - 1) effective_x++;
start.y = y;
found = screen_selection_range_for_word(self, effective_x, &start.y, &end.y, &start.x, &end.x, &start.in_left_half_of_cell, &end.in_left_half_of_cell, false);
found = screen_selection_range_for_word(self, effective_x, &start.y, &end.y, &start.x, &end.x, false);
if (found) {
if (selection_boundary_less_than(&start, a)) memcpy_ptr(a, &start);
if (selection_boundary_less_than(b, &end)) memcpy_ptr(b, &end);
if (selection_boundary_less_than(&start, a)) *a = start;
if (selection_boundary_less_than(b, &end)) *b = end;
}
break;
}
@ -2287,7 +2289,7 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
if (y < a->y) {
index_type top_line = y;
while (top_line > 0 && visual_line_(self, top_line)->continued) top_line--;
if (screen_selection_range_for_line(self, top_line, &start.x, &end.x, &start.in_left_half_of_cell, &end.in_left_half_of_cell)) {
if (screen_selection_range_for_line(self, top_line, &start.x, &end.x)) {
a->y = top_line;
a->x = multiline ? 0 : start.x;
a->in_left_half_of_cell = start.in_left_half_of_cell;
@ -2296,7 +2298,7 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
else if (y > b->y) {
index_type bottom_line = b->y;
while(bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) bottom_line++;
if (screen_selection_range_for_line(self, bottom_line, &start.x, &end.x, &start.in_left_half_of_cell, &end.in_left_half_of_cell))
if (screen_selection_range_for_line(self, bottom_line, &start.x, &end.x))
{
b->y = bottom_line;
b->x = end.x; b->in_left_half_of_cell = end.in_left_half_of_cell;

View File

@ -27,7 +27,7 @@ typedef struct {
typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE } SelectionExtendMode;
typedef struct {
SelectionBoundary start, end;
SelectionBoundary start, end, input_start, input_current;
unsigned int start_scrolled_by, end_scrolled_by;
bool in_progress, rectangle_select;
SelectionExtendMode extend_mode;
@ -184,8 +184,8 @@ bool screen_has_selection(Screen*);
bool screen_invert_colors(Screen *self);
void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE, bool cursor_has_moved);
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*, bool*);
bool screen_selection_range_for_word(Screen *self, index_type x, index_type *, index_type *, index_type *start, index_type *end, bool*, bool*, bool);
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, index_type x, 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_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, bool ended);
bool screen_history_scroll(Screen *self, int amt, bool upwards);

View File

@ -90,6 +90,8 @@ class TestMouse(BaseTest):
init()
press(), release(1)
self.ae(sel(), '1')
press(3), release(2)
self.ae(sel(), '3')
# Multi-line click release
init()
@ -108,9 +110,9 @@ class TestMouse(BaseTest):
s.draw(' f gh')
s.draw(' stuv')
s.draw('X Y')
multi_click(x=1)
multi_click(x=1.6)
self.ae(sel(), 'ab')
move(3)
move(3.6)
self.ae(sel(), 'ab cd')
release(3, 1)
self.ae(sel(), 'ab cd f gh')