From 95683c658b4d3f68af0196178a7b9c10c48bc1b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 13 Dec 2017 11:11:13 +0530 Subject: [PATCH] When hovering over a URL, highlight the URL fully even if it continues over multiple lines. Note that URL detection has not changed. A URL will be detected only if the mouse hovers over the line containing the start of the URL. --- kitty/boss.py | 3 ++ kitty/cell_vertex.glsl | 17 ++++----- kitty/line.c | 6 ++-- kitty/lineops.h | 2 +- kitty/mouse.c | 33 +++++++++-------- kitty/screen.c | 80 +++++++++++++++++++++++++----------------- kitty/screen.h | 4 +-- kitty/shaders.c | 7 ++-- 8 files changed, 84 insertions(+), 68 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 15fb10a9e..177d40b99 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -291,6 +291,9 @@ class Boss: if url: open_url(url, self.opts.open_url_with) + def open_url_lines(self, lines): + self.open_url(''.join(lines)) + def destroy(self): self.shutting_down = True self.child_monitor.shutdown_monitor() diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index e076773ff..318e71afb 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -10,7 +10,7 @@ layout(std140) uniform CellRenderData { int color1, color2; - uint xnum, ynum, cursor_x, cursor_y, cursor_w, url_xl, url_yl, url_xr, url_yr; + uint xnum, ynum, cursor_x, cursor_y, cursor_w; uint color_table[256]; }; @@ -18,7 +18,7 @@ layout(std140) uniform CellRenderData { // Have to use fixed locations here as all variants of the cell program share the same VAO layout(location=0) in uvec3 colors; layout(location=1) in uvec4 sprite_coords; -layout(location=2) in float is_selected; +layout(location=2) in uint is_selected; @@ -108,11 +108,6 @@ vec3 choose_color(float q, vec3 a, vec3 b) { return mix(b, a, q); } -float in_range(uint x, uint y) { - if (url_yl == y && url_xl <= x && x <= url_xr) return 1.0; - return 0.0; -} - float is_cursor(uint x, uint y) { if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0; return 0.0; @@ -159,9 +154,9 @@ void main() { uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); foreground = color_to_vec(resolved_fg); // Selection - foreground = choose_color(is_selected, color_to_vec(highlight_fg), foreground); + foreground = choose_color(float(is_selected & ONE), color_to_vec(highlight_fg), foreground); // Underline and strike through (rendered via sprites) - float in_url = in_range(c, r); + float in_url = float((is_selected & TWO) >> 1); decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg)); underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO)); strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * FOUR, ZERO, ZERO); @@ -189,11 +184,11 @@ void main() { #if defined(SPECIAL) || defined(SIMPLE) // Selection and cursor - bg = choose_color(is_selected, color_to_vec(highlight_bg), bg); + bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg); background = choose_color(cursor, color_to_vec(cursor_color), bg); #ifdef SPECIAL // bg_alpha should be 1 if cursor/selection otherwise 0 - bg_alpha = mix(0.0, 1.0, step(0.5, is_selected + cursor)); + bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cursor)); #endif #endif diff --git a/kitty/line.c b/kitty/line.c index c2af8f98f..128f4575a 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -135,9 +135,9 @@ line_url_start_at(Line *self, index_type x) { } index_type -line_url_end_at(Line *self, index_type x) { +line_url_end_at(Line *self, index_type x, bool check_short) { index_type ans = x; - if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return 0; + if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0; while (ans < self->xnum && is_url_char(self->cells[ans].ch)) ans++; ans--; while (ans > x && can_strip_from_end_of_url(self->cells[ans].ch)) ans--; @@ -153,7 +153,7 @@ url_start_at(Line *self, PyObject *x) { static PyObject* url_end_at(Line *self, PyObject *x) { #define url_end_at_doc "url_end_at(x) -> Return the end cell number for a URL containing x or 0 if not found" - return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, PyLong_AsUnsignedLong(x))); + return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, PyLong_AsUnsignedLong(x), true)); } // }}} diff --git a/kitty/lineops.h b/kitty/lineops.h index b8a90c2ef..470ce641d 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -51,7 +51,7 @@ void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bo void line_right_shift(Line *, unsigned int , unsigned int ); void line_add_combining_char(Line *, uint32_t , unsigned int ); index_type line_url_start_at(Line *self, index_type x); -index_type line_url_end_at(Line *self, index_type x); +index_type line_url_end_at(Line *self, index_type x, bool); index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen); unsigned int line_length(Line *self); size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type); diff --git a/kitty/mouse.c b/kitty/mouse.c index e57410035..2add8c26b 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -142,20 +142,33 @@ drag_scroll(Window *w, OSWindow *frame) { return false; } +static inline void +extend_url(Screen *screen, Line *line, index_type *x, index_type *y) { + while(true) { + if (*x != line->xnum - 1) break; + line = screen_visual_line(screen, *y + 1); + if (!line) break; // we deliberately allow non-continued lines as some programs, like mutt split URLs with newlines at line boundaries + index_type new_x = line_url_end_at(line, 0, false); + if (!new_x) break; + *y += 1; *x = new_x; + } +} static inline void -detect_url(Window *w, Screen *screen, unsigned int x, unsigned int y) { +detect_url(Screen *screen, unsigned int x, unsigned int y) { bool has_url = false; index_type url_start, url_end = 0; Line *line = screen_visual_line(screen, y); if (line) { url_start = line_url_start_at(line, x); - if (url_start < line->xnum) url_end = line_url_end_at(line, x); + if (url_start < line->xnum) url_end = line_url_end_at(line, x, true); has_url = url_end > url_start; } if (has_url) { mouse_cursor_shape = HAND; - screen_mark_url(screen, url_start, w->mouse_cell_y, url_end, w->mouse_cell_y); + index_type y_extended = y; + extend_url(screen, line, &url_end, &y_extended); + screen_mark_url(screen, url_start, y, url_end, y_extended); } else { mouse_cursor_shape = BEAM; screen_mark_url(screen, 0, 0, 0, 0); @@ -173,7 +186,7 @@ HANDLER(handle_move_event) { } if (!cell_for_pos(w, &x, &y)) return; Screen *screen = w->render_data.screen; - detect_url(w, screen, x, y); + detect_url(screen, x, y); bool mouse_cell_changed = x != w->mouse_cell_x || y != w->mouse_cell_y; w->mouse_cell_x = x; w->mouse_cell_y = y; bool handle_in_kitty = ( @@ -237,16 +250,8 @@ HANDLER(add_click) { static inline void open_url(Window *w) { - Line *line = screen_visual_line(w->render_data.screen, w->mouse_cell_y); - if (line) { - index_type start = line_url_start_at(line, w->mouse_cell_x); - if (start < line->xnum) { - index_type end = line_url_end_at(line, w->mouse_cell_x); - if (end > start) { - call_boss(open_url, "N", unicode_in_range(line, start, end + 1, true, 0)); - } - } - } + Screen *screen = w->render_data.screen; + screen_open_url(screen); } HANDLER(handle_button_event) { diff --git a/kitty/screen.c b/kitty/screen.c index 5b095c2a6..b6f6a5c6c 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -180,6 +180,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { self->is_dirty = true; self->selection = EMPTY_SELECTION; self->url_range = EMPTY_SELECTION; + self->selection_updated_once = false; // Ensure cursor is on the correct line self->cursor->x = 0; @@ -1225,33 +1226,57 @@ visual_line_(Screen *self, index_type y) { return self->linebuf->line; } -void -screen_apply_selection(Screen *self, void *address, size_t size) { -#define start (self->last_rendered_selection_start) -#define end (self->last_rendered_selection_end) - float *data = address; - memset(data, 0, size); - selection_limits_(selection, &start, &end); - self->last_selection_scrolled_by = self->scrolled_by; - self->selection_updated_once = true; - if (is_selection_empty(self, start.x, start.y, end.x, end.y)) { return; } - for (index_type y = start.y; y <= end.y; y++) { +static inline void +apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask) { + if (is_selection_empty(self, start->x, start->y, end->x, end->y)) return; + for (index_type y = start->y; y <= end->y; y++) { Line *line = visual_line_(self, y); index_type xlimit = xlimit_for_line(line); - if (y == end.y) xlimit = MIN(end.x + 1, xlimit); - float *line_start = data + self->columns * y; - for (index_type x = (y == start.y ? start.x : 0); x < xlimit; x++) line_start[x] = 1.0; + if (y == end->y) xlimit = MIN(end->x + 1, xlimit); + uint8_t *line_start = data + self->columns * y; + for (index_type x = (y == start->y ? start->x : 0); x < xlimit; x++) line_start[x] |= set_mask; } -#undef start -#undef end + } void -screen_url_range(Screen *self, uint32_t *data) { +screen_apply_selection(Screen *self, void *address, size_t size) { + memset(address, 0, 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); + 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); +} + +static inline PyObject* +text_for_range(Screen *self, SelectionBoundary start, SelectionBoundary end) { + Py_ssize_t i = 0, num_of_lines = end.y - start.y + 1; + PyObject *ans = PyTuple_New(num_of_lines); + if (ans == NULL) return PyErr_NoMemory(); + for (index_type y = start.y; y <= end.y; y++, i++) { + Line *line = visual_line_(self, y); + index_type xlimit = xlimit_for_line(line); + if (y == end.y) xlimit = MIN(end.x + 1, xlimit); + index_type xstart = (y == start.y ? start.x : 0); + char leading_char = i > 0 && !line->continued ? '\n' : 0; + PyObject *text = unicode_in_range(line, xstart, xlimit, true, leading_char); + if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } + PyTuple_SET_ITEM(ans, i, text); + } + return ans; +} + +bool +screen_open_url(Screen *self) { SelectionBoundary start, end; selection_limits_(url_range, &start, &end); - if (is_selection_empty(self, start.x, start.y, end.x, end.y)) { *(data + 1) = self->lines; *(data + 3) = self->lines; *data = self->columns; *(data + 2) = self->columns; } - else { *data = start.x; *(data+1) = start.y; *(data + 2) = end.x; *(data + 3) = end.y; } + if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return false; + PyObject *text = text_for_range(self, start, end); + if (text) { call_boss(open_url_lines, "(O)", text); Py_CLEAR(text); } + else PyErr_Print(); + return true; } // }}} @@ -1410,20 +1435,7 @@ text_for_selection(Screen *self) { SelectionBoundary start, end; selection_limits_(selection, &start, &end); if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return PyTuple_New(0); - Py_ssize_t i = 0, num_of_lines = end.y - start.y + 1; - PyObject *ans = PyTuple_New(num_of_lines); - if (ans == NULL) return PyErr_NoMemory(); - for (index_type y = start.y; y <= end.y; y++, i++) { - Line *line = visual_line_(self, y); - index_type xlimit = xlimit_for_line(line); - if (y == end.y) xlimit = MIN(end.x + 1, xlimit); - index_type xstart = (y == start.y ? start.x : 0); - char leading_char = i > 0 && !line->continued ? '\n' : 0; - PyObject *text = unicode_in_range(line, xstart, xlimit, true, leading_char); - if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } - PyTuple_SET_ITEM(ans, i, text); - } - return ans; + return text_for_range(self, start, end); } bool @@ -1506,6 +1518,8 @@ screen_is_selection_dirty(Screen *self) { SelectionBoundary start, end; selection_limits_(selection, &start, &end); if (self->last_selection_scrolled_by != self->scrolled_by || start.x != self->last_rendered_selection_start.x || start.y != self->last_rendered_selection_start.y || end.x != self->last_rendered_selection_end.x || end.y != self->last_rendered_selection_end.y || !self->selection_updated_once) return true; + selection_limits_(url_range, &start, &end); + if (start.x != self->last_rendered_url_start.x || start.y != self->last_rendered_url_start.y || end.x != self->last_rendered_url_end.x || end.y != self->last_rendered_url_end.y) return true; return false; } diff --git a/kitty/screen.h b/kitty/screen.h index 8af19e3a1..5ae7605b8 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -34,7 +34,7 @@ typedef struct { id_type window_id; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; Selection selection; - SelectionBoundary last_rendered_selection_start, last_rendered_selection_end; + SelectionBoundary last_rendered_selection_start, last_rendered_selection_end, last_rendered_url_start, last_rendered_url_end; Selection url_range; bool use_latin1, selection_updated_once, is_dirty, scroll_changed; Cursor *cursor; @@ -130,9 +130,9 @@ void screen_update_selection(Screen *self, index_type x, index_type y, bool ende bool screen_history_scroll(Screen *self, int amt, bool upwards); Line* screen_visual_line(Screen *self, index_type y); unsigned long screen_current_char_width(Screen *self); -void screen_url_range(Screen *self, uint32_t *); void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y); void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload); +bool screen_open_url(Screen*); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/shaders.c b/kitty/shaders.c index 7563ee4ee..0636ce692 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -177,7 +177,7 @@ create_cell_vao() { A1(colors, 3, GL_UNSIGNED_INT, fg); add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); - A(is_selected, 1, GL_FLOAT, NULL, 0); + A(is_selected, 1, GL_UNSIGNED_BYTE, NULL, 0); size_t bufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER); alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].render_data.size, bufnum, GL_STREAM_DRAW); @@ -204,7 +204,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G GLint color1, color2; - GLuint xnum, ynum, cursor_x, cursor_y, cursor_w, url_xl, url_yl, url_xr, url_yr; + GLuint xnum, ynum, cursor_x, cursor_y, cursor_w; }; static struct CellRenderData *rd; @@ -222,7 +222,6 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G rd->cursor_w = rd->cursor_x + MAX(1, screen_current_char_width(screen)) - 1; rd->xnum = screen->columns; rd->ynum = screen->lines; - screen_url_range(screen, &rd->url_xl); rd->xstart = xstart; rd->ystart = ystart; rd->dx = dx; rd->dy = dy; unsigned int x, y, z; @@ -255,7 +254,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa } if (screen_is_selection_dirty(screen)) { - sz = sizeof(GLfloat) * screen->lines * screen->columns; + sz = screen->lines * screen->columns; address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY); screen_apply_selection(screen, address, sz); unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;