From cf6894d74f3eb61484fff1a1eac8f2df03a17622 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 14 Sep 2017 10:44:20 +0530 Subject: [PATCH] Port multi-click handler to C --- kitty/boss.py | 9 ++++++- kitty/mouse.c | 57 +++++++++++++++++++++++++++++++++++++------- kitty/screen.c | 64 ++++++++++++++++++++------------------------------ kitty/screen.h | 4 ++++ kitty/state.c | 8 +++++++ kitty/state.h | 16 +++++++++++-- 6 files changed, 108 insertions(+), 50 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 00999f5fa..c74d41562 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -21,7 +21,7 @@ from .keys import ( ) from .session import create_session from .tabs import SpecialWindow, TabManager -from .utils import safe_print, get_primary_selection +from .utils import safe_print, get_primary_selection, set_primary_selection from .window import load_shader_programs @@ -288,6 +288,13 @@ class Boss: text = get_primary_selection() self.paste_to_active_window(text) + def set_primary_selection(self): + w = self.active_window + if w is not None and not w.destroyed: + text = w.text_for_selection() + if text: + set_primary_selection(text) + def next_tab(self): self.tab_manager.next_tab() diff --git a/kitty/mouse.c b/kitty/mouse.c index 7994c40b5..c666b2813 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -38,8 +38,9 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y) { return ret; } -void -handle_move_event(Window *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) { +#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) + +HANDLER(handle_move_event) { unsigned int x, y; if (cell_for_pos(w, &x, &y)) { Line *line = screen_visual_line(w->render_data.screen, y); @@ -50,8 +51,46 @@ handle_move_event(Window *w, int UNUSED button, int UNUSED modifiers, unsigned i } } -void -handle_button_event(Window *w, int button, int modifiers, unsigned int window_idx) { +static inline void +multi_click(Window *w, unsigned int count) { + Screen *screen = w->render_data.screen; + index_type start, end; + bool found_selection = false; + switch(count) { + case 2: + found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, w->mouse_cell_y, &start, &end); + break; + case 3: + found_selection = screen_selection_range_for_line(screen, w->mouse_cell_y, &start, &end); + break; + default: + break; + } + if (found_selection) { + screen_start_selection(screen, start, w->mouse_cell_y); + screen_update_selection(screen, end, w->mouse_cell_y, true); + call_boss(set_primary_selection, NULL); + } +} + +HANDLER(add_click) { + ClickQueue *q = &w->click_queue; + if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; } + double now = monotonic(); +#define N(n) (q->clicks[q->length - n]) + N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; + q->length++; + // Now dispatch the multi-click if any + if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) { + multi_click(w, 3); + q->length = 0; + } else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) { + multi_click(w, 2); + } +#undef N +} + +HANDLER(handle_button_event) { Tab *t = global_state.tabs + global_state.active_tab; bool is_release = !global_state.mouse_button_pressed[button]; if (window_idx != t->active_window) { @@ -72,8 +111,8 @@ handle_button_event(Window *w, int button, int modifiers, unsigned int window_id if (is_release) { if (modifiers == (int)OPT(open_url_modifiers)) { // TODO: click_url - } else if (!modifiers) { - // TODO: handle multi click + } else { + if (is_release) add_click(w, button, modifiers, window_idx); } } break; @@ -85,8 +124,7 @@ handle_button_event(Window *w, int button, int modifiers, unsigned int window_id } } -void -handle_event(Window *w, int button, int modifiers, unsigned int window_idx) { +HANDLER(handle_event) { switch(button) { case -1: handle_move_event(w, button, modifiers, window_idx); @@ -103,7 +141,8 @@ handle_event(Window *w, int button, int modifiers, unsigned int window_idx) { } } -void handle_tab_bar_mouse(int button, int UNUSED modifiers) { +static inline void +handle_tab_bar_mouse(int button, int UNUSED modifiers) { if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.mouse_button_pressed[button]) return; call_boss(activate_tab_at, "d", global_state.mouse_x); } diff --git a/kitty/screen.c b/kitty/screen.c index 396baf306..de58e0d33 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1344,41 +1344,40 @@ text_for_selection(Screen *self) { return ans; } -static PyObject* -selection_range_for_line(Screen *self, PyObject *args) { - unsigned int y; - if (!PyArg_ParseTuple(args, "I", &y)) return NULL; - if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; } +bool +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->cells[xlimit - 1].ch)) xlimit--; while (xstart < xlimit && CHAR_IS_BLANK(line->cells[xstart].ch)) xstart++; - return Py_BuildValue("II", (unsigned int)xstart, (unsigned int)xlimit); + *start = xstart; *end = xlimit; + return true; } static inline bool -has_char(int kind, void *data, Py_ssize_t sz, char_type ch) { - for (Py_ssize_t i = 0; i < sz; i++) { - if (PyUnicode_READ(kind, data, i) == ch) return true; +is_opt_word_char(char_type ch) { + for (size_t i = 0; i < OPT(select_by_word_characters_count); i++) { + if (OPT(select_by_word_characters[i]) == ch) return true; } return false; } -static PyObject* -selection_range_for_word(Screen *self, PyObject *args) { - unsigned int x, y; - PyObject *extra_chars; - if (!PyArg_ParseTuple(args, "IIU", &x, &y, &extra_chars)) return NULL; - if (PyUnicode_READY(extra_chars) != 0) return NULL; - if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; } - if (x >= self->columns) { PyErr_SetString(PyExc_ValueError, "x larger than columns"); return NULL; } +bool +screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *s, index_type *e) { + if (y >= self->lines || x >= self->columns) return false; + index_type start, end; Line *line = visual_line_(self, y); -#define is_ok(x) (is_word_char((line->cells[x].ch) & CHAR_MASK) || has_char(PyUnicode_KIND(extra_chars), PyUnicode_DATA(extra_chars), PyUnicode_GET_LENGTH(extra_chars), (line->cells[x].ch) & CHAR_MASK)) - if (!is_ok(x)) Py_BuildValue("II", x, x + 1); - unsigned int start = x, end = x; - while(start > 0 && is_ok(start - 1)) start--; - while(end < self->columns - 1 && is_ok(end + 1)) end++; - return Py_BuildValue("II", start, end + 1); +#define is_ok(x) (is_word_char((line->cells[x].ch) & CHAR_MASK) || is_opt_word_char(line->cells[x].ch & CHAR_MASK)) + if (!is_ok(x)) { + start = x; end = x + 1; + } else { + start = x, end = x; + while(start > 0 && is_ok(start - 1)) start--; + while(end < self->columns - 1 && is_ok(end + 1)) end++; + } + *s = start; *e = end; + return true; #undef is_ok } @@ -1433,24 +1432,17 @@ screen_is_selection_dirty(Screen *self) { return false; } -static PyObject* -start_selection(Screen *self, PyObject *args) { - unsigned int x, y; - if (!PyArg_ParseTuple(args, "II", &x, &y)) return NULL; +void +screen_start_selection(Screen *self, index_type x, index_type y) { #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); #undef A - Py_RETURN_NONE; } -static PyObject* -update_selection(Screen *self, PyObject *args) { - unsigned int x, y; - int ended; - if (!PyArg_ParseTuple(args, "IIp", &x, &y, &ended)) return NULL; +void +screen_update_selection(Screen *self, index_type x, index_type y, bool ended) { self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by; if (ended) self->selection.in_progress = false; - Py_RETURN_NONE; } static PyObject* @@ -1533,12 +1525,8 @@ static PyMethodDef methods[] = { MND(mark_as_dirty, METH_NOARGS) MND(resize, METH_VARARGS) MND(set_margins, METH_VARARGS) - MND(selection_range_for_line, METH_VARARGS) - MND(selection_range_for_word, METH_VARARGS) MND(text_for_selection, METH_NOARGS) MND(is_selection_in_progress, METH_NOARGS) - MND(start_selection, METH_VARARGS) - MND(update_selection, METH_VARARGS) MND(scroll, METH_VARARGS) MND(toggle_alt_screen, METH_NOARGS) MND(reset_callbacks, METH_NOARGS) diff --git a/kitty/screen.h b/kitty/screen.h index 3e802ff51..ed448447a 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -64,6 +64,10 @@ bool screen_is_selection_dirty(Screen *self); bool screen_invert_colors(Screen *self); void screen_update_cell_data(Screen *self, void *address, size_t sz); 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_word(Screen *self, index_type x, index_type y, index_type *start, index_type *end); +void screen_start_selection(Screen *self, index_type x, index_type y); +void screen_update_selection(Screen *self, index_type x, index_type y, bool ended); Line* screen_visual_line(Screen *self, index_type y); unsigned long screen_current_char_width(Screen *self); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); diff --git a/kitty/state.c b/kitty/state.c index 4ef58283c..be216970a 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -128,6 +128,14 @@ PYWRAP1(set_options) { S(cursor_opacity, PyFloat_AsDouble); S(mouse_hide_wait, PyFloat_AsDouble); S(open_url_modifiers, PyLong_AsUnsignedLong); + S(click_interval, PyFloat_AsDouble); + PyObject *chars = PyObject_GetAttrString(args, "select_by_word_characters"); + if (chars == NULL) return NULL; + for (size_t i = 0; i < MIN((size_t)PyUnicode_GET_LENGTH(chars), sizeof(OPT(select_by_word_characters))/sizeof(OPT(select_by_word_characters[0]))); i++) { + OPT(select_by_word_characters)[i] = PyUnicode_READ(PyUnicode_KIND(chars), PyUnicode_DATA(chars), i); + } + OPT(select_by_word_characters_count) = PyUnicode_GET_LENGTH(chars); + Py_DECREF(chars); #undef S Py_RETURN_NONE; } diff --git a/kitty/state.h b/kitty/state.h index 69fb156cc..f78b0b746 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -10,11 +10,11 @@ #define OPT(name) global_state.opts.name typedef struct { - double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait; + double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, cursor_opacity; bool enable_audio_bell; CursorShape cursor_shape; - double cursor_opacity; unsigned int open_url_modifiers; + char_type select_by_word_characters[256]; size_t select_by_word_characters_count; } Options; typedef struct { @@ -27,6 +27,17 @@ typedef struct { unsigned int left, top, right, bottom; } WindowGeometry; +typedef struct { + double at; + int button, modifiers; +} Click; + +#define CLICK_QUEUE_SZ 3 +typedef struct { + Click clicks[CLICK_QUEUE_SZ]; + unsigned int length; +} ClickQueue; + typedef struct { unsigned int id; bool visible; @@ -34,6 +45,7 @@ typedef struct { ScreenRenderData render_data; unsigned int mouse_cell_x, mouse_cell_y; WindowGeometry geometry; + ClickQueue click_queue; } Window; typedef struct {