From b383592be4583390860e6275eca8d408b3b5912b Mon Sep 17 00:00:00 2001 From: Andreas Heck Date: Fri, 30 Dec 2022 23:44:12 +0100 Subject: [PATCH] Rewrite of tsm_screen_selection_copy Fixes bugs in the original prototype implementation and introduces unit tests --- src/tsm/tsm-selection.c | 467 ++++++++++++++-------- test/CMakeLists.txt | 6 + test/test_selection.c | 860 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1159 insertions(+), 174 deletions(-) create mode 100644 test/test_selection.c diff --git a/src/tsm/tsm-selection.c b/src/tsm/tsm-selection.c index 9056abf..7821f03 100644 --- a/src/tsm/tsm-selection.c +++ b/src/tsm/tsm-selection.c @@ -126,6 +126,21 @@ void tsm_screen_selection_target(struct tsm_screen *con, selection_set(con, &con->sel_end, posx, posy); } +/* 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; +} + /* 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. */ @@ -134,8 +149,19 @@ static unsigned int copy_line(struct line *line, char *buf, { unsigned int i, end; char *pos = buf; + int line_len; + + line_len = calc_line_len(line); + if (start > line_len) { + return 0; + } end = start + 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) pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos); @@ -143,215 +169,308 @@ static unsigned int copy_line(struct line *line, char *buf, pos += tsm_ucs4_to_utf8(' ', pos); } + pos += tsm_ucs4_to_utf8('\n', pos); + return pos - buf; } -/* TODO: This beast definitely needs some "beautification", however, it's meant - * as a "proof-of-concept" so its enough for now. */ -SHL_EXPORT -int tsm_screen_selection_copy(struct tsm_screen *con, char **out) +static void swap_selections(struct selection_pos **a, struct selection_pos **b) +{ + struct selection_pos *c; + + c = *a; + *a = *b; + *b = c; +} + +/* + * Normalize a selection + * + * 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) { - unsigned int len, i; - struct selection_pos *start, *end; struct line *iter; - char *str, *pos; + struct selection_pos *buffer; - if (!con || !out) - return -EINVAL; + if ((*end)->line == NULL && (*end)->y == SELECTION_TOP) { + swap_selections(start, end); - if (!con->sel_active) - return -ENOENT; + return; + } + + if ((*start)->line && (*end)->line) { + /* single line selection */ + if ((*start)->line == (*end)->line) { + if ((*start)->x > (*end)->x) { + swap_selections(start, end); + } - /* check whether sel_start or sel_end comes first */ - if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) { - if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) { - str = strdup(""); - if (!str) - return -ENOMEM; - *out = str; - return 0; + return; } - start = &con->sel_start; - end = &con->sel_end; - } else if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) { - start = &con->sel_end; - end = &con->sel_start; - } else if (con->sel_start.line && con->sel_end.line) { - if (con->sel_start.line->sb_id < con->sel_end.line->sb_id) { - start = &con->sel_start; - end = &con->sel_end; - } else if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) { - start = &con->sel_end; - end = &con->sel_start; - } else if (con->sel_start.x < con->sel_end.x) { - start = &con->sel_start; - end = &con->sel_end; - } else { - start = &con->sel_end; - end = &con->sel_start; + + /* + * 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; } - } else if (con->sel_start.line) { - start = &con->sel_start; - end = &con->sel_end; - } else if (con->sel_end.line) { - start = &con->sel_end; - end = &con->sel_start; - } else if (con->sel_start.y < con->sel_end.y) { - start = &con->sel_start; - end = &con->sel_end; - } else if (con->sel_start.y > con->sel_end.y) { - start = &con->sel_end; - end = &con->sel_start; - } else if (con->sel_start.x < con->sel_end.x) { - start = &con->sel_start; - end = &con->sel_end; - } else { - start = &con->sel_end; - end = &con->sel_start; + + return; } - /* calculate size of buffer */ - len = 0; - iter = start->line; - if (!iter && start->y == SELECTION_TOP) - iter = con->sb_first; + /* end is in scroll back buffer and start on screen */ + if (!(*start)->line && (*end)->line) { + swap_selections(start, end); + 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); + } + + return; + } + /* reorder multi-line selection if selection was created bottom to top */ + if ((*start)->y > (*end)->y) { + swap_selections(start, end); + } +} + +/* + * 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) +{ + struct line *iter; + int count = 0; + + /* Single line selection */ + if (start->line && (start->line == end->line)) { + return 1; + } + + iter = start->line; while (iter) { - if (iter == start->line && iter == end->line) { - if (iter->size > start->x) { - if (iter->size > end->x) - len += end->x - start->x + 1; - else - len += iter->size - start->x; - } - break; - } else if (iter == start->line) { - if (iter->size > start->x) - len += iter->size - start->x; - } else if (iter == end->line) { - if (iter->size > end->x) - len += end->x + 1; - else - len += iter->size; + count++; + + if (iter == con->sb_last) { break; - } else { - len += iter->size; } - ++len; iter = iter->next; } - if (!end->line) { - if (start->line || start->y == SELECTION_TOP) - i = 0; - else - i = start->y; - for ( ; i < con->size_y; ++i) { - if (!start->line && start->y == i && end->y == i) { - if (con->size_x > start->x) { - if (con->size_x > end->x) - len += end->x - start->x + 1; - else - len += con->size_x - start->x; - } - break; - } else if (!start->line && start->y == i) { - if (con->size_x > start->x) - len += con->size_x - start->x; - } else if (end->y == i) { - if (con->size_x > end->x) - len += end->x + 1; - else - len += con->size_x; - break; - } else { - len += con->size_x; - } + return count; +} + +/* + * 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 selection_pos *start, struct selection_pos *end) +{ + /* Selection only spans lines of the scroll back buffer */ + if (start->line && end->line) { + return 0; + } + + return end->y - start->y + 1; +} - ++len; +/* + * 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) +{ + /* 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 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; } } - /* allocate buffer */ - len *= 4; - ++len; - str = malloc(len); - if (!str) - return -ENOMEM; - pos = str; + /* last line of a multi-line selection */ + if (line_num == end->y) { + return end->x + 1; + } - /* copy data into buffer */ - iter = start->line; - if (!iter && start->y == SELECTION_TOP) - iter = con->sb_first; + /* every other selection */ + return con->size_x; +} +/* + * Calculate the maximum needed space for the number of lines given + */ +static unsigned int calc_line_copy_buffer(struct tsm_screen *con, unsigned int num_lines) +{ + // 4 is the max size of a Unicode character + 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) +{ + struct line *iter; + int line_x, line_len; + + if (!start->line) { + return pos; + } + + iter = start->line; while (iter) { - if (iter == start->line && iter == end->line) { - if (iter->size > start->x) { - if (iter->size > end->x) - len = end->x - start->x + 1; - else - len = iter->size - start->x; - pos += copy_line(iter, pos, start->x, len); - } - break; - } else if (iter == start->line) { - if (iter->size > start->x) - pos += copy_line(iter, pos, start->x, - iter->size - start->x); - } else if (iter == end->line) { - if (iter->size > end->x) - len = end->x + 1; - else - len = iter->size; - pos += copy_line(iter, pos, 0, len); + line_x = 0; + if (iter == start->line) { + line_x = start->x; + } + + line_len = calc_selection_line_len_sb(con, start, end, iter); + pos += copy_line(iter, &(buf[pos]), line_x, line_len); + + if (iter == con->sb_last || iter == end->line) { break; - } else { - pos += copy_line(iter, pos, 0, iter->size); } - *pos++ = '\n'; iter = iter->next; } - if (!end->line) { - if (start->line || start->y == SELECTION_TOP) - i = 0; - else - i = start->y; - for ( ; i < con->size_y; ++i) { - iter = con->lines[i]; - if (!start->line && start->y == i && end->y == i) { - if (con->size_x > start->x) { - if (con->size_x > end->x) - len = end->x - start->x + 1; - else - len = con->size_x - start->x; - pos += copy_line(iter, pos, start->x, len); - } - break; - } else if (!start->line && start->y == i) { - if (con->size_x > start->x) - pos += copy_line(iter, pos, start->x, - con->size_x - start->x); - } else if (end->y == i) { - if (con->size_x > end->x) - len = end->x + 1; - else - len = con->size_x; - pos += copy_line(iter, pos, 0, len); - break; - } else { - pos += copy_line(iter, pos, 0, con->size_x); - } + 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, line_x, i; + + /* selection is scroll back buffer only */ + if (end->line) { + return pos; + } + + for (i = start->y; i <= end->y; i++) { + line_len = calc_selection_line_len(con, start, end, i); + + line_x = 0; + if (!start->line && i == start->y) { + line_x = start->x; + } + + pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len); + } + + 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; + int buf_size = 0; + int pos = 0; + int total_lines; + + if (!con || !out) { + return -EINVAL; + } + + if (!con->sel_active) { + 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) { + *out = strdup(""); + return 0; + } - *pos++ = '\n'; + 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; } } - /* return buffer */ - *pos = 0; - *out = str; - return pos - str; + total_lines = selection_count_lines_sb(con, start, end); + total_lines += selection_count_lines(start, end); + buf_size = calc_line_copy_buffer(con, total_lines); + + *out = calloc(buf_size, 1); + if (!*out) { + return -ENOMEM; + } + + pos = copy_lines_sb(con, start, end, *out, pos); + pos = copy_lines(con, start, end, *out, pos); + + /* remove last line break */ + if (pos > 0) { + (*out)[--pos] = '\0'; + } + + return pos; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fb56d8a..9ec5aa7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,6 +41,12 @@ libtsm_add_test(test_screen check::check ) +libtsm_add_test(test_selection + LINK_LIBRARIES + tsm_test + check::check +) + libtsm_add_test(test_vte LINK_LIBRARIES tsm_test diff --git a/test/test_selection.c b/test/test_selection.c new file mode 100644 index 0000000..5c151f1 --- /dev/null +++ b/test/test_selection.c @@ -0,0 +1,860 @@ +/* + * TSM - Selection Copy Tests + * + * Copyright (c) 2022 Andreas Heck + * + * 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. + */ + +#include +#include "test_common.h" +#include "libtsm.h" +#include "libtsm-int.h" + +static void write_string(struct tsm_screen *screen, const char *str) +{ + struct tsm_screen_attr attr; + int i; + + bzero(&attr, sizeof(attr)); + attr.fccode = 37; /* white */ + attr.bccode = 40; /* black */ + + for (i = 0; str[i]; i++) { + tsm_screen_write(screen, str[i], &attr); + } +} + +START_TEST(test_screen_copy_incomplete) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + + /* select start but don't end selection and copy it */ + tsm_screen_selection_start(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_int_eq(1, r); // strlen == 1? + ck_assert_str_eq("H", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} + +START_TEST(test_screen_copy_one_cell) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + + /* select start, end selection on same cell and copy it */ + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_int_eq(1, r); // strlen == 1? + ck_assert_str_eq("H", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} + +START_TEST(test_screen_copy_line) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "Filler Text"); + tsm_screen_newline(screen); + + /* select "Hello World!" from left to right and copy it */ + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 14, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + const char *expected = "Hello World!"; + ck_assert_int_eq(strlen(expected), r); + ck_assert_str_eq(expected, str); + free(str); + str = NULL; + + /* select "Hello" from left to right and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 7, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello", str); + free(str); + str = NULL; + + /* select " Hello" from left to right and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 0, 1); + tsm_screen_selection_target(screen, 7, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq(" Hello", str); + free(str); + str = NULL; + + /* select "Hello World!" from right to left and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 14, 1); + tsm_screen_selection_target(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + /* select "Hello" from right to left and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 7, 1); + tsm_screen_selection_target(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_line_scrolled) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + for (int i = 0; i < 39; i++) { + tsm_screen_newline(screen); + } + + write_string(screen, " Hello World!"); + + /* select "Hello World!" from left to right and copy it */ + tsm_screen_selection_start(screen, 3, 39); + 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); + tsm_screen_newline(screen); + tsm_screen_newline(screen); + tsm_screen_newline(screen); + 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, 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); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_lines) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "This is a copy test"); + tsm_screen_newline(screen); + write_string(screen, "for a selection with multiple lines."); + tsm_screen_newline(screen); + write_string(screen, "All of them are on screen (not in the sb).------"); + tsm_screen_newline(screen); + + /* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */ + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 41, 4); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + const char *expected = "Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb)."; + ck_assert_int_eq(strlen(expected), r); + ck_assert_str_eq(expected, str); + free(str); + 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); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("This is a copy test\nfor a selection", str); + free(str); + 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); + + 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); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_lines_scrolled) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + for (int i = 0; i < 37; i++) { + tsm_screen_newline(screen); + } + tsm_screen_newline(screen); + + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "Line 2"); + tsm_screen_newline(screen); + write_string(screen, "Line 3"); + + /* select "Hello World!" from left to right and copy it */ + tsm_screen_selection_start(screen, 3, 37); + 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); + tsm_screen_newline(screen); + tsm_screen_newline(screen); + tsm_screen_newline(screen); + 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, 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); + ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_line_sb) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + int i; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + tsm_screen_set_max_sb(screen, 10); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "Filler Text"); + tsm_screen_newline(screen); + + for (i = 0; i < 40; i++) { + tsm_screen_newline(screen); + } + + tsm_screen_sb_up(screen, 4); + + /* select "Hello World!" from left to right and copy it */ + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 14, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + /* select "Hello" from left to right and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 7, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello", str); + free(str); + str = NULL; + + /* select " Hello" from left to right and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 0, 1); + tsm_screen_selection_target(screen, 7, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq(" Hello", str); + free(str); + str = NULL; + + /* select "Hello World!" from right to left and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 14, 1); + tsm_screen_selection_target(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + /* select "Hello" from right to left and copy it */ + tsm_screen_selection_reset(screen); + tsm_screen_selection_start(screen, 7, 1); + tsm_screen_selection_target(screen, 3, 1); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_line_sb_scrolled) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + /* + * Create a selection of text in one line on the main screen, scroll it + * into the sb and copy it. + */ + + tsm_screen_set_max_sb(screen, 10); + + write_string(screen, " Hello World!"); + + /* select "Hello World!" from left to right */ + tsm_screen_selection_start(screen, 3, 0); + 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++) { + tsm_screen_newline(screen); + } + + 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); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + /* + * Create a selection of the text in the sb, scroll it further into the + * sb and copy it. + */ + + tsm_screen_selection_reset(screen); + tsm_screen_sb_up(screen, 1); + + tsm_screen_selection_start(screen, 3, 0); + 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); + 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, 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); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_line_sb_scrolled_invalid) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + /* + * Disable the sb. If a selection is moved out of the sb it becomes invalid. + * Copying it should return an empty string. + */ + tsm_screen_set_max_sb(screen, 0); + + write_string(screen, " Hello World!"); + + /* select "Hello World!" from left to right */ + tsm_screen_selection_start(screen, 3, 0); + 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++) { + 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); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_lines_sb) +{ + struct tsm_screen *screen; + int r; + char *str = NULL; + int i; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + tsm_screen_set_max_sb(screen, 10); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + tsm_screen_newline(screen); + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "This is a copy test"); + tsm_screen_newline(screen); + write_string(screen, "for a selection with multiple lines."); + tsm_screen_newline(screen); + write_string(screen, "All of them are on screen (not in the sb).------"); + tsm_screen_newline(screen); + + write_string(screen, "Text not in SB"); + tsm_screen_newline(screen); + write_string(screen, "More Text not in SB"); + tsm_screen_newline(screen); + + + for (i = 0; i < 38; i++) { + tsm_screen_newline(screen); + } + + tsm_screen_sb_up(screen, 6); + + /* Select all text excluding the first 3 spaces and the trailing '-' chars from top left to bottom right and copy it */ + tsm_screen_selection_start(screen, 3, 1); + tsm_screen_selection_target(screen, 41, 4); + + 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); + free(str); + 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); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("This is a copy test\nfor a selection", str); + free(str); + 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); + + 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); + free(str); + 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); + + 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); + free(str); + 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); + + 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); + free(str); + 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); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("them are on screen (not in the sb).------\nText not in SB\nMore Text", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_lines_sb_scrolled) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + /* + * Create a selection of text on the main screen, scroll it into the sb + * and copy it. + */ + + tsm_screen_set_max_sb(screen, 10); + + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "Line 2"); + tsm_screen_newline(screen); + write_string(screen, "Line 3"); + + /* select "Hello World!" from left to right */ + tsm_screen_selection_start(screen, 3, 0); + 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 (int i = 0; i < 40; i++) { + tsm_screen_newline(screen); + } + + 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); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str); + free(str); + str = NULL; + + /* + * Create a selection of the text in the sb, scroll it further into + * the sb and copy it. + */ + + tsm_screen_selection_reset(screen); + tsm_screen_sb_up(screen, 3); + + tsm_screen_selection_start(screen, 3, 0); + 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); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Hello World!\nLine 2\nLine 3", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +START_TEST(test_screen_copy_lines_sb_scrolled_cut_off) +{ + struct tsm_screen *screen; + int r, i; + char *str = NULL; + + r = tsm_screen_new(&screen, NULL, NULL); + ck_assert_int_eq(r, 0); + + r = tsm_screen_resize(screen, 80, 40); + ck_assert_int_eq(r, 0); + + /* + * Disable the sb. If a part of a selection is moved out of the sb it is + * cut off. Copying it should return the remaining part of the selection. + */ + tsm_screen_set_max_sb(screen, 0); + + write_string(screen, " Hello World!"); + tsm_screen_newline(screen); + write_string(screen, "Line 2"); + tsm_screen_newline(screen); + write_string(screen, "Line 3"); + + /* select "Hello World!" from left to right */ + tsm_screen_selection_start(screen, 3, 0); + 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 (int i = 0; i < 39; i++) { + tsm_screen_newline(screen); + } + + 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, 5); + ck_assert_int_eq(screen->sel_end.y, 0); + ck_assert_ptr_eq(screen->sel_end.line, NULL); + + r = tsm_screen_selection_copy(screen, &str); + ck_assert_ptr_ne(NULL, str); + ck_assert_str_eq("Line 3", str); + free(str); + str = NULL; + + tsm_screen_unref(screen); + screen = NULL; +} +END_TEST + +TEST_DEFINE_CASE(misc) + TEST(test_screen_copy_incomplete) + TEST(test_screen_copy_one_cell) + TEST(test_screen_copy_line) + TEST(test_screen_copy_line_scrolled) + TEST(test_screen_copy_lines) + TEST(test_screen_copy_lines_scrolled) + TEST(test_screen_copy_line_sb) + TEST(test_screen_copy_line_sb_scrolled) + TEST(test_screen_copy_line_sb_scrolled_invalid) + TEST(test_screen_copy_lines_sb) + TEST(test_screen_copy_lines_sb_scrolled) + TEST(test_screen_copy_lines_sb_scrolled_cut_off) +TEST_END_CASE + +TEST_DEFINE( + TEST_SUITE(selection, + TEST_CASE(misc), + TEST_END + ) +)