diff --git a/kitty/line.c b/kitty/line.c index 938bd6caa..72fe0320e 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -172,26 +172,43 @@ is_url_lc(const ListOfChars *lc) { return true; } +static index_type +next_char_pos(const Line *self, index_type x, index_type num) { + const CPUCell *ans = self->cpu_cells + x, *limit = self->cpu_cells + self->xnum; + while (num-- && ans < limit) ans += ans->is_multicell ? mcd_x_limit(ans) - ans->x : 1; + return ans - self->cpu_cells; +} static index_type -find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc) { +prev_char_pos(const Line *self, index_type x, index_type num) { + const CPUCell *ans = self->cpu_cells + x, *limit = self->cpu_cells - 1; + if (ans->is_multicell) ans -= ans->x; + while (num-- && --ans > limit) if (ans->is_multicell) ans -= ans->x; + return ans > limit ? ans - self->cpu_cells : self->xnum; +} + + +static index_type +find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc, index_type scale) { // Find :// at or before x index_type pos = MIN(x, self->xnum - 1); enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH}; enum URL_PARSER_STATES state = ANY; limit = MAX(2u, limit); if (pos < limit) return 0; + const CPUCell *c = self->cpu_cells + pos; + index_type n; +#define next_char_is(num, ch) ((n = next_char_pos(self, pos, num)) < self->xnum && cell_is_char(self->cpu_cells + n, ch) && cell_scale(self->cpu_cells + n) == scale) + if (cell_is_char(c, ':')) { + if (next_char_is(1, '/') && next_char_is(2, '/')) state = SECOND_SLASH; + } else if (cell_is_char(c, '/')) { + if (next_char_is(1, '/')) state = FIRST_SLASH; + } +#undef next_char_is + do { - const CPUCell *c = self->cpu_cells + pos; text_in_cell(c, self->text_cache, lc); if (!is_hostname_lc(lc)) return false; - if (pos == x) { - if (cell_is_char(c, ':')) { - if (pos + 2 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/') && cell_is_char(self->cpu_cells + pos + 2, '/')) state = SECOND_SLASH; - } else if (cell_is_char(c, '/')) { - if (pos + 1 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/')) state = FIRST_SLASH; - } - } switch(state) { case ANY: if (cell_is_char(c, '/')) state = FIRST_SLASH; @@ -204,7 +221,10 @@ find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc) { state = cell_is_char(c, '/') ? SECOND_SLASH : ANY; break; } - pos--; + pos = prev_char_pos(self, pos, 1); + if (pos >= self->xnum) break; + c = self->cpu_cells + pos; + if (cell_scale(c) != scale) break; } while(pos >= limit); return 0; } @@ -249,19 +269,19 @@ has_url_beyond_colon_slash(Line *self, index_type x, ListOfChars *lc) { } index_type -line_url_start_at(Line *self, index_type x) { +line_url_start_at(Line *self, index_type x, ListOfChars *lc) { // Find the starting cell for a URL that contains the position x. A URL is defined as // known-prefix://url-chars. If no URL is found self->xnum is returned. + if (self->cpu_cells[x].is_multicell && self->cpu_cells[x].x) x = x > self->cpu_cells[x].x ? x - self->cpu_cells[x].x : 0; if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return self->xnum; - index_type ds_pos = 0, t; - RAII_ListOfChars(lc); + index_type ds_pos = 0, t, scale = cell_scale(self->cpu_cells + x); // First look for :// ahead of x - ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2, &lc); - if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos, &lc)) { + ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2, lc, scale); + if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos, lc)) { if (has_url_prefix_at(self, ds_pos, ds_pos > x ? ds_pos - x: 0, &t)) return t; } - ds_pos = find_colon_slash(self, x, 0, &lc); - if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos, &lc)) return self->xnum; + ds_pos = find_colon_slash(self, x, 0, lc, scale); + if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos, lc)) return self->xnum; if (has_url_prefix_at(self, ds_pos, 0, &t)) return t; return self->xnum; } @@ -302,7 +322,8 @@ line_startswith_url_chars(Line *self, bool in_hostname) { static PyObject* url_start_at(Line *self, PyObject *x) { #define url_start_at_doc "url_start_at(x) -> Return the start cell number for a URL containing x or self->xnum if not found" - return PyLong_FromUnsignedLong((unsigned long)line_url_start_at(self, PyLong_AsUnsignedLong(x))); + RAII_ListOfChars(lc); + return PyLong_FromUnsignedLong((unsigned long)line_url_start_at(self, PyLong_AsUnsignedLong(x), &lc)); } static PyObject* diff --git a/kitty/line.h b/kitty/line.h index f1c587c98..1485f5a66 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -116,6 +116,7 @@ const char* cell_as_sgr(const GPUCell *, const GPUCell *); static inline bool cell_has_text(const CPUCell *c) { return c->ch_and_idx != 0; } static inline void cell_set_char(CPUCell *c, char_type ch) { c->ch_and_idx = ch & 0x7fffffff; } static inline bool cell_is_char(const CPUCell *c, char_type ch) { return c->ch_and_idx == ch; } +static inline index_type cell_scale(const CPUCell *c) { return c->is_multicell ? c->scale : 1; } static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache *tc) { unsigned ans; if (c->ch_is_idx) { diff --git a/kitty/lineops.h b/kitty/lineops.h index 85c6e7774..4199cff7f 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -69,7 +69,7 @@ typedef Line*(get_line_func)(void *, int); 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); +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); 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)); diff --git a/kitty/screen.c b/kitty/screen.c index 8a6112c91..7abacd156 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -616,6 +616,19 @@ init_line(Screen *self, index_type y) { return self->linebuf->line; } +static void +visual_line(Screen *self, int y_, Line *line) { + 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, line); + return; + } + y -= self->scrolled_by; + } + init_line_(self, y, line); +} + static Line* visual_line_(Screen *self, int y_) { index_type y = MAX(0, y_); @@ -3750,33 +3763,37 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { index_type url_start, url_end = 0; Line *line = screen_visual_line(screen, y); if (!line || x >= screen->columns) return 0; + if (line->cpu_cells[x].is_multicell && line->cpu_cells[x].scale > 1 && line->cpu_cells[x].y) { + if (line->cpu_cells[x].y > y) return 0; + y -= line->cpu_cells[x].y; + line = screen_visual_line(screen, y); + } + if (line->cpu_cells[x].is_multicell && line->cpu_cells[x].x) x = x > line->cpu_cells[x].x ? x - line->cpu_cells[x].x : 0; hyperlink_id_type hid; if ((hid = line->cpu_cells[x].hyperlink_id)) { screen_mark_hyperlink(screen, x, y); return hid; } char_type sentinel = 0; - bool newlines_allowed = !is_excluded_from_url('\n'); + const bool newlines_allowed = !is_excluded_from_url('\n'); index_type last_hostname_char_pos = screen->columns; - if (line) { - url_start = line_url_start_at(line, x); - if (url_start < line->xnum) { - bool next_line_starts_with_url_chars = false; - if (y < screen->lines - 1) { - line = screen_visual_line(screen, y+1); - next_line_starts_with_url_chars = line_startswith_url_chars(line, last_hostname_char_pos >= line->xnum); - if (next_line_starts_with_url_chars && !newlines_allowed && !line->attrs.is_continued) next_line_starts_with_url_chars = false; - line = screen_visual_line(screen, y); - } - sentinel = get_url_sentinel(line, url_start); - index_type slash_count = 0; - 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_start = line_url_start_at(line, x, screen->lc); + Line scratch = {.xnum=line->xnum, .text_cache=line->text_cache}; + if (url_start < line->xnum) { + 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); + if (next_line_starts_with_url_chars && !newlines_allowed && !scratch.attrs.is_continued) next_line_starts_with_url_chars = false; } - has_url = url_end > url_start; + sentinel = get_url_sentinel(line, url_start); + index_type slash_count = 0; + 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); } + has_url = url_end > url_start; if (has_url) { index_type y_extended = y; extend_url(screen, line, &url_end, &y_extended, sentinel, newlines_allowed, last_hostname_char_pos); @@ -4260,7 +4277,7 @@ screen_visual_line(Screen *self, index_type y) { } static PyObject* -visual_line(Screen *self, PyObject *args) { +pyvisual_line(Screen *self, PyObject *args) { // The line corresponding to the yth visual line, taking into account scrolling unsigned int y; if (!PyArg_ParseTuple(args, "I", &y)) return NULL; @@ -5389,7 +5406,7 @@ static PyMethodDef methods[] = { MND(dump_lines_with_attrs, METH_VARARGS) MND(cpu_cells, METH_VARARGS) MND(cursor_at_prompt, METH_NOARGS) - MND(visual_line, METH_VARARGS) + {"visual_line", (PyCFunction)pyvisual_line, METH_VARARGS, ""}, MND(current_url_text, METH_NOARGS) MND(draw, METH_O) MND(apply_sgr, METH_O)