From 6a19918687567f8a05ed35375c0a10ba8a8611e3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Jan 2025 11:11:16 +0530 Subject: [PATCH] More work on multicell URL detection --- kitty/line.c | 64 ++++++++++++++++++++++++++++++---------- kitty/lineops.h | 5 ++-- kitty/screen.c | 33 ++++----------------- kitty_tests/multicell.py | 9 ++++++ 4 files changed, 65 insertions(+), 46 deletions(-) diff --git a/kitty/line.c b/kitty/line.c index df2d8b89b..64d173972 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -296,29 +296,60 @@ is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_ho } index_type -line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos) { +line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) { index_type ans = x; - if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0; - RAII_ListOfChars(lc); -#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos, &lc) - if (sentinel) { while (ans < self->xnum && !cell_is_char(self->cpu_cells + ans, sentinel) && pos_ok(ans)) ans++; } - else { while (ans < self->xnum && pos_ok(ans)) ans++; } - if (ans) ans--; - if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) { - while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) ans--; +#define is_not_ok(n) ((sentinel && cell_is_char(self->cpu_cells + n, sentinel)) || !is_pos_ok_for_url(self, n, in_hostname, last_hostname_char_pos, lc)) + if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3) || is_not_ok(x)) return 0; + index_type n = ans; + while ((n = next_char_pos(self, ans, 1)) < self->xnum) { + if (is_not_ok(n)) break; + ans = n; + } +#undef is_not_ok + if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) { + while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) { + n = prev_char_pos(self, ans, 1); + if (n >= self->xnum || n < x) break; + ans = n; + } } -#undef pos_ok return ans; } bool -line_startswith_url_chars(Line *self, bool in_hostname) { - RAII_ListOfChars(lc); - text_in_cell(self->cpu_cells, self->text_cache, &lc); - if (in_hostname) return is_hostname_lc(&lc); - return is_url_lc(&lc); +line_startswith_url_chars(Line *self, bool in_hostname, ListOfChars *lc) { + text_in_cell(self->cpu_cells, self->text_cache, lc); + if (in_hostname) return is_hostname_lc(lc); + return is_url_lc(lc); } +char_type +get_url_sentinel(Line *line, index_type url_start) { + char_type before = 0, sentinel; + if (url_start > 0 && url_start < line->xnum) { + index_type n = prev_char_pos(line, url_start, 1); + if (n < line->xnum) before = cell_first_char(line->cpu_cells + n, line->text_cache); + } + switch(before) { + case '"': + case '\'': + case '*': + sentinel = before; break; + case '(': + sentinel = ')'; break; + case '[': + sentinel = ']'; break; + case '{': + sentinel = '}'; break; + case '<': + sentinel = '>'; break; + default: + sentinel = 0; break; + } + return sentinel; +} + + static PyObject* url_start_at(Line *self, PyObject *x) { @@ -333,7 +364,8 @@ url_end_at(Line *self, PyObject *args) { unsigned int x, sentinel = 0; int next_line_starts_with_url_chars = 0; if (!PyArg_ParseTuple(args, "I|Ip", &x, &sentinel, &next_line_starts_with_url_chars)) return NULL; - return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum)); + RAII_ListOfChars(lc); + return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum, &lc)); } // }}} diff --git a/kitty/lineops.h b/kitty/lineops.h index 4199cff7f..1079d2a63 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -70,8 +70,9 @@ void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsigned int num, bool clear_char); char_type line_get_char(Line *self, index_type at); index_type line_url_start_at(Line *self, index_type x, ListOfChars *lc); -index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type); -bool line_startswith_url_chars(Line*, bool); +index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type, ListOfChars*); +bool line_startswith_url_chars(Line*, bool, ListOfChars*); +char_type get_url_sentinel(Line *line, index_type url_start); bool line_as_ansi(Line *self, ANSILineState *s, index_type start_at, index_type stop_before, char_type prefix_char, bool skip_multiline_non_zero_lines) __attribute__((nonnull)); unsigned int line_length(Line *self); size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf); diff --git a/kitty/screen.c b/kitty/screen.c index 7abacd156..f81bb1b58 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -3706,7 +3706,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s bool next_line_starts_with_url_chars = false; line = screen_visual_line(screen, *y + 2); if (line) { - next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname); + next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname, screen->lc); has_newline = !line->attrs.is_continued; if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false; if (sentinel && next_line_starts_with_url_chars && cell_is_char(line->cpu_cells, sentinel)) next_line_starts_with_url_chars = false; @@ -3722,8 +3722,8 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s } } } - index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos); - if (!new_x && !line_startswith_url_chars(line, in_hostname)) break; + index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos, screen->lc); + if (!new_x && !line_startswith_url_chars(line, in_hostname, screen->lc)) break; *y += 1; *x = new_x; } if (sentinel && *x == 0 && *y > orig_y) { @@ -3734,29 +3734,6 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s } } -static char_type -get_url_sentinel(Line *line, index_type url_start) { - char_type before = 0, sentinel; - if (url_start > 0 && url_start < line->xnum) before = cell_first_char(line->cpu_cells + url_start - 1, line->text_cache); - switch(before) { - case '"': - case '\'': - case '*': - sentinel = before; break; - case '(': - sentinel = ')'; break; - case '[': - sentinel = ']'; break; - case '{': - sentinel = '}'; break; - case '<': - sentinel = '>'; break; - default: - sentinel = 0; break; - } - return sentinel; -} - int screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { bool has_url = false; @@ -3783,7 +3760,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { bool next_line_starts_with_url_chars = false; if (y < screen->lines - 1) { visual_line(screen, y + 1, &scratch); - next_line_starts_with_url_chars = line_startswith_url_chars(&scratch, last_hostname_char_pos >= line->xnum); + next_line_starts_with_url_chars = line_startswith_url_chars(&scratch, last_hostname_char_pos >= line->xnum, screen->lc); if (next_line_starts_with_url_chars && !newlines_allowed && !scratch.attrs.is_continued) next_line_starts_with_url_chars = false; } sentinel = get_url_sentinel(line, url_start); @@ -3791,7 +3768,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { for (index_type i = url_start; i < line->xnum; i++) { if (cell_is_char(line->cpu_cells + i, '/') && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; } } - url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos); + url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos, screen->lc); } has_url = url_end > url_start; if (has_url) { diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 6c5467130..d8ef162fc 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -679,3 +679,12 @@ def test_multicell(self: TestMulticell) -> None: self.ae('url-a', s.hyperlink_at(x, y)) asu((0, 0, 3), (1, 0, 3)) self.ae(s.current_url_text(), 'ab') + + # URL detection + s = self.create_screen(cols=40) + + s.reset() + url = 'http://moo.com' + multicell(s, url, scale=2) + s.detect_url(0, 0) + asl((0, 0, len(url)*2), (1, 0, len(url)*2))