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:
parent
2f24588c66
commit
9c486f6e69
@ -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); }
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user