From f412ec617ce23a72ee69c47a7db2a6b35a0a69bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Feb 2025 08:58:50 +0530 Subject: [PATCH] Track line continuation in only one place The canonical flag tracking this is the last cell of the previous line. So use that only rather than copying it onto a line attribute. Should micro-optimize cases where we init a line but dont need the continuation status. --- kitty/history.c | 32 ++++++++++++++++++----------- kitty/history.h | 1 + kitty/line-buf.c | 1 - kitty/line.h | 3 +-- kitty/screen.c | 53 +++++++++++++++++++++++++++++++++--------------- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/kitty/history.c b/kitty/history.c index 770f628af..99e2c7d41 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -168,22 +168,25 @@ index_of(HistoryBuf *self, index_type lnum) { return (self->start_of_data + idx) % self->ynum; } +static bool +hb_line_is_continued(HistoryBuf *self, index_type num) { + if (num == 0) { + size_t sz; + if (self->pagerhist && self->pagerhist->ringbuf && (sz = ringbuf_bytes_used(self->pagerhist->ringbuf)) > 0) { + size_t pos = ringbuf_findchr(self->pagerhist->ringbuf, '\n', sz - 1); + if (pos >= sz) return true; // ringbuf does not end with a newline + } + return false; + } + return cpu_lineptr(self, num - 1)[self->xnum-1].next_char_was_wrapped; +} + static void init_line(HistoryBuf *self, index_type num, Line *l) { // Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num l->cpu_cells = cpu_lineptr(self, num); l->gpu_cells = gpu_lineptr(self, num); l->attrs = *attrptr(self, num); - if (num > 0) { - l->attrs.is_continued = cpu_lineptr(self, num - 1)[self->xnum-1].next_char_was_wrapped; - } else { - l->attrs.is_continued = false; - size_t sz; - if (self->pagerhist && self->pagerhist->ringbuf && (sz = ringbuf_bytes_used(self->pagerhist->ringbuf)) > 0) { - size_t pos = ringbuf_findchr(self->pagerhist->ringbuf, '\n', sz - 1); - if (pos >= sz) l->attrs.is_continued = true; // ringbuf does not end with a newline - } - } } void @@ -191,6 +194,11 @@ historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) { init_line(self, index_of(self, lnum), l); } +bool +historybuf_is_line_continued(HistoryBuf *self, index_type lnum) { + return hb_line_is_continued(self, index_of(self, lnum + 1)); +} + bool history_buf_endswith_wrap(HistoryBuf *self) { return cpu_lineptr(self, index_of(self, 0))[self->xnum-1].next_char_was_wrapped; @@ -555,8 +563,8 @@ is_continued(HistoryBuf *self, PyObject *val) { #define is_continued_doc "is_continued(y) -> Whether the line y is continued or not" unsigned long y = PyLong_AsUnsignedLong(val); if (y >= self->count) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } - get_line(self, y, self->line); - if (self->line->attrs.is_continued) { Py_RETURN_TRUE; } + index_type num = index_of(self, self->count - y - 1); + if (hb_line_is_continued(self, num)) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } diff --git a/kitty/history.h b/kitty/history.h index 9639e7cff..6fe83a18a 100644 --- a/kitty/history.h +++ b/kitty/history.h @@ -40,3 +40,4 @@ HistoryBuf *historybuf_alloc_for_rewrap(unsigned int columns, HistoryBuf *self); void historybuf_finish_rewrap(HistoryBuf *dest, HistoryBuf *src); void historybuf_fast_rewrap(HistoryBuf *dest, HistoryBuf *src); index_type historybuf_next_dest_line(HistoryBuf *self, ANSIBuf *as_ansi_buf, Line *src_line, index_type dest_y, Line *dest_line, bool continued); +bool historybuf_is_line_continued(HistoryBuf *self, index_type lnum); diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 3d6a61b7d..854d00ccb 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -150,7 +150,6 @@ linebuf_init_line_at(LineBuf *self, index_type idx, Line *line) { line->ynum = idx; line->xnum = self->xnum; line->attrs = self->line_attrs[idx]; - line->attrs.is_continued = idx > 0 ? cpu_lineptr(self, self->line_map[idx - 1])[self->xnum - 1].next_char_was_wrapped : false; init_line(self, line, self->line_map[idx]); } diff --git a/kitty/line.h b/kitty/line.h index d086f6a74..f9049251c 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -70,11 +70,10 @@ static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell"); typedef union LineAttrs { struct { - uint8_t is_continued : 1; uint8_t has_dirty_text : 1; uint8_t has_image_placeholders : 1; uint8_t prompt_kind : 2; - uint8_t : 3; + uint8_t : 4; }; uint8_t val; } LineAttrs ; diff --git a/kitty/screen.c b/kitty/screen.c index 82bd3b77a..a42cc396b 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -362,9 +362,14 @@ found: return num_of_prompt_lines; } +static bool +linebuf_is_line_continued(LineBuf *linebuf, index_type y) { + return y ? linebuf_line_ends_with_continuation(linebuf, y - 1) : false; +} + static bool preserve_blank_output_start_line(Cursor *cursor, LineBuf *linebuf) { - if (cursor->x == 0 && cursor->y < linebuf->ynum && !linebuf->line_attrs[cursor->y].is_continued) { + if (cursor->x == 0 && cursor->y < linebuf->ynum && !linebuf_is_line_continued(linebuf, cursor->y)) { linebuf_init_line(linebuf, cursor->y); if (!cell_has_text(linebuf->line->cpu_cells)) { // we have a blank output start line, we need it to be preserved by @@ -605,9 +610,6 @@ typedef Line*(linefunc_t)(Screen*, int); static void init_line_(Screen *self, index_type y, Line *line) { linebuf_init_line_at(self->linebuf, y, line); - if (y == 0 && self->linebuf == self->main_linebuf) { - if (history_buf_endswith_wrap(self->historybuf)) line->attrs.is_continued = true; - } } @@ -643,6 +645,17 @@ visual_line_(Screen *self, int y_) { return init_line(self, y); } +static bool +visual_line_is_continued(Screen *self, int y_) { + index_type y = MAX(0, y_); + if (self->scrolled_by) { + if (y < self->scrolled_by) return historybuf_is_line_continued(self->historybuf, self->scrolled_by - 1 - y); + y -= self->scrolled_by; + } + if (y) return linebuf_is_line_continued(self->linebuf, y); + return self->linebuf == self->main_linebuf ? history_buf_endswith_wrap(self->historybuf) : false; +} + static Line* range_line_(Screen *self, int y) { if (y < 0) { @@ -658,7 +671,6 @@ range_line(Screen *self, int y, Line *line) { else init_line_(self, y, line); } - static Line* checked_range_line(Screen *self, int y) { if ( @@ -667,6 +679,15 @@ checked_range_line(Screen *self, int y) { return range_line_(self, y); } +static bool +range_line_is_continued(Screen *self, int y) { + if ( + (y < 0 && -(y + 1) >= (int)self->historybuf->count) || y >= (int)self->lines + ) return false; + if (y < 0) return historybuf_is_line_continued(self->historybuf, -(y + 1)); + return visual_line_is_continued(self, y); +} + static void nuke_in_line(CPUCell *cp, GPUCell *gp, index_type start, index_type x_limit, char_type ch) { for (index_type x = start; x < x_limit; x++) { @@ -3721,7 +3742,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s line = screen_visual_line(screen, *y + 2 * scale); if (line) { next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname, screen->lc); - has_newline = !line->attrs.is_continued; + has_newline = !visual_line_is_continued(screen, *y + 2 * scale); 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; } @@ -3777,7 +3798,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { if (y + scale < screen->lines) { visual_line(screen, y + scale, &scratch); 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; + if (next_line_starts_with_url_chars && !newlines_allowed && !visual_line_is_continued(screen, y + scale)) next_line_starts_with_url_chars = false; } sentinel = get_url_sentinel(line, url_start); last_hostname_char_pos = get_last_hostname_char_pos(line, url_start); @@ -4050,7 +4071,7 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig found_prompt = true; // change direction to downwards to find command output direction = 1; - } else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued) { + } else if (line && line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y1)) { found_output = true; start = y1; found_prompt = true; // keep finding the first output start upwards @@ -4064,14 +4085,14 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig // find upwards: find prompt after the output, and the first output while (y1 >= upward_limit) { line = checked_range_line(self, y1); - if (line && line->attrs.prompt_kind == PROMPT_START && !line->attrs.is_continued) { + if (line && line->attrs.prompt_kind == PROMPT_START && !range_line_is_continued(self, y1)) { if (direction == 0) { // find around: stop at prompt start start = y1 + 1; break; } found_next_prompt = true; end = y1; - } else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued) { + } else if (line && line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y1)) { start = y1; break; } @@ -4142,7 +4163,7 @@ cmd_output(Screen *self, PyObject *args) { bool reached_upper_limit = false; while (!found && !reached_upper_limit) { line = checked_range_line(self, y); - if (!line || (line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued)) { + if (!line || (line->attrs.prompt_kind == OUTPUT_START && !range_line_is_continued(self, y))) { int start = line ? y : y + 1; reached_upper_limit = !line; int y2 = start; unsigned int num_lines = 0; bool found_content = false; @@ -4584,7 +4605,7 @@ screen_selection_range_for_word(Screen *self, const index_type x, const index_ty start = x; end = x; while(true) { while(start > 0 && is_ok(start - 1, false)) start--; - if (start > 0 || !line->attrs.is_continued || *y1 == 0) break; + if (start > 0 || !visual_line_is_continued(self, y) || *y1 == 0) break; line = visual_line_(self, *y1 - 1); if (!is_ok(self->columns - 1, false)) break; (*y1)--; start = self->columns - 1; @@ -4594,7 +4615,7 @@ screen_selection_range_for_word(Screen *self, const index_type x, const index_ty while(end < self->columns - 1 && is_ok(end + 1, true)) end++; if (end < self->columns - 1 || *y2 >= self->lines - 1) break; line = visual_line_(self, *y2 + 1); - if (!line->attrs.is_continued || !is_ok(0, true)) break; + if (!visual_line_is_continued(self, *y2 + 1) || !is_ok(0, true)) break; (*y2)++; end = 0; } *s = start; *e = end; @@ -4771,7 +4792,7 @@ screen_mark_hyperlink(Screen *self, index_type x, index_type y) { static index_type continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) { - while (top_line > 0 && visual_line_(self, top_line)->attrs.is_continued) { + while (top_line > 0 && visual_line_is_continued(self, top_line)) { if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break; top_line--; } @@ -4780,7 +4801,7 @@ continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *star static index_type continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) { - while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->attrs.is_continued) { + while (bottom_line + 1 < self->lines && visual_line_is_continued(self, bottom_line + 1)) { if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break; bottom_line++; } @@ -5244,7 +5265,7 @@ dump_line_with_attrs(Screen *self, int y, PyObject *accum) { case SECONDARY_PROMPT: call_string("\x1b[32msecondary_prompt \x1b[39m"); break; case OUTPUT_START: call_string("\x1b[33moutput \x1b[39m"); break; } - if (line->attrs.is_continued) call_string("continued "); + if (range_line_is_continued(self, y)) call_string("continued "); if (line->attrs.has_dirty_text) call_string("dirty "); call_string("\n"); RAII_PyObject(t, line_as_unicode(line, false, &self->as_ansi_buf)); if (!t) return;