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.
This commit is contained in:
Kovid Goyal 2025-02-08 08:58:50 +05:30
parent f55704c50d
commit f412ec617c
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
5 changed files with 59 additions and 31 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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]);
}

View file

@ -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 ;

View file

@ -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;