From d706e6828062aa35505d3893fb3c04b84985aa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 3 Jan 2020 23:29:45 +0100 Subject: [PATCH 01/11] selection: track selection type; normal or block selection --- input.c | 4 +++- search.c | 2 +- selection.c | 13 +++++++++---- selection.h | 3 ++- terminal.h | 3 +++ 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/input.c b/input.c index 7f81da8d..0d0f1e4f 100644 --- a/input.c +++ b/input.c @@ -581,7 +581,9 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, if (button == BTN_LEFT) { switch (wayl->mouse.count) { case 1: - selection_start(term, wayl->mouse.col, wayl->mouse.row); + selection_start( + term, wayl->mouse.col, wayl->mouse.row, + wayl->kbd.ctrl ? SELECTION_BLOCK : SELECTION_NORMAL); break; case 2: diff --git a/search.c b/search.c index 31d26c36..6eef411c 100644 --- a/search.c +++ b/search.c @@ -138,7 +138,7 @@ search_update_selection(struct terminal *term, assert(selection_row >= 0 && selection_row < term->grid->num_rows); - selection_start(term, start_col, selection_row); + selection_start(term, start_col, selection_row, SELECTION_NORMAL); } /* Update selection endpoint */ diff --git a/selection.c b/selection.c index 2da2ba03..6efe682c 100644 --- a/selection.c +++ b/selection.c @@ -126,14 +126,19 @@ extract_selection(const struct terminal *term) } void -selection_start(struct terminal *term, int col, int row) +selection_start(struct terminal *term, int col, int row, + enum selection_kind kind) { if (!selection_enabled(term)) return; selection_cancel(term); - LOG_DBG("selection started at %d,%d", row, col); + LOG_DBG("%s selection started at %d,%d", + kind == SELECTION_NORMAL ? "normal" : + kind == SELECTION_BLOCK ? "block" : "", + row, col); + term->selection.kind = kind; term->selection.start = (struct coord){col, term->grid->view + row}; term->selection.end = (struct coord){-1, -1}; } @@ -287,7 +292,7 @@ selection_mark_word(struct terminal *term, int col, int row, bool spaces_only, } } - selection_start(term, start.col, start.row); + selection_start(term, start.col, start.row, SELECTION_NORMAL); selection_update(term, end.col, end.row); selection_finalize(term, serial); } @@ -295,7 +300,7 @@ selection_mark_word(struct terminal *term, int col, int row, bool spaces_only, void selection_mark_row(struct terminal *term, int row, uint32_t serial) { - selection_start(term, 0, row); + selection_start(term, 0, row, SELECTION_NORMAL); selection_update(term, term->cols - 1, row); selection_finalize(term, serial); } diff --git a/selection.h b/selection.h index 0ead8f0e..bc690eb1 100644 --- a/selection.h +++ b/selection.h @@ -9,7 +9,8 @@ extern const struct wl_data_device_listener data_device_listener; extern const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener; bool selection_enabled(const struct terminal *term); -void selection_start(struct terminal *term, int col, int row); +void selection_start( + struct terminal *term, int col, int row, enum selection_kind kind); void selection_update(struct terminal *term, int col, int row); void selection_finalize(struct terminal *term, uint32_t serial); void selection_cancel(struct terminal *term); diff --git a/terminal.h b/terminal.h index eb3b7f1e..e0b8d44a 100644 --- a/terminal.h +++ b/terminal.h @@ -157,6 +157,8 @@ enum mouse_reporting { enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; +enum selection_kind { SELECTION_NORMAL, SELECTION_BLOCK }; + struct ptmx_buffer { void *data; size_t len; @@ -248,6 +250,7 @@ struct terminal { const char *xcursor; struct { + enum selection_kind kind; struct coord start; struct coord end; } selection; From cb9ae4f6a13f9e4001539dfed359f55a200c758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 3 Jan 2020 23:34:58 +0100 Subject: [PATCH 02/11] render: coord_is_selected: handle block selections --- render.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/render.c b/render.c index ba70aee0..755304ea 100644 --- a/render.c +++ b/render.c @@ -350,16 +350,30 @@ coord_is_selected(const struct terminal *term, int col, int row) row += term->grid->view; - if (start->row == end->row) { - return row == start->row && col >= start->col && col <= end->col; - } else { - if (row == start->row) - return col >= start->col; - else if (row == end->row) - return col <= end->col; + switch (term->selection.kind) { + case SELECTION_NORMAL: + if (start->row == end->row) { + return row == start->row && col >= start->col && col <= end->col; + } else { + if (row == start->row) + return col >= start->col; + else if (row == end->row) + return col <= end->col; + else + return row >= start->row && row <= end->row; + } + + case SELECTION_BLOCK: + if (start->col <= end->col) + return row >= start->row && row <= end->row && + col >= start->col && col <= end->col; else - return row >= start->row && row <= end->row; + return row >= start->row && row <= end->row && + col <= start->col && col >= end->col; } + + assert(false); + return false; } static int From f12b1473fde2045d38750aa1051ad624e0fea2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 12:03:04 +0100 Subject: [PATCH 03/11] selection: store cell 'selected' state in the cells' attributes Instead of having the renderer calculate, for each cell, whether that cell is currently selected or not, make selection_update() mark/unmark the selected cells. The renderer now only has to look at the cells' 'selected' attribute. This makes the renderer both smaller and faster. --- render.c | 61 +++------------------- selection.c | 143 +++++++++++++++++++++++++++++++++++++++++++--------- terminal.h | 3 +- 3 files changed, 129 insertions(+), 78 deletions(-) diff --git a/render.c b/render.c index 755304ea..d97da212 100644 --- a/render.c +++ b/render.c @@ -275,13 +275,14 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix, static void draw_cursor(const struct terminal *term, const struct cell *cell, - bool is_selected, const struct font *font, - pixman_image_t *pix, pixman_color_t *fg, const pixman_color_t *bg, - int x, int y, int cols) + const struct font *font, pixman_image_t *pix, pixman_color_t *fg, + const pixman_color_t *bg, int x, int y, int cols) { pixman_color_t cursor_color; pixman_color_t text_color; + bool is_selected = cell->attrs.selected; + if (term->cursor_color.cursor >> 31) { assert(term->cursor_color.text); @@ -331,51 +332,6 @@ draw_cursor(const struct terminal *term, const struct cell *cell, } } -static bool -coord_is_selected(const struct terminal *term, int col, int row) -{ - if (term->selection.start.col == -1 || term->selection.end.col == -1) - return false; - - const struct coord *start = &term->selection.start; - const struct coord *end = &term->selection.end; - - if (start->row > end->row || (start->row == end->row && start->col > end->col)) { - const struct coord *tmp = start; - start = end; - end = tmp; - } - - assert(start->row <= end->row); - - row += term->grid->view; - - switch (term->selection.kind) { - case SELECTION_NORMAL: - if (start->row == end->row) { - return row == start->row && col >= start->col && col <= end->col; - } else { - if (row == start->row) - return col >= start->col; - else if (row == end->row) - return col <= end->col; - else - return row >= start->row && row <= end->row; - } - - case SELECTION_BLOCK: - if (start->col <= end->col) - return row >= start->row && row <= end->row && - col >= start->col && col <= end->col; - else - return row >= start->row && row <= end->row && - col <= start->col && col >= end->col; - } - - assert(false); - return false; -} - static int render_cell(struct terminal *term, pixman_image_t *pix, struct cell *cell, int col, int row, bool has_cursor) @@ -390,7 +346,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, int x = term->x_margin + col * width; int y = term->y_margin + row * height; - bool is_selected = coord_is_selected(term, col, row); + //bool is_selected = coord_is_selected(term, col, row); + bool is_selected = cell->attrs.selected; uint32_t _fg = 0; uint32_t _bg = 0; @@ -430,10 +387,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, PIXMAN_OP_SRC, pix, &bg, 1, &(pixman_rectangle16_t){x, y, cell_cols * width, height}); - if (has_cursor) { - draw_cursor( - term, cell, is_selected, font, pix, &fg, &bg, x, y, cell_cols); - } + if (has_cursor) + draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); if (cell->attrs.blink) term_arm_blink_timer(term); diff --git a/selection.c b/selection.c index 6efe682c..1510210e 100644 --- a/selection.c +++ b/selection.c @@ -45,6 +45,92 @@ selection_on_row_in_view(const struct terminal *term, int row_no) return row_no >= start->row && row_no <= end->row; } +static void +foreach_selected_normal( + struct terminal *term, + void (*cb)(struct terminal *term, struct row *row, struct cell *cell, void *data), + void *data) +{ + const struct coord *start = &term->selection.start; + const struct coord *end = &term->selection.end; + + int start_row, end_row; + int start_col, end_col; + + if (start->row < end->row) { + start_row = start->row; + end_row = end->row; + start_col = start->col; + end_col = end->col; + } else if (start->row > end->row) { + start_row = end->row; + end_row = start->row; + start_col = end->col; + end_col = start->col; + } else { + start_row = end_row = start->row; + start_col = min(start->col, end->col); + end_col = max(start->col, end->col); + } + + for (int r = start_row; r <= end_row; r++) { + struct row *row = term->grid->rows[r]; + + for (int c = start_col; + c <= (r == end_row ? end_col : term->cols - 1); + c++) + { + cb(term, row, &row->cells[c], data); + } + + start_col = 0; + } +} + +static void +foreach_selected_block( + struct terminal *term, + void (*cb)(struct terminal *term, struct row *row, struct cell *cell, void *data), + void *data) +{ + const struct coord *start = &term->selection.start; + const struct coord *end = &term->selection.end; + + struct coord top_left = { + .row = min(start->row, end->row), + .col = min(start->col, end->col), + }; + + struct coord bottom_right = { + .row = max(start->row, end->row), + .col = max(start->col, end->col), + }; + + for (int r = top_left.row; r <= bottom_right.row; r++) { + struct row *row = term->grid->rows[r]; + + for (int c = top_left.col; c <= bottom_right.col; c++) + cb(term, row, &row->cells[c], data); + } +} + +static void +foreach_selected( + struct terminal *term, + void (*cb)(struct terminal *term, struct row *row, struct cell *cell, void *data), + void *data) +{ + switch (term->selection.kind) { + case SELECTION_NORMAL: + return foreach_selected_normal(term, cb, data); + + case SELECTION_BLOCK: + return foreach_selected_block(term, cb, data); + } + + assert(false); +} + static char * extract_selection(const struct terminal *term) { @@ -143,6 +229,30 @@ selection_start(struct terminal *term, int col, int row, term->selection.end = (struct coord){-1, -1}; } +static void +unmark_selected(struct terminal *term, struct row *row, struct cell *cell, + void *data) +{ + if (!cell->attrs.selected) + return; + + row->dirty = 1; + cell->attrs.selected = 0; + cell->attrs.clean = 0; +} + +static void +mark_selected(struct terminal *term, struct row *row, struct cell *cell, + void *data) +{ + if (cell->attrs.selected) + return; + + row->dirty = 1; + cell->attrs.selected = 1; + cell->attrs.clean = 0; +} + void selection_update(struct terminal *term, int col, int row) { @@ -154,24 +264,16 @@ selection_update(struct terminal *term, int col, int row) term->selection.end.row, term->selection.end.col, row, col); - int start_row = term->selection.start.row; - int old_end_row = term->selection.end.row; - int new_end_row = term->grid->view + row; + assert(term->selection.start.row != -1); + assert(term->grid->view + row != -1); - assert(start_row != -1); - assert(new_end_row != -1); - - if (old_end_row == -1) - old_end_row = new_end_row; - - int from = min(start_row, min(old_end_row, new_end_row)); - int to = max(start_row, max(old_end_row, new_end_row)); + if (term->selection.end.row != -1) + foreach_selected(term, &unmark_selected, NULL); term->selection.end = (struct coord){col, term->grid->view + row}; assert(term->selection.start.row != -1 && term->selection.end.row != -1); - term_damage_rows_in_view(term, from - term->grid->view, to - term->grid->view); - + foreach_selected(term, &mark_selected, NULL); render_refresh(term); } @@ -212,20 +314,13 @@ selection_cancel(struct terminal *term) term->selection.start.row, term->selection.start.col, term->selection.end.row, term->selection.end.col); - int start_row = term->selection.start.row; - int end_row = term->selection.end.row; + if (term->selection.start.row != -1 && term->selection.end.row != -1) { + foreach_selected(term, &unmark_selected, NULL); + render_refresh(term); + } term->selection.start = (struct coord){-1, -1}; term->selection.end = (struct coord){-1, -1}; - - if (start_row != -1 && end_row != -1) { - term_damage_rows_in_view( - term, - min(start_row, end_row) - term->grid->view, - max(start_row, end_row) - term->grid->view); - - render_refresh(term); - } } void diff --git a/terminal.h b/terminal.h index e0b8d44a..4bc4d092 100644 --- a/terminal.h +++ b/terminal.h @@ -41,7 +41,8 @@ struct attributes { uint32_t clean:1; uint32_t have_fg:1; uint32_t have_bg:1; - uint32_t reserved:5; + uint32_t selected:1; + uint32_t reserved:4; uint32_t bg:24; }; static_assert(sizeof(struct attributes) == 8, "bad size"); From 2a531327dde3c86edea2baf9503d08545d8f4767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 12:09:09 +0100 Subject: [PATCH 04/11] selection: selection_cancel() now sets 'kind' to SELECTION_NONE --- selection.c | 5 +++++ terminal.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index 1510210e..b9f4579e 100644 --- a/selection.c +++ b/selection.c @@ -126,6 +126,10 @@ foreach_selected( case SELECTION_BLOCK: return foreach_selected_block(term, cb, data); + + case SELECTION_NONE: + assert(false); + return; } assert(false); @@ -319,6 +323,7 @@ selection_cancel(struct terminal *term) render_refresh(term); } + term->selection.kind = SELECTION_NONE; term->selection.start = (struct coord){-1, -1}; term->selection.end = (struct coord){-1, -1}; } diff --git a/terminal.h b/terminal.h index 4bc4d092..9d28ff41 100644 --- a/terminal.h +++ b/terminal.h @@ -158,7 +158,7 @@ enum mouse_reporting { enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; -enum selection_kind { SELECTION_NORMAL, SELECTION_BLOCK }; +enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK }; struct ptmx_buffer { void *data; From b7c970010d4a7d06215ac9a77e92d57ec2d593c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 12:09:26 +0100 Subject: [PATCH 05/11] term: mouse_grabbed: ctrl may be pressed too (for block selection) --- terminal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 463ceb90..87ab25b0 100644 --- a/terminal.c +++ b/terminal.c @@ -1530,7 +1530,7 @@ term_mouse_grabbed(const struct terminal *term) return term->wl->kbd_focus == term && term->wl->kbd.shift && - !term->wl->kbd.alt && !term->wl->kbd.ctrl && !term->wl->kbd.meta; + !term->wl->kbd.alt && /*!term->wl->kbd.ctrl &&*/ !term->wl->kbd.meta; } void From 975a35ae16949495076c3377573e906422de97ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 12:59:29 +0100 Subject: [PATCH 06/11] selection: extract_selection now uses foreach_selected() This "automagically" adds support for block selections, since foreach_selected already handles block selections. --- selection.c | 172 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 62 deletions(-) diff --git a/selection.c b/selection.c index b9f4579e..1ecf6a7a 100644 --- a/selection.c +++ b/selection.c @@ -135,84 +135,132 @@ foreach_selected( assert(false); } -static char * -extract_selection(const struct terminal *term) +static size_t +selection_cell_count(const struct terminal *term) { const struct coord *start = &term->selection.start; const struct coord *end = &term->selection.end; - assert(start->row <= end->row); + switch (term->selection.kind) { + case SELECTION_NONE: + return 0; - size_t max_cells = 0; - if (start->row == end->row) { - assert(start->col <= end->col); - max_cells = end->col - start->col + 1; - } else { - max_cells = term->cols - start->col; - max_cells += term->cols * (end->row - start->row - 1); - max_cells += end->col + 1; - } + case SELECTION_NORMAL: + if (term->selection.end.row == -1) + return 0; - const size_t buf_size = max_cells * 4 + 1; - char *buf = malloc(buf_size); - int idx = 0; + assert(term->selection.start.row != -1); - int start_col = start->col; - for (int r = start->row; r <= end->row; r++) { - const struct row *row = grid_row_in_view(term->grid, r - term->grid->view); - if (row == NULL) - continue; - - /* - * Trailing empty cells are never included in the selection. - * - * Empty cells between non-empty cells however are replaced - * with spaces. - */ - - for (int col = start_col, empty_count = 0; - col <= (r == end->row ? end->col : term->cols - 1); - col++) - { - const struct cell *cell = &row->cells[col]; - - if (cell->wc == 0) { - empty_count++; - if (col == term->cols - 1) - buf[idx++] = '\n'; - continue; - } - - assert(idx + empty_count <= buf_size); - memset(&buf[idx], ' ', empty_count); - idx += empty_count; - empty_count = 0; - - assert(idx + 1 <= buf_size); - - mbstate_t ps = {0}; - size_t len = wcrtomb(&buf[idx], cell->wc, &ps); - assert(len >= 0); /* All wchars were valid multibyte strings to begin with */ - idx += len; + if (start->row > end->row) { + const struct coord *tmp = start; + start = end; + end = tmp; } - start_col = 0; + if (start->row == end->row) + return end->col - start->col + 1; + else { + size_t cells = term->cols - start->col; + cells += term->cols * (end->row - start->row - 1); + cells += end->col + 1; + return cells; + } + + case SELECTION_BLOCK: { + struct coord top_left = { + .row = min(start->row, end->row), + .col = min(start->col, end->col), + }; + + struct coord bottom_right = { + .row = max(start->row, end->row), + .col = max(start->col, end->col), + }; + + int cols = bottom_right.col - top_left.col + 1; + int rows = bottom_right.row - top_left.row + 1; + return rows * cols; + } } - if (idx == 0) { + assert(false); + return 0; +} + +struct extract { + char *buf; + size_t size; + size_t idx; + size_t empty_count; + struct row *last_row; + struct cell *last_cell; +}; + +static void +extract_one(struct terminal *term, struct row *row, struct cell *cell, + void *data) +{ + struct extract *ctx = data; + + if (ctx->last_row != NULL && row != ctx->last_row && + ((term->selection.kind == SELECTION_NORMAL && ctx->last_cell->wc == 0) || + term->selection.kind == SELECTION_BLOCK)) + { + /* Last cell was the last column in the selection */ + ctx->buf[ctx->idx++] = '\n'; + ctx->empty_count = 0; + } + + else if (cell->wc == 0) + ctx->empty_count++; + + else { + /* Replace empty cells with spaces when followed by non-empty cell */ + assert(ctx->idx + ctx->empty_count <= ctx->size); + memset(&ctx->buf[ctx->idx], ' ', ctx->empty_count); + ctx->idx += ctx->empty_count; + ctx->empty_count = 0; + + assert(ctx->idx + 1 <= ctx->size); + + mbstate_t ps = {0}; + size_t len = wcrtomb(&ctx->buf[ctx->idx], cell->wc, &ps); + assert(len >= 0); /* All wchars were valid multibyte strings to begin with */ + assert(ctx->idx + len <= ctx->size); + ctx->idx += len; + } + + ctx->last_row = row; + ctx->last_cell = cell; +} + +static char * +extract_selection(const struct terminal *term) +{ + const size_t max_cells = selection_cell_count(term); + const size_t buf_size = max_cells * 4 + 1; /* Multiply by 4 to handle multibyte chars */ + + struct extract ctx = { + .buf = malloc(buf_size), + .size = buf_size, + }; + + foreach_selected((struct terminal *)term, &extract_one, &ctx); + + if (ctx.idx == 0) { /* Selection of empty cells only */ - buf[idx] = '\0'; - return buf; + ctx.buf[ctx.idx] = '\0'; + return ctx.buf; } - assert(idx > 0); - assert(idx < buf_size); - if (buf[idx - 1] == '\n') - buf[idx - 1] = '\0'; + assert(ctx.idx > 0); + assert(ctx.idx < ctx.size); + if (ctx.buf[ctx.idx - 1] == '\n') + ctx.buf[ctx.idx - 1] = '\0'; else - buf[idx] = '\0'; + ctx.buf[ctx.idx] = '\0'; - return buf; + return ctx.buf; } void From e28cb989d84c14f1026c263419462bfdd3d58e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 13:09:06 +0100 Subject: [PATCH 07/11] selection: simplify extraction by converting to UTF-8 at the end Instead of converting a cell at a time to UTF-8, copy the wide characters as is, and then convert everything at once at the end. --- selection.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/selection.c b/selection.c index 1ecf6a7a..f81fcc85 100644 --- a/selection.c +++ b/selection.c @@ -188,7 +188,7 @@ selection_cell_count(const struct terminal *term) } struct extract { - char *buf; + wchar_t *buf; size_t size; size_t idx; size_t empty_count; @@ -207,7 +207,7 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, term->selection.kind == SELECTION_BLOCK)) { /* Last cell was the last column in the selection */ - ctx->buf[ctx->idx++] = '\n'; + ctx->buf[ctx->idx++] = L'\n'; ctx->empty_count = 0; } @@ -217,17 +217,12 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, else { /* Replace empty cells with spaces when followed by non-empty cell */ assert(ctx->idx + ctx->empty_count <= ctx->size); - memset(&ctx->buf[ctx->idx], ' ', ctx->empty_count); - ctx->idx += ctx->empty_count; + for (size_t i = 0; i < ctx->empty_count; i++) + ctx->buf[ctx->idx++] = L' '; ctx->empty_count = 0; assert(ctx->idx + 1 <= ctx->size); - - mbstate_t ps = {0}; - size_t len = wcrtomb(&ctx->buf[ctx->idx], cell->wc, &ps); - assert(len >= 0); /* All wchars were valid multibyte strings to begin with */ - assert(ctx->idx + len <= ctx->size); - ctx->idx += len; + ctx->buf[ctx->idx++] = cell->wc; } ctx->last_row = row; @@ -238,10 +233,10 @@ static char * extract_selection(const struct terminal *term) { const size_t max_cells = selection_cell_count(term); - const size_t buf_size = max_cells * 4 + 1; /* Multiply by 4 to handle multibyte chars */ + const size_t buf_size = max_cells + 1; struct extract ctx = { - .buf = malloc(buf_size), + .buf = malloc(buf_size * sizeof(wchar_t)), .size = buf_size, }; @@ -249,18 +244,27 @@ extract_selection(const struct terminal *term) if (ctx.idx == 0) { /* Selection of empty cells only */ - ctx.buf[ctx.idx] = '\0'; - return ctx.buf; + ctx.buf[ctx.idx] = L'\0'; + } else { + assert(ctx.idx > 0); + assert(ctx.idx < ctx.size); + if (ctx.buf[ctx.idx - 1] == L'\n') + ctx.buf[ctx.idx - 1] = L'\0'; + else + ctx.buf[ctx.idx] = L'\0'; } - assert(ctx.idx > 0); - assert(ctx.idx < ctx.size); - if (ctx.buf[ctx.idx - 1] == '\n') - ctx.buf[ctx.idx - 1] = '\0'; - else - ctx.buf[ctx.idx] = '\0'; + size_t len = wcstombs(NULL, ctx.buf, 0); + if (len == (size_t)-1) { + LOG_ERRNO("failed to convert selection to UTF-8"); + free(ctx.buf); + return NULL; + } - return ctx.buf; + char *ret = malloc(len + 1); + wcstombs(ret, ctx.buf, len + 1); + free(ctx.buf); + return ret; } void From 6ee86be1bf881f99533cc660123c6992f19f20f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 13:19:30 +0100 Subject: [PATCH 08/11] selection: fix bug where first column on all rows but the first was lost When extracting text from the selection, we lost the first column on all rows but the first. This is because the algorithm changed slightly when we moved to foreach_selection(); the end-of-line detection is now done on the first column of the new line, instead of the last column on the previous line. --- selection.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/selection.c b/selection.c index f81fcc85..b241d375 100644 --- a/selection.c +++ b/selection.c @@ -211,20 +211,22 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, ctx->empty_count = 0; } - else if (cell->wc == 0) + if (cell->wc == 0) { ctx->empty_count++; - - else { - /* Replace empty cells with spaces when followed by non-empty cell */ - assert(ctx->idx + ctx->empty_count <= ctx->size); - for (size_t i = 0; i < ctx->empty_count; i++) - ctx->buf[ctx->idx++] = L' '; - ctx->empty_count = 0; - - assert(ctx->idx + 1 <= ctx->size); - ctx->buf[ctx->idx++] = cell->wc; + ctx->last_row = row; + ctx->last_cell = cell; + return; } + /* Replace empty cells with spaces when followed by non-empty cell */ + assert(ctx->idx + ctx->empty_count <= ctx->size); + for (size_t i = 0; i < ctx->empty_count; i++) + ctx->buf[ctx->idx++] = L' '; + ctx->empty_count = 0; + + assert(ctx->idx + 1 <= ctx->size); + ctx->buf[ctx->idx++] = cell->wc; + ctx->last_row = row; ctx->last_cell = cell; } From e6f04832949c97e829b21a2f5f71e268de00c7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 13:53:30 +0100 Subject: [PATCH 09/11] selection: cell count now adds one extra column per row, for \n When we extract text, we may insert '\n' at the end of each line (or last column of selection, for block selections). These newlines doesn't occupy any physical cells, and thus we must **make** room for them in the extraction buffer. --- selection.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/selection.c b/selection.c index b241d375..709334d4 100644 --- a/selection.c +++ b/selection.c @@ -160,8 +160,15 @@ selection_cell_count(const struct terminal *term) if (start->row == end->row) return end->col - start->col + 1; else { - size_t cells = term->cols - start->col; - cells += term->cols * (end->row - start->row - 1); + size_t cells = 0; + + /* First row; start of selection to end of row */ + cells += term->cols - start->col; + + /* Full rows; add one extra column for \n */ + cells += (term->cols + 1) * (end->row - start->row - 1); + + /* Final row; start of row to end of selection */ cells += end->col + 1; return cells; } @@ -177,7 +184,8 @@ selection_cell_count(const struct terminal *term) .col = max(start->col, end->col), }; - int cols = bottom_right.col - top_left.col + 1; + /* Add one extra column for \n */ + int cols = bottom_right.col - top_left.col + 1 + 1; int rows = bottom_right.row - top_left.row + 1; return rows * cols; } From 4c78b0203e89b20394ac0fa3a83973d923805ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 13:56:52 +0100 Subject: [PATCH 10/11] selection: rename selection_cell_count -> min_bufsize_for_extraction --- selection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selection.c b/selection.c index 709334d4..0df05647 100644 --- a/selection.c +++ b/selection.c @@ -136,7 +136,7 @@ foreach_selected( } static size_t -selection_cell_count(const struct terminal *term) +min_bufsize_for_extraction(const struct terminal *term) { const struct coord *start = &term->selection.start; const struct coord *end = &term->selection.end; @@ -242,7 +242,7 @@ extract_one(struct terminal *term, struct row *row, struct cell *cell, static char * extract_selection(const struct terminal *term) { - const size_t max_cells = selection_cell_count(term); + const size_t max_cells = min_bufsize_for_extraction(term); const size_t buf_size = max_cells + 1; struct extract ctx = { From a83c9e28137158fe0fb68a0e163e0305ca14ddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2020 14:06:39 +0100 Subject: [PATCH 11/11] selection: min_bufsize_for_extraction: add one extra column for *all* rows --- selection.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/selection.c b/selection.c index 0df05647..9a574169 100644 --- a/selection.c +++ b/selection.c @@ -162,14 +162,11 @@ min_bufsize_for_extraction(const struct terminal *term) else { size_t cells = 0; - /* First row; start of selection to end of row */ - cells += term->cols - start->col; + /* Add one extra column on each row, for \n */ - /* Full rows; add one extra column for \n */ + cells += term->cols - start->col + 1; cells += (term->cols + 1) * (end->row - start->row - 1); - - /* Final row; start of row to end of selection */ - cells += end->col + 1; + cells += end->col + 1 + 1; return cells; } @@ -184,7 +181,7 @@ min_bufsize_for_extraction(const struct terminal *term) .col = max(start->col, end->col), }; - /* Add one extra column for \n */ + /* Add one extra column on each row, for \n */ int cols = bottom_right.col - top_left.col + 1 + 1; int rows = bottom_right.row - top_left.row + 1; return rows * cols;