From 32f0397f96091862b73599903dd11cd166ab9f57 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 14 Apr 2026 23:31:18 +0200 Subject: [PATCH 1/7] scrollback/selection: refactor scrollback and selection code scrollback: Use a standard double linked list instead of open coding it everywhere. selection: Use the same line pointer, if the line is in the screen or in the scrollback. This greatly simplify the code. Signed-off-by: Jocelyn Falempe --- src/shared/shl_dlist.h | 129 +++++++++++++++ src/tsm/libtsm-int.h | 36 ++-- src/tsm/tsm-render.c | 33 ++-- src/tsm/tsm-screen.c | 238 ++++++++++----------------- src/tsm/tsm-selection.c | 352 +++++++++++++++++----------------------- test/test_selection.c | 48 +----- 6 files changed, 401 insertions(+), 435 deletions(-) create mode 100644 src/shared/shl_dlist.h diff --git a/src/shared/shl_dlist.h b/src/shared/shl_dlist.h new file mode 100644 index 0000000..53b29f9 --- /dev/null +++ b/src/shared/shl_dlist.h @@ -0,0 +1,129 @@ +/* + * shl - Double Linked List + * + * Copyright (c) 2011-2012 David Herrmann + * Copyright (c) 2011 University of Tuebingen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * A simple double linked list implementation + */ + +#ifndef SHL_DLIST_H +#define SHL_DLIST_H + +#include +#include +#include +#include + +/* miscellaneous */ + +#define shl_offsetof(pointer, type, member) \ + ({ \ + const typeof(((type *)0)->member) *__ptr = (pointer); \ + (type *)(((char *)__ptr) - offsetof(type, member)); \ + }) + +/* double linked list */ + +struct shl_dlist { + struct shl_dlist *next; + struct shl_dlist *prev; +}; + +#define SHL_DLIST_INIT(head) {&(head), &(head)} + +static inline void shl_dlist_init(struct shl_dlist *list) +{ + list->next = list; + list->prev = list; +} + +static inline void shl_dlist__link(struct shl_dlist *prev, struct shl_dlist *next, + struct shl_dlist *n) +{ + next->prev = n; + n->next = next; + n->prev = prev; + prev->next = n; +} + +static inline void shl_dlist_link(struct shl_dlist *head, struct shl_dlist *n) +{ + return shl_dlist__link(head, head->next, n); +} + +static inline void shl_dlist_link_tail(struct shl_dlist *head, struct shl_dlist *n) +{ + return shl_dlist__link(head->prev, head, n); +} + +static inline void shl_dlist__unlink(struct shl_dlist *prev, struct shl_dlist *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void shl_dlist_unlink(struct shl_dlist *e) +{ + shl_dlist__unlink(e->prev, e->next); + e->prev = NULL; + e->next = NULL; +} + +static inline bool shl_dlist_empty(struct shl_dlist *head) +{ + return head->next == head; +} + +#define shl_dlist_entry(ptr, type, member) shl_offsetof((ptr), type, member) + +#define shl_dlist_first(head, type, member) shl_dlist_entry((head)->next, type, member) + +#define shl_dlist_last(head, type, member) shl_dlist_entry((head)->prev, type, member) + +#define shl_dlist_next(iter, head, member) \ + ((iter)->member.next == (head) ? NULL : shl_dlist_entry((iter)->member.next, typeof(*iter), list)) + +#define shl_dlist_for_each(iter, head) for (iter = (head)->next; iter != (head); iter = iter->next) + +#define shl_dlist_for_each_but_one(iter, start, head) \ + for (iter = ((start)->next == (head)) ? (start)->next->next : (start)->next; \ + iter != (start); \ + iter = (iter->next == (head) && (start) != (head)) ? iter->next->next : iter->next) + +#define shl_dlist_for_each_safe(iter, tmp, head) \ + for (iter = (head)->next, tmp = iter->next; iter != (head); iter = tmp, tmp = iter->next) + +#define shl_dlist_for_each_reverse(iter, head) \ + for (iter = (head)->prev; iter != (head); iter = iter->prev) + +#define shl_dlist_for_each_reverse_but_one(iter, start, head) \ + for (iter = ((start)->prev == (head)) ? (start)->prev->prev : (start)->prev; \ + iter != (start); \ + iter = (iter->prev == (head) && (start) != (head)) ? iter->prev->prev : iter->prev) + +#define shl_dlist_for_each_reverse_safe(iter, tmp, head) \ + for (iter = (head)->prev, tmp = iter->prev; iter != (head); iter = tmp, tmp = iter->prev) + +#endif /* SHL_DLIST_H */ diff --git a/src/tsm/libtsm-int.h b/src/tsm/libtsm-int.h index 668ba4a..f167158 100644 --- a/src/tsm/libtsm-int.h +++ b/src/tsm/libtsm-int.h @@ -32,6 +32,7 @@ #include #include #include "libtsm.h" +#include "shl_dlist.h" #include "shl-llog.h" #define SHL_EXPORT __attribute__((visibility("default"))) @@ -87,20 +88,26 @@ struct cell { }; struct line { - struct line *next; /* next line (NULL if not sb) */ - struct line *prev; /* prev line (NULL if not sb) */ - + struct shl_dlist list; /* list node, next/prev are NULL if not in sb */ unsigned int size; /* real width */ struct cell *cells; /* actuall cells */ - uint64_t sb_id; /* sb ID */ + uint64_t sb_id; /* sb ID, 0 if not in sb */ tsm_age_t age; /* age of the whole line */ }; -#define SELECTION_TOP -1 struct selection_pos { - struct line *line; - unsigned int x; - int y; + unsigned int x; /* x offset from the start of the line */ + struct line *line; /* line the selection is on */ +}; + +struct tsm_scrollback { + /* scroll-back buffer */ + unsigned int count; /* number of lines in sb */ + struct shl_dlist list; /* list of lines in sb */ + unsigned int max; /* max-limit of lines in sb */ + struct line *pos; /* current position in sb or NULL */ + unsigned int pos_num; /* current numeric position in sb */ + uint64_t last_id; /* last id given to sb-line */ }; struct tsm_screen { @@ -134,14 +141,7 @@ struct tsm_screen { struct line **alt_lines; /* real alternative lines */ tsm_age_t age; /* whole screen age */ - /* scroll-back buffer */ - unsigned int sb_count; /* number of lines in sb */ - struct line *sb_first; /* first line; was moved first */ - struct line *sb_last; /* last line; was moved last*/ - unsigned int sb_max; /* max-limit of lines in sb */ - struct line *sb_pos; /* current position in sb or NULL */ - unsigned int sb_pos_num; /* current numeric position in sb */ - uint64_t sb_last_id; /* last id given to sb-line */ + struct tsm_scrollback sb; /* cursor: positions are always in-bound, but cursor_x might be * bigger than size_x if new-line is pending */ @@ -172,6 +172,10 @@ static inline void screen_inc_age(struct tsm_screen *con) } } +static inline bool is_in_scrollback(struct selection_pos *sel) { + return (sel->line && sel->line->sb_id); +} + /* available character sets */ typedef tsm_symbol_t tsm_vte_charset[96]; diff --git a/src/tsm/tsm-render.c b/src/tsm/tsm-render.c index 88ad847..0429e7d 100644 --- a/src/tsm/tsm-render.c +++ b/src/tsm/tsm-render.c @@ -38,6 +38,7 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-render" @@ -47,7 +48,7 @@ tsm_age_t tsm_screen_draw(struct tsm_screen *con, tsm_screen_draw_cb draw_cb, { unsigned int cur_x, cur_y; unsigned int i, j, k; - struct line *iter, *line = NULL; + struct line *line, *next_line = NULL; struct cell *cell, empty; struct tsm_screen_attr attr; int ret, warned = 0; @@ -71,47 +72,41 @@ tsm_age_t tsm_screen_draw(struct tsm_screen *con, tsm_screen_draw_cb draw_cb, cur_y = con->size_y - 1; /* push each character into rendering pipeline */ - - iter = con->sb_pos; k = 0; + next_line = con->sb.pos; if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) + if (!con->sel_start.line) in_sel = !in_sel; - if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) + if (!con->sel_end.line) in_sel = !in_sel; - if (con->sel_start.line && - (!iter || con->sel_start.line->sb_id < iter->sb_id)) + if (is_in_scrollback(&con->sel_start) && + (!con->sb.pos || con->sel_start.line->sb_id < con->sb.pos->sb_id)) in_sel = !in_sel; - if (con->sel_end.line && - (!iter || con->sel_end.line->sb_id < iter->sb_id)) + if (is_in_scrollback(&con->sel_end) && + (!con->sb.pos || con->sel_end.line->sb_id < con->sb.pos->sb_id)) in_sel = !in_sel; } for (i = 0; i < con->size_y; ++i) { - if (iter) { - line = iter; - iter = iter->next; + if (next_line) { + line = next_line; + next_line = shl_dlist_next(next_line, &con->sb.list, list); } else { line = con->lines[k]; k++; } if (con->sel_active) { - if (con->sel_start.line == line || - (!con->sel_start.line && - con->sel_start.y == k - 1)) + if (con->sel_start.line == line) sel_start = true; else sel_start = false; - if (con->sel_end.line == line || - (!con->sel_end.line && - con->sel_end.y == k - 1)) + if (con->sel_end.line == line) sel_end = true; else sel_end = false; - was_sel = false; } diff --git a/src/tsm/tsm-screen.c b/src/tsm/tsm-screen.c index 3c8dd7f..d121deb 100644 --- a/src/tsm/tsm-screen.c +++ b/src/tsm/tsm-screen.c @@ -64,6 +64,7 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-screen" @@ -110,7 +111,7 @@ static void move_cursor(struct tsm_screen *con, unsigned int x, unsigned int y) c->age = con->age_cnt; } -void screen_cell_init_generic(struct tsm_screen *con, struct cell *cell, struct tsm_screen_attr *attr) +static void screen_cell_init_generic(struct tsm_screen *con, struct cell *cell, struct tsm_screen_attr *attr) { cell->ch = 0; cell->width = 1; @@ -136,8 +137,9 @@ static int line_new(struct tsm_screen *con, struct line **out, line = malloc(sizeof(*line)); if (!line) return -ENOMEM; - line->next = NULL; - line->prev = NULL; + line->list.next = NULL; + line->list.prev = NULL; + line->sb_id = 0; line->size = width; line->age = con->age_cnt; @@ -192,15 +194,13 @@ static void link_to_scrollback(struct tsm_screen *con, struct line *line) /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - if (con->sb_max == 0) { + if (con->sb.max == 0) { if (con->sel_active) { if (con->sel_start.line == line) { con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; } if (con->sel_end.line == line) { con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; } } line_free(line); @@ -209,64 +209,34 @@ static void link_to_scrollback(struct tsm_screen *con, struct line *line) /* Remove a line from the scrollback buffer if it reaches its maximum. * We must take care to correctly keep the current position as the new - * line is linked in after we remove the top-most line here. - * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In - * other words, buf->sb_first is a valid line if sb_count >= sb_max. */ - if (con->sb_count >= con->sb_max) { - tmp = con->sb_first; - con->sb_first = tmp->next; - if (tmp->next) - tmp->next->prev = NULL; - else - con->sb_last = NULL; - --con->sb_count; - - /* (position == tmp && !next) means we have sb_max=1 so set - * position to the new line. Otherwise, set to new first line. - * If position!=tmp and we have a fixed-position then nothing - * needs to be done because we can stay at the same line. If we - * have no fixed-position, we need to set the position to the - * next inserted line, which can be "line", too. */ - if (con->sb_pos) { - if (con->sb_pos == tmp || - !(con->flags & TSM_SCREEN_FIXED_POS)) { - if (con->sb_pos->next) { - con->sb_pos = con->sb_pos->next; - ++con->sb_pos_num; - } else { - con->sb_pos = line; - con->sb_pos_num = 0; - } - } + * line is linked in after we remove the top-most line here. */ + if (con->sb.count >= con->sb.max) { + tmp = shl_dlist_first(&con->sb.list, struct line, list); + shl_dlist_unlink(&tmp->list); + --con->sb.count; + + /* Only consider sb.max > 1, so there is always another line in sb. */ + if (con->sb.pos == tmp) { + con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); + ++con->sb.pos_num; } if (con->sel_active) { if (con->sel_start.line == tmp) { con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; } if (con->sel_end.line == tmp) { con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; } } line_free(tmp); } - line->sb_id = ++con->sb_last_id; - line->next = NULL; - line->prev = con->sb_last; - if (con->sb_last) { - con->sb_last->next = line; - } else { - con->sb_first = line; - } - con->sb_last = line; - ++con->sb_count; - - if (con->sb_pos == NULL) { - con->sb_pos_num = con->sb_count; - } + line->sb_id = ++con->sb.last_id; + shl_dlist_link_tail(&con->sb.list, &line->list); + ++con->sb.count; + if (con->sb.pos == NULL) + con->sb.pos_num = con->sb.count; } /* Remove num lines from scroll back to current buffer */ @@ -277,33 +247,27 @@ static void remove_from_sb(struct tsm_screen *con, unsigned int num) /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - if (!con->sb_max || !con->sb_count || !con->sb_last) + if (!con->sb.max || !con->sb.count || shl_dlist_empty(&con->sb.list)) return; - if (num > con->sb_count) - num = con->sb_count; + if (num > con->sb.count) + num = con->sb.count; while (num--) { - tmp = con->sb_last; - con->sb_last = tmp->prev; + tmp = shl_dlist_last(&con->sb.list, struct line, list); + shl_dlist_unlink(&tmp->list); + --con->sb.count; - if (tmp->prev) - tmp->prev->next = NULL; - else - con->sb_first = NULL; - --con->sb_count; - - tmp->next = NULL; - tmp->prev = NULL; - tmp->sb_id = 0; - - if (con->sb_pos == tmp) { - con->sb_pos_num = 0; - con->sb_pos = NULL; + if (con->sb.pos == tmp) { + con->sb.pos_num = con->sb.count; + con->sb.pos = NULL; } + tmp->sb_id = 0; memcpy(con->lines[num], tmp, sizeof(*tmp)); free(tmp); } + if (!con->sb.pos) + con->sb.pos_num = con->sb.count; } static void screen_scroll_up(struct tsm_screen *con, unsigned int num) @@ -356,27 +320,6 @@ static void screen_scroll_up(struct tsm_screen *con, unsigned int num) memcpy(&con->lines[con->margin_top + (max - num)], cache, num * sizeof(struct line*)); - - if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y >= 0) { - con->sel_start.y -= num; - if (con->sel_start.y < 0) { - con->sel_start.line = con->sb_last; - while (con->sel_start.line && ++con->sel_start.y < 0) - con->sel_start.line = con->sel_start.line->prev; - con->sel_start.y = SELECTION_TOP; - } - } - if (!con->sel_end.line && con->sel_end.y >= 0) { - con->sel_end.y -= num; - if (con->sel_end.y < 0) { - con->sel_end.line = con->sb_last; - while (con->sel_end.line && ++con->sel_end.y < 0) - con->sel_end.line = con->sel_end.line->prev; - con->sel_end.y = SELECTION_TOP; - } - } - } } static void screen_scroll_down(struct tsm_screen *con, unsigned int num) @@ -414,13 +357,6 @@ static void screen_scroll_down(struct tsm_screen *con, unsigned int num) memcpy(&con->lines[con->margin_top], cache, num * sizeof(struct line*)); - - if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y >= 0) - con->sel_start.y += num; - if (!con->sel_end.line && con->sel_end.y >= 0) - con->sel_end.y += num; - } } static void screen_write(struct tsm_screen *con, unsigned int x, @@ -533,6 +469,7 @@ int tsm_screen_new(struct tsm_screen **out, tsm_log_t log, void *log_data) con->def_attr.fr = 255; con->def_attr.fg = 255; con->def_attr.fb = 255; + shl_dlist_init(&con->sb.list); ret = tsm_symbol_table_new(&con->sym_table); if (ret) @@ -578,17 +515,16 @@ void tsm_screen_unref(struct tsm_screen *con) return; llog_debug(con, "destroying screen"); + tsm_screen_clear_sb(con); for (i = 0; i < con->line_num; ++i) { line_free(con->main_lines[i]); line_free(con->alt_lines[i]); } - free(con->main_lines); free(con->alt_lines); free(con->tab_ruler); tsm_symbol_table_unref(con->sym_table); - tsm_screen_clear_sb(con); free(con); } @@ -767,7 +703,7 @@ int tsm_screen_resize(struct tsm_screen *con, unsigned int x, /* scroll buffer if screen height shrinks */ if (y < con->size_y) { diff = con->size_y - y; - if (!con->sb_last || (con->flags & TSM_SCREEN_ALTERNATE)) { + if (shl_dlist_empty(&con->sb.list) || (con->flags & TSM_SCREEN_ALTERNATE)) { /* If there is nothing in the scrollback buffer, * Only scroll up if the cursor would go off-screen */ if (con->cursor_y >= y) { @@ -784,8 +720,8 @@ int tsm_screen_resize(struct tsm_screen *con, unsigned int x, } } else if (y > con->size_y) { diff = y - con->size_y; - if (diff > con->sb_count) - diff = con->sb_count; + if (diff > con->sb.count) + diff = con->sb.count; /* * When increasing the terminal number of rows, we can move some * lines from the scrollback buffer to the main buffer. @@ -835,7 +771,7 @@ int tsm_screen_set_margins(struct tsm_screen *con, return 0; } -/* set maximum scrollback buffer size */ +/* set maximum scrollback buffer size in number of lines*/ SHL_EXPORT void tsm_screen_set_max_sb(struct tsm_screen *con, unsigned int max) @@ -845,45 +781,44 @@ void tsm_screen_set_max_sb(struct tsm_screen *con, if (!con) return; + // Don't allow only one line in the scrollback buffer, this simplifies + // the code, and is not a useful usecase. + if (max == 1) + max = 2; + screen_inc_age(con); /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - while (con->sb_count > max) { - line = con->sb_first; - con->sb_first = line->next; - if (line->next) - line->next->prev = NULL; - else - con->sb_last = NULL; - con->sb_count--; + while (con->sb.count > max) { + line = shl_dlist_first(&con->sb.list, struct line, list); + shl_dlist_unlink(&line->list); + --con->sb.count; /* We treat fixed/unfixed position the same here because we * remove lines from the TOP of the scrollback buffer. */ - if (con->sb_pos == line) - con->sb_pos = con->sb_first; + if (con->sb.pos == line) + con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); if (con->sel_active) { if (con->sel_start.line == line) { con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; } if (con->sel_end.line == line) { con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; } } line_free(line); } - - con->sb_max = max; + con->sb.max = max; } /* clear scrollback buffer */ SHL_EXPORT void tsm_screen_clear_sb(struct tsm_screen *con) { - struct line *iter, *tmp; + struct shl_dlist *iter, *safe; + struct line *tmp; if (!con) return; @@ -892,28 +827,20 @@ void tsm_screen_clear_sb(struct tsm_screen *con) /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - for (iter = con->sb_first; iter; ) { - tmp = iter; - iter = iter->next; - line_free(tmp); - } - - con->sb_first = NULL; - con->sb_last = NULL; - con->sb_count = 0; - con->sb_pos = NULL; - con->sb_pos_num = 0; - if (con->sel_active) { - if (con->sel_start.line) { + if (con->sel_start.line && is_in_scrollback(&con->sel_start)) con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; - } - if (con->sel_end.line) { + if (con->sel_end.line && is_in_scrollback(&con->sel_end)) con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; - } } + shl_dlist_for_each_safe(iter, safe, &con->sb.list) { + tmp = shl_dlist_entry(iter, struct line, list); + shl_dlist_unlink(&tmp->list); + line_free(tmp); + } + con->sb.count = 0; + con->sb.pos = NULL; + con->sb.pos_num = 0; } SHL_EXPORT @@ -926,18 +853,19 @@ void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num) /* TODO: more sophisticated ageing */ con->age = con->age_cnt; + if (shl_dlist_empty(&con->sb.list)) + return; + while (num--) { - if (con->sb_pos) { - if (!con->sb_pos->prev) + if (con->sb.pos) { + if (con->sb.pos_num == 0) return; - con->sb_pos = con->sb_pos->prev; - --con->sb_pos_num; - } else if (!con->sb_last) { - return; + con->sb.pos = shl_dlist_last(&con->sb.pos->list, struct line, list); + --con->sb.pos_num; } else { - con->sb_pos = con->sb_last; - con->sb_pos_num = con->sb_count - 1; + con->sb.pos = shl_dlist_last(&con->sb.list, struct line, list); + con->sb.pos_num = con->sb.count - 1; } } } @@ -952,14 +880,12 @@ void tsm_screen_sb_down(struct tsm_screen *con, unsigned int num) /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - while (num--) { - if (con->sb_pos) { - con->sb_pos = con->sb_pos->next; - ++con->sb_pos_num; - } - else - return; + while (num-- && con->sb.pos && con->sb.pos_num < con->sb.count) { + con->sb.pos = shl_dlist_next(con->sb.pos, &con->sb.list, list); + ++con->sb.pos_num; } + if (con->sb.pos_num == con->sb.count) + con->sb.pos = NULL; } SHL_EXPORT @@ -985,15 +911,15 @@ void tsm_screen_sb_page_down(struct tsm_screen *con, unsigned int num) SHL_EXPORT void tsm_screen_sb_reset(struct tsm_screen *con) { - if (!con || !con->sb_pos) + if (!con || !con->sb.pos) return; screen_inc_age(con); /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - con->sb_pos = NULL; - con->sb_pos_num = con->sb_count; + con->sb.pos = NULL; + con->sb.pos_num = con->sb.count; } unsigned int tsm_screen_sb_get_line_count(struct tsm_screen *con) @@ -1002,7 +928,7 @@ unsigned int tsm_screen_sb_get_line_count(struct tsm_screen *con) return 0; } - return con->sb_count; + return con->sb.count; } unsigned int tsm_screen_sb_get_line_pos(struct tsm_screen *con) @@ -1011,7 +937,7 @@ unsigned int tsm_screen_sb_get_line_pos(struct tsm_screen *con) return 0; } - return con->sb_pos_num; + return con->sb.pos_num; } SHL_EXPORT diff --git a/src/tsm/tsm-selection.c b/src/tsm/tsm-selection.c index 26047c7..d8aca60 100644 --- a/src/tsm/tsm-selection.c +++ b/src/tsm/tsm-selection.c @@ -58,27 +58,30 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-selection" static void selection_set(struct tsm_screen *con, struct selection_pos *sel, unsigned int x, unsigned int y) { - struct line *pos; + struct line *line; - sel->line = NULL; - pos = con->sb_pos; + sel->x = x; - while (y && pos) { - --y; - pos = pos->next; + if (!con->sb.pos) { + sel->line = con->lines[y]; + return; } - - if (pos) - sel->line = pos; - - sel->x = x; - sel->y = y; + if (con->sb.pos_num + y >= con->sb.count) { + y -= con->sb.count - con->sb.pos_num; + sel->line = con->lines[y]; + return; + } + line = con->sb.pos; + while (y--) + line = shl_dlist_next(line, &con->sb.list, list); + sel->line = line; } static void word_select(struct tsm_screen *con, @@ -90,10 +93,7 @@ static void word_select(struct tsm_screen *con, selection_set(con, &con->sel_start, posx, posy); - if (con->sel_start.line) - line = con->sel_start.line; - else - line = con->lines[con->sel_start.y]; + line = con->sel_start.line; if (!line || line->cells[posx].ch == ' ') return; @@ -115,7 +115,8 @@ static void word_select(struct tsm_screen *con, } } con->sel_start.x = start; - selection_set(con, &con->sel_end, end, posy); + con->sel_end.x = end; + con->sel_end.line = line; con->sel_active = true; } @@ -130,53 +131,8 @@ void tsm_screen_selection_reset(struct tsm_screen *con) con->age = con->age_cnt; con->sel_active = false; -} - -SHL_EXPORT -void tsm_screen_selection_start(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - con->sel_active = true; - selection_set(con, &con->sel_start, posx, posy); - memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end)); -} - -SHL_EXPORT -void tsm_screen_selection_target(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con || !con->sel_active) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - selection_set(con, &con->sel_end, posx, posy); -} - -SHL_EXPORT -void tsm_screen_selection_word(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - word_select(con, posx, posy); + con->sel_start.line = NULL; + con->sel_end.line = NULL; } /* calculates the line length from the beginning to the last non zero character */ @@ -227,13 +183,13 @@ static unsigned int copy_line(struct line *line, char *buf, return pos - buf; } -static void swap_selections(struct selection_pos **a, struct selection_pos **b) +static void swap_selections(struct tsm_screen *con) { - struct selection_pos *c; + struct selection_pos c; - c = *a; - *a = *b; - *b = c; + c = con->sel_start; + con->sel_start = con->sel_end; + con->sel_end = c; } /* @@ -241,64 +197,90 @@ static void swap_selections(struct selection_pos **a, struct selection_pos **b) * * Start must always point to the top left and end to the bottom right cell */ -static void norm_selection(struct tsm_screen *con, struct selection_pos **start, struct selection_pos **end) +static void norm_selection(struct tsm_screen *con) { - struct line *iter; + int i; + struct selection_pos *start, *end; - if ((*end)->line == NULL && (*end)->y == SELECTION_TOP) { - swap_selections(start, end); + start = &con->sel_start; + end = &con->sel_end; + if (start->line == end->line) { + if (con->sel_start.x > con->sel_end.x) + swap_selections(con); return; } - if ((*start)->line && (*end)->line) { - /* single line selection */ - if ((*start)->line == (*end)->line) { - if ((*start)->x > (*end)->x) { - swap_selections(start, end); - } + if (is_in_scrollback(&con->sel_start) != is_in_scrollback(&con->sel_end)) { + if (is_in_scrollback(&con->sel_end)) + swap_selections(con); + return; + } - return; - } + if (is_in_scrollback(&con->sel_start) && is_in_scrollback(&con->sel_end)) { + if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) + swap_selections(con); + return; + } - /* - * multi line selection - * - * search from end->line to con->sb_last - * if we find start->line on the way we - * need to change start and end - */ - iter = (*end)->line; - while (iter && iter != con->sb_last) { - if (iter == (*start)->line) { - swap_selections(start, end); - } - - iter = iter->next; + /* so both are not in scroll back buffer and can't be equal */ + for (i = 0; i < con->size_y; i++) { + if (con->lines[i] == con->sel_end.line) { + swap_selections(con); + return; } + if (con->lines[i] == con->sel_start.line) + return; + } +} +SHL_EXPORT +void tsm_screen_selection_start(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) +{ + if (!con) return; - } - /* end is in scroll back buffer and start on screen */ - if (!(*start)->line && (*end)->line) { - swap_selections(start, end); + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; + + con->sel_active = true; + selection_set(con, &con->sel_start, posx, posy); + memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end)); +} + +SHL_EXPORT +void tsm_screen_selection_target(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) +{ + if (!con || !con->sel_active) return; - } - /* reorder one-line selection if selection was created right to left */ - if ((*start)->y == (*end)->y) { - if ((*start)->x > (*end)->x) { - swap_selections(start, end); - } + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; + + selection_set(con, &con->sel_end, posx, posy); + /* always normalize the selection */ + norm_selection(con); +} +SHL_EXPORT +void tsm_screen_selection_word(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) +{ + if (!con) return; - } - /* reorder multi-line selection if selection was created bottom to top */ - if ((*start)->y > (*end)->y) { - swap_selections(start, end); - } + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; + + word_select(con, posx, posy); } /* @@ -308,25 +290,17 @@ static void norm_selection(struct tsm_screen *con, struct selection_pos **start, */ static int selection_count_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) { + int count = 1; struct line *iter; - int count = 0; - /* Single line selection */ - if (start->line && (start->line == end->line)) { - return 1; - } + if (!is_in_scrollback(start)) + return 0; iter = start->line; - while (iter) { + while (iter && iter != end->line) { count++; - - if (iter == con->sb_last) { - break; - } - - iter = iter->next; + iter = shl_dlist_next(iter, &con->sb.list, list); } - return count; } @@ -335,20 +309,34 @@ static int selection_count_lines_sb(struct tsm_screen *con, struct selection_pos * * Does not count the lines selected in the scroll back buffer */ -static int selection_count_lines(struct selection_pos *start, struct selection_pos *end) +static int selection_count_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) { + bool in_sel; + int i, count = 0; + /* Selection only spans lines of the scroll back buffer */ - if (start->line && end->line) { + if (is_in_scrollback(end)) return 0; - } + if (is_in_scrollback(start)) + in_sel = true; + + for (i = 0; i < con->size_y; i++) { + if (start->line == con->lines[i]) + in_sel = true; - return end->y - start->y + 1; + if (in_sel) + count++; + if (end->line == con->lines[i]) + return count; + } + llog_error(con, "selection_count_lines: end->line not found"); + return count; } /* * Calculate the number of selected cells in a line */ -static int calc_selection_line_len_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, struct line *line) +static int calc_selection_line_len(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, struct line *line) { /* one-line selection */ if (start->line == end->line) { @@ -369,32 +357,6 @@ static int calc_selection_line_len_sb(struct tsm_screen *con, struct selection_p return con->size_x; } -/* - * Calculate the number of selected cells in a line - */ -static int calc_selection_line_len(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, int line_num) -{ - if (!start->line) { - /* one-line selection */ - if (start->y == end->y) { - return end->x - start->x + 1; - } - - /* first line of a multi-line selection */ - if (line_num == start->y) { - return con->size_x - start->x; - } - } - - /* last line of a multi-line selection */ - if (line_num == end->y) { - return end->x + 1; - } - - /* every other selection */ - return con->size_x; -} - /* * Calculate the maximum needed space for the number of lines given */ @@ -409,30 +371,22 @@ static unsigned int calc_line_copy_buffer(struct tsm_screen *con, unsigned int n */ static int copy_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos) { - struct line *iter; + struct line *iter; int line_x, line_len; - if (!start->line) { + if (!is_in_scrollback(start)) return pos; - } iter = start->line; + line_x = start->x; while (iter) { - line_x = 0; - if (iter == start->line) { - line_x = start->x; - } - - line_len = calc_selection_line_len_sb(con, start, end, iter); + line_len = calc_selection_line_len(con, start, end, iter); pos += copy_line(iter, &(buf[pos]), line_x, line_len); - - if (iter == con->sb_last || iter == end->line) { + line_x = 0; + if (iter == end->line) break; - } - - iter = iter->next; + iter = shl_dlist_next(iter, &con->sb.list, list); } - return pos; } @@ -441,32 +395,36 @@ static int copy_lines_sb(struct tsm_screen *con, struct selection_pos *start, st */ static int copy_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos) { - int line_len, line_x, i; + int line_len, i; + int line_x = 0; + bool in_sel; - /* selection is scroll back buffer only */ - if (end->line) { + if (is_in_scrollback(end)) return pos; - } - for (i = start->y; i <= end->y; i++) { - line_len = calc_selection_line_len(con, start, end, i); + in_sel = is_in_scrollback(start); - line_x = 0; - if (!start->line && i == start->y) { + for (i = 0; i < con->size_y; i++) { + if (start->line == con->lines[i]) { + in_sel = true; line_x = start->x; } - - pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len); + if (in_sel) { + line_len = calc_selection_line_len(con, start, end, con->lines[i]); + pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len); + line_x = 0; + } + if (end->line == con->lines[i]) + break; } - return pos; } SHL_EXPORT int tsm_screen_selection_copy(struct tsm_screen *con, char **out) { - struct selection_pos *start, *end; - struct selection_pos start_copy, end_copy; + struct selection_pos *start = &con->sel_start; + struct selection_pos *end = &con->sel_end; int buf_size = 0; int pos = 0; int total_lines; @@ -479,36 +437,22 @@ int tsm_screen_selection_copy(struct tsm_screen *con, char **out) return -ENOENT; } - /* - * copy the selection start and end so we can modify it without affecting - * the screen in any way - */ - memcpy(&start_copy, &con->sel_start, sizeof(con->sel_start)); - memcpy(&end_copy, &con->sel_end, sizeof(con->sel_end)); - start = &start_copy; - end = &end_copy; - /* invalid selection */ - if (start->y == SELECTION_TOP && start->line == NULL && - end->y == SELECTION_TOP && end->line == NULL) { + if (start->line == NULL && end->line == NULL) { *out = strdup(""); return 0; } - norm_selection(con, &start, &end); - - if (start->line == NULL && start->y == SELECTION_TOP) { - if (con->sb_first != NULL) { - start->line = con->sb_first; - start->x = 0; - } else { - start->y = 0; - start->x = 0; - } + if (start->line == NULL) { + if (!shl_dlist_empty(&con->sb.list)) + start->line = shl_dlist_first(&con->sb.list, struct line, list); + else + start->line = con->lines[0]; + start->x = 0; } total_lines = selection_count_lines_sb(con, start, end); - total_lines += selection_count_lines(start, end); + total_lines += selection_count_lines(con,start, end); buf_size = calc_line_copy_buffer(con, total_lines); *out = calloc(buf_size, 1); diff --git a/test/test_selection.c b/test/test_selection.c index 1ee6ab0..ca0f32b 100644 --- a/test/test_selection.c +++ b/test/test_selection.c @@ -203,9 +203,7 @@ START_TEST(test_screen_copy_line_scrolled) tsm_screen_selection_target(screen, 14, 39); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 39); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 39); /* force the selected text to scroll up */ tsm_screen_newline(screen); @@ -217,9 +215,7 @@ START_TEST(test_screen_copy_line_scrolled) tsm_screen_newline(screen); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 32); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 32); r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str); @@ -321,9 +317,7 @@ START_TEST(test_screen_copy_lines_scrolled) tsm_screen_selection_target(screen, 5, 39); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 37); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 39); /* force the selected text to scroll up */ tsm_screen_newline(screen); @@ -335,9 +329,7 @@ START_TEST(test_screen_copy_lines_scrolled) tsm_screen_newline(screen); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 30); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 32); r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str); @@ -462,9 +454,7 @@ START_TEST(test_screen_copy_line_sb_scrolled) tsm_screen_selection_target(screen, 14, 0); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 0); /* force the selected text to scroll up */ for (int i = 0; i < 40; i++) { @@ -472,10 +462,8 @@ START_TEST(test_screen_copy_line_sb_scrolled) } ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, -1); ck_assert_ptr_ne(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, -1); ck_assert_ptr_ne(screen->sel_end.line, NULL); r = tsm_screen_selection_copy(screen, &str); @@ -496,10 +484,8 @@ START_TEST(test_screen_copy_line_sb_scrolled) tsm_screen_selection_target(screen, 14, 0); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_ptr_ne(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 0); ck_assert_ptr_ne(screen->sel_end.line, NULL); tsm_screen_newline(screen); @@ -507,10 +493,8 @@ START_TEST(test_screen_copy_line_sb_scrolled) tsm_screen_newline(screen); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_ptr_ne(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 0); ck_assert_ptr_ne(screen->sel_end.line, NULL); r = tsm_screen_selection_copy(screen, &str); @@ -549,26 +533,16 @@ START_TEST(test_screen_copy_line_sb_scrolled_invalid) tsm_screen_selection_target(screen, 14, 0); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, 0); /* force the selected text to scroll up */ for (i = 0; i < 40; i++) { tsm_screen_newline(screen); } - /* - * sel_start.y == -1, sel_start.line == NULL - * sel_end.y == -1, sel_end.line == NULL - * - * => Invalid selection - */ ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, -1); ck_assert_ptr_eq(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 14); - ck_assert_int_eq(screen->sel_end.y, -1); ck_assert_ptr_eq(screen->sel_end.line, NULL); r = tsm_screen_selection_copy(screen, &str); @@ -645,6 +619,9 @@ START_TEST(test_screen_copy_lines_sb) tsm_screen_selection_start(screen, 41, 4); tsm_screen_selection_target(screen, 3, 1); + ck_assert_int_eq(screen->sel_start.x, 3); + ck_assert_int_eq(screen->sel_end.x, 41); + r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str); ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str); @@ -667,6 +644,9 @@ START_TEST(test_screen_copy_lines_sb) tsm_screen_selection_start(screen, 18, 6); tsm_screen_selection_target(screen, 0, 4); + ck_assert_int_eq(screen->sel_start.x, 0); + ck_assert_int_eq(screen->sel_end.x, 18); + r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str); ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in SB\nMore Text not in SB", str); @@ -719,9 +699,7 @@ START_TEST(test_screen_copy_lines_sb_scrolled) tsm_screen_selection_target(screen, 5, 2); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 2); /* force the selected text to scroll into the sb */ for (i = 0; i < 40; i++) { @@ -729,10 +707,8 @@ START_TEST(test_screen_copy_lines_sb_scrolled) } ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, -1); ck_assert_ptr_ne(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, -1); ck_assert_ptr_ne(screen->sel_end.line, NULL); ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line); @@ -754,19 +730,15 @@ START_TEST(test_screen_copy_lines_sb_scrolled) tsm_screen_selection_target(screen, 5, 2); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 0); tsm_screen_newline(screen); tsm_screen_newline(screen); tsm_screen_newline(screen); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_ptr_ne(screen->sel_start.line, NULL); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 0); ck_assert_ptr_ne(screen->sel_end.line, NULL); ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line); @@ -810,9 +782,7 @@ START_TEST(test_screen_copy_lines_sb_scrolled_cut_off) tsm_screen_selection_target(screen, 5, 2); ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, 0); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 2); /* force the selected text to scroll up */ for (i = 0; i < 39; i++) { @@ -820,11 +790,9 @@ START_TEST(test_screen_copy_lines_sb_scrolled_cut_off) } ck_assert_int_eq(screen->sel_start.x, 3); - ck_assert_int_eq(screen->sel_start.y, -1); - ck_assert_ptr_eq(screen->sel_start.line, NULL); + ck_assert(!is_in_scrollback(&screen->sel_start)); ck_assert_int_eq(screen->sel_end.x, 5); - ck_assert_int_eq(screen->sel_end.y, 0); - ck_assert_ptr_eq(screen->sel_end.line, NULL); + ck_assert(!is_in_scrollback(&screen->sel_end)); r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str); From 1abc3c67a4becae76972c0abcb67eec87be2a0b4 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 09:40:37 +0200 Subject: [PATCH 2/7] selection: Check posx and posy Make sure posx and posy are on the screen, before setting the selection. Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-selection.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tsm/tsm-selection.c b/src/tsm/tsm-selection.c index d8aca60..f3a719f 100644 --- a/src/tsm/tsm-selection.c +++ b/src/tsm/tsm-selection.c @@ -239,7 +239,7 @@ void tsm_screen_selection_start(struct tsm_screen *con, unsigned int posx, unsigned int posy) { - if (!con) + if (!con || posx >= con->size_x || posy >= con->size_y) return; screen_inc_age(con); @@ -256,7 +256,7 @@ void tsm_screen_selection_target(struct tsm_screen *con, unsigned int posx, unsigned int posy) { - if (!con || !con->sel_active) + if (!con || !con->sel_active || posx >= con->size_x || posy >= con->size_y) return; screen_inc_age(con); @@ -273,7 +273,7 @@ void tsm_screen_selection_word(struct tsm_screen *con, unsigned int posx, unsigned int posy) { - if (!con) + if (!con || posx >= con->size_x || posy >= con->size_y) return; screen_inc_age(con); From ad10f5f7a9d1f839dc76515a9fe3bb715d151dbb Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 10:14:49 +0200 Subject: [PATCH 3/7] screen: Add clear_selection_on_line() This remove some duplicated code Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-screen.c | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/tsm/tsm-screen.c b/src/tsm/tsm-screen.c index d121deb..83656da 100644 --- a/src/tsm/tsm-screen.c +++ b/src/tsm/tsm-screen.c @@ -186,6 +186,16 @@ static int line_resize(struct tsm_screen *con, struct line *line, return 0; } +static void clear_selection_on_line(struct tsm_screen *con, struct line *line) +{ + if (!con->sel_active) + return; + if (con->sel_start.line == line) + con->sel_start.line = NULL; + if (con->sel_end.line == line) + con->sel_end.line = NULL; +} + /* This links the given line into the scrollback-buffer */ static void link_to_scrollback(struct tsm_screen *con, struct line *line) { @@ -195,14 +205,7 @@ static void link_to_scrollback(struct tsm_screen *con, struct line *line) con->age = con->age_cnt; if (con->sb.max == 0) { - if (con->sel_active) { - if (con->sel_start.line == line) { - con->sel_start.line = NULL; - } - if (con->sel_end.line == line) { - con->sel_end.line = NULL; - } - } + clear_selection_on_line(con, line); line_free(line); return; } @@ -220,15 +223,7 @@ static void link_to_scrollback(struct tsm_screen *con, struct line *line) con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); ++con->sb.pos_num; } - - if (con->sel_active) { - if (con->sel_start.line == tmp) { - con->sel_start.line = NULL; - } - if (con->sel_end.line == tmp) { - con->sel_end.line = NULL; - } - } + clear_selection_on_line(con, tmp); line_free(tmp); } @@ -800,14 +795,7 @@ void tsm_screen_set_max_sb(struct tsm_screen *con, if (con->sb.pos == line) con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); - if (con->sel_active) { - if (con->sel_start.line == line) { - con->sel_start.line = NULL; - } - if (con->sel_end.line == line) { - con->sel_end.line = NULL; - } - } + clear_selection_on_line(con, line); line_free(line); } con->sb.max = max; From 3067b7f8bba541d40fefb7a6a5149e44ccfec207 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 10:59:00 +0200 Subject: [PATCH 4/7] selection: unify line count and copy Add a generic function to get the next selection line, and use that to count and copy the lines, regardless if they are in the scrollback buffer or not. Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-selection.c | 121 +++++++++++++--------------------------- 1 file changed, 40 insertions(+), 81 deletions(-) diff --git a/src/tsm/tsm-selection.c b/src/tsm/tsm-selection.c index f3a719f..5c96d3b 100644 --- a/src/tsm/tsm-selection.c +++ b/src/tsm/tsm-selection.c @@ -55,6 +55,7 @@ #include #include #include +#include #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" @@ -283,53 +284,48 @@ void tsm_screen_selection_word(struct tsm_screen *con, word_select(con, posx, posy); } -/* - * Counts the lines a normalized selection selects on the scroll back buffer - * - * Does not count the lines selected on the screen - */ -static int selection_count_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) +static unsigned int get_line_index(struct tsm_screen *con, struct line *line) { - int count = 1; - struct line *iter; - - if (!is_in_scrollback(start)) + unsigned int i = 0; + + if (line->sb_id) return 0; - iter = start->line; - while (iter && iter != end->line) { - count++; - iter = shl_dlist_next(iter, &con->sb.list, list); + for (i = 0; i < con->size_y; i++) { + if (con->lines[i] == line) + return i; } - return count; + return 0; } -/* - * Counts the lines a normalized selection selects on the screen - * - * Does not count the lines selected in the scroll back buffer - */ -static int selection_count_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) +static struct line *get_next_line(struct tsm_screen *con, struct line *line, unsigned int *index) { - bool in_sel; - int i, count = 0; - - /* Selection only spans lines of the scroll back buffer */ - if (is_in_scrollback(end)) - return 0; - if (is_in_scrollback(start)) - in_sel = true; + struct line *next; + + if (line->sb_id) { + next = shl_dlist_next(line, &con->sb.list, list); + if (next) + return next; + *index = 0; + return con->lines[0]; + } else if (*index < con->size_y) { + (*index)++; + return con->lines[*index]; + } + return NULL; +} - for (i = 0; i < con->size_y; i++) { - if (start->line == con->lines[i]) - in_sel = true; +static int selection_count_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) +{ + int count = 1; + unsigned int index = get_line_index(con, start->line); + struct line *iter; - if (in_sel) - count++; - if (end->line == con->lines[i]) - return count; + iter = start->line; + while (iter && iter != end->line) { + count++; + iter = get_next_line(con, iter, &index); } - llog_error(con, "selection_count_lines: end->line not found"); return count; } @@ -366,56 +362,21 @@ static unsigned int calc_line_copy_buffer(struct tsm_screen *con, unsigned int n return con->size_x * num_lines * 4 + 1; } -/* - * Copy all selected lines from the scroll back buffer - */ -static int copy_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos) +static int copy_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos) { - struct line *iter; - int line_x, line_len; - - if (!is_in_scrollback(start)) - return pos; + unsigned int index = get_line_index(con, start->line); + struct line *iter; + int line_len; + int line_x = start->x; iter = start->line; - line_x = start->x; while (iter) { line_len = calc_selection_line_len(con, start, end, iter); pos += copy_line(iter, &(buf[pos]), line_x, line_len); line_x = 0; if (iter == end->line) break; - iter = shl_dlist_next(iter, &con->sb.list, list); - } - return pos; -} - -/* - * Copy all selected lines from the regular screen - */ -static int copy_lines(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int pos) -{ - int line_len, i; - int line_x = 0; - bool in_sel; - - if (is_in_scrollback(end)) - return pos; - - in_sel = is_in_scrollback(start); - - for (i = 0; i < con->size_y; i++) { - if (start->line == con->lines[i]) { - in_sel = true; - line_x = start->x; - } - if (in_sel) { - line_len = calc_selection_line_len(con, start, end, con->lines[i]); - pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len); - line_x = 0; - } - if (end->line == con->lines[i]) - break; + iter = get_next_line(con, iter, &index); } return pos; } @@ -451,8 +412,7 @@ int tsm_screen_selection_copy(struct tsm_screen *con, char **out) start->x = 0; } - total_lines = selection_count_lines_sb(con, start, end); - total_lines += selection_count_lines(con,start, end); + total_lines = selection_count_lines(con, start, end); buf_size = calc_line_copy_buffer(con, total_lines); *out = calloc(buf_size, 1); @@ -460,7 +420,6 @@ int tsm_screen_selection_copy(struct tsm_screen *con, char **out) return -ENOMEM; } - pos = copy_lines_sb(con, start, end, *out, pos); pos = copy_lines(con, start, end, *out, pos); /* remove last line break */ From ceb732bbb381fd0e2c6886f9fe34247a457e4323 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 11:23:16 +0200 Subject: [PATCH 5/7] selection: simplify copy_line() Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-selection.c | 71 ++++++++++++----------------------------- 1 file changed, 20 insertions(+), 51 deletions(-) diff --git a/src/tsm/tsm-selection.c b/src/tsm/tsm-selection.c index 5c96d3b..51c70e6 100644 --- a/src/tsm/tsm-selection.c +++ b/src/tsm/tsm-selection.c @@ -55,7 +55,6 @@ #include #include #include -#include #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" @@ -139,48 +138,40 @@ void tsm_screen_selection_reset(struct tsm_screen *con) /* calculates the line length from the beginning to the last non zero character */ static unsigned int calc_line_len(struct line *line) { - unsigned int line_len = 0; int i; - for (i = 0; i < line->size; i++) { - if (line->cells[i].ch != 0) { - line_len = i + 1; - } - } - - return line_len; + for (i = line->size - 1; i >= 0; i--) + if (line->cells[i].ch != 0) + return i + 1; + return 0; } /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing * support for combining characters. */ -static unsigned int copy_line(struct line *line, char *buf, - unsigned int start, unsigned int len) +static unsigned int copy_line(struct tsm_screen *con, struct line *line, char *buf) { - unsigned int i, end; + unsigned int i, start, end; char *pos = buf; int line_len; line_len = calc_line_len(line); - if (start > line_len) { - return 0; - } + start = (con->sel_start.line == line) ? con->sel_start.x : 0; + end = (con->sel_end.line == line) ? con->sel_end.x + 1 : con->size_x; - end = start + len; + if (start > line_len) + return 0; - if (end > line_len) { + if (end > line_len) end = line_len; - } - for (i = start; i < line->size && i < end; ++i) { - if (i < line->size || !line->cells[i].ch) + for (i = start; i < end; i++) { + if (line->cells[i].ch) pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos); else pos += tsm_ucs4_to_utf8(' ', pos); } - pos += tsm_ucs4_to_utf8('\n', pos); - return pos - buf; } @@ -284,6 +275,12 @@ void tsm_screen_selection_word(struct tsm_screen *con, word_select(con, posx, posy); } +/* + * Get the index of a line in the screen + * + * If the line is in the scroll back buffer, return 0 + * Otherwise, return the index of the line in the screen + */ static unsigned int get_line_index(struct tsm_screen *con, struct line *line) { unsigned int i = 0; @@ -329,30 +326,6 @@ static int selection_count_lines(struct tsm_screen *con, struct selection_pos *s return count; } -/* - * Calculate the number of selected cells in a line - */ -static int calc_selection_line_len(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, struct line *line) -{ - /* one-line selection */ - if (start->line == end->line) { - return end->x - start->x + 1; - } - - /* first line of a multi-line selection */ - if (line == start->line) { - return con->size_x - start->x; - } - - /* last line of a multi-line selection */ - if (line == end->line) { - return end->x + 1; - } - - /* every other selection */ - return con->size_x; -} - /* * Calculate the maximum needed space for the number of lines given */ @@ -366,14 +339,10 @@ static int copy_lines(struct tsm_screen *con, struct selection_pos *start, struc { unsigned int index = get_line_index(con, start->line); struct line *iter; - int line_len; - int line_x = start->x; iter = start->line; while (iter) { - line_len = calc_selection_line_len(con, start, end, iter); - pos += copy_line(iter, &(buf[pos]), line_x, line_len); - line_x = 0; + pos += copy_line(con, iter, &(buf[pos])); if (iter == end->line) break; iter = get_next_line(con, iter, &index); From 8dcc4dd91bb00caf91eb428c943c3f5a3024e156 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 11:31:28 +0200 Subject: [PATCH 6/7] screen: reset selection when switching between main and alt Otherwise the selection pointers may point to the other screen lines which can cause problems when trying to copy. Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-screen.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tsm/tsm-screen.c b/src/tsm/tsm-screen.c index 83656da..43198de 100644 --- a/src/tsm/tsm-screen.c +++ b/src/tsm/tsm-screen.c @@ -959,6 +959,7 @@ void tsm_screen_reset(struct tsm_screen *con) else con->tab_ruler[i] = false; } + tsm_screen_selection_reset(con); } SHL_EXPORT @@ -978,6 +979,7 @@ void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags) if (!(old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) { con->age = con->age_cnt; con->lines = con->alt_lines; + tsm_screen_selection_reset(con); /* save attributes of main screen when we switch to alt screen */ memcpy(&con->def_attr_main, &con->def_attr, sizeof(con->def_attr)); @@ -1010,6 +1012,7 @@ void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags) if ((old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) { con->age = con->age_cnt; con->lines = con->main_lines; + tsm_screen_selection_reset(con); } if ((old & TSM_SCREEN_HIDE_CURSOR) && From 415881b2d3b7294a10b20e184ddb4333efd45059 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 17 Apr 2026 12:09:53 +0200 Subject: [PATCH 7/7] screen: reset scrollback position when switching to alternate. The scrollback is only for the main screen. Also remove the reset from the tests, as its not necessary, and now reset the scrollback. Signed-off-by: Jocelyn Falempe --- src/tsm/tsm-screen.c | 9 +++++++++ test/test_selection.c | 7 ------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tsm/tsm-screen.c b/src/tsm/tsm-screen.c index 43198de..80f3686 100644 --- a/src/tsm/tsm-screen.c +++ b/src/tsm/tsm-screen.c @@ -441,6 +441,12 @@ static inline unsigned int to_abs_y(struct tsm_screen *con, unsigned int y) return con->margin_top + y; } +static void reset_scrollback_position(struct tsm_screen *con) +{ + con->sb.pos = NULL; + con->sb.pos_num = con->sb.count; +} + SHL_EXPORT int tsm_screen_new(struct tsm_screen **out, tsm_log_t log, void *log_data) { @@ -960,6 +966,7 @@ void tsm_screen_reset(struct tsm_screen *con) con->tab_ruler[i] = false; } tsm_screen_selection_reset(con); + reset_scrollback_position(con); } SHL_EXPORT @@ -980,6 +987,7 @@ void tsm_screen_set_flags(struct tsm_screen *con, unsigned int flags) con->age = con->age_cnt; con->lines = con->alt_lines; tsm_screen_selection_reset(con); + reset_scrollback_position(con); /* save attributes of main screen when we switch to alt screen */ memcpy(&con->def_attr_main, &con->def_attr, sizeof(con->def_attr)); @@ -1013,6 +1021,7 @@ void tsm_screen_reset_flags(struct tsm_screen *con, unsigned int flags) con->age = con->age_cnt; con->lines = con->main_lines; tsm_screen_selection_reset(con); + reset_scrollback_position(con); } if ((old & TSM_SCREEN_HIDE_CURSOR) && diff --git a/test/test_selection.c b/test/test_selection.c index ca0f32b..a2e310d 100644 --- a/test/test_selection.c +++ b/test/test_selection.c @@ -263,7 +263,6 @@ START_TEST(test_screen_copy_lines) str = NULL; /* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 0, 2); tsm_screen_selection_target(screen, 14, 3); @@ -274,7 +273,6 @@ START_TEST(test_screen_copy_lines) str = NULL; /* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 41, 4); tsm_screen_selection_target(screen, 3, 1); @@ -604,7 +602,6 @@ START_TEST(test_screen_copy_lines_sb) str = NULL; /* Select "This is a copy test\nfor a selection" from top left and to bottom right copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 0, 2); tsm_screen_selection_target(screen, 14, 3); @@ -615,7 +612,6 @@ START_TEST(test_screen_copy_lines_sb) str = NULL; /* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 41, 4); tsm_screen_selection_target(screen, 3, 1); @@ -629,7 +625,6 @@ START_TEST(test_screen_copy_lines_sb) str = NULL; /* Select from scroll back buffer and the screen from top left to bottom right and copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 0, 4); tsm_screen_selection_target(screen, 18, 6); @@ -640,7 +635,6 @@ START_TEST(test_screen_copy_lines_sb) str = NULL; /* Select from scroll back buffer and the screen from bottom right to top left and copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 18, 6); tsm_screen_selection_target(screen, 0, 4); @@ -654,7 +648,6 @@ START_TEST(test_screen_copy_lines_sb) str = NULL; /* Select from scroll back buffer and the screen from bottom right to top left and copy it */ - tsm_screen_reset(screen); tsm_screen_selection_start(screen, 8, 6); tsm_screen_selection_target(screen, 7, 4);