mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-15 04:27:52 -05:00
Merge branch 'block-selection'
This commit is contained in:
commit
0b99ed7a55
7 changed files with 273 additions and 133 deletions
4
input.c
4
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:
|
||||
|
|
|
|||
47
render.c
47
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,37 +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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
render_cell(struct terminal *term, pixman_image_t *pix,
|
||||
struct cell *cell, int col, int row, bool has_cursor)
|
||||
|
|
@ -376,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;
|
||||
|
|
@ -416,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);
|
||||
|
|
|
|||
2
search.c
2
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 */
|
||||
|
|
|
|||
342
selection.c
342
selection.c
|
|
@ -45,99 +45,277 @@ selection_on_row_in_view(const struct terminal *term, int row_no)
|
|||
return row_no >= start->row && row_no <= end->row;
|
||||
}
|
||||
|
||||
static char *
|
||||
extract_selection(const struct terminal *term)
|
||||
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;
|
||||
|
||||
assert(start->row <= end->row);
|
||||
int start_row, end_row;
|
||||
int start_col, end_col;
|
||||
|
||||
size_t max_cells = 0;
|
||||
if (start->row == end->row) {
|
||||
assert(start->col <= end->col);
|
||||
max_cells = end->col - start->col + 1;
|
||||
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 {
|
||||
max_cells = term->cols - start->col;
|
||||
max_cells += term->cols * (end->row - start->row - 1);
|
||||
max_cells += end->col + 1;
|
||||
start_row = end_row = start->row;
|
||||
start_col = min(start->col, end->col);
|
||||
end_col = max(start->col, end->col);
|
||||
}
|
||||
|
||||
const size_t buf_size = max_cells * 4 + 1;
|
||||
char *buf = malloc(buf_size);
|
||||
int idx = 0;
|
||||
for (int r = start_row; r <= end_row; r++) {
|
||||
struct row *row = term->grid->rows[r];
|
||||
|
||||
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++)
|
||||
for (int c = start_col;
|
||||
c <= (r == end_row ? end_col : term->cols - 1);
|
||||
c++)
|
||||
{
|
||||
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;
|
||||
cb(term, row, &row->cells[c], data);
|
||||
}
|
||||
|
||||
start_col = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx == 0) {
|
||||
/* Selection of empty cells only */
|
||||
buf[idx] = '\0';
|
||||
return buf;
|
||||
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);
|
||||
|
||||
case SELECTION_NONE:
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(idx > 0);
|
||||
assert(idx < buf_size);
|
||||
if (buf[idx - 1] == '\n')
|
||||
buf[idx - 1] = '\0';
|
||||
else
|
||||
buf[idx] = '\0';
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return buf;
|
||||
static size_t
|
||||
min_bufsize_for_extraction(const struct terminal *term)
|
||||
{
|
||||
const struct coord *start = &term->selection.start;
|
||||
const struct coord *end = &term->selection.end;
|
||||
|
||||
switch (term->selection.kind) {
|
||||
case SELECTION_NONE:
|
||||
return 0;
|
||||
|
||||
case SELECTION_NORMAL:
|
||||
if (term->selection.end.row == -1)
|
||||
return 0;
|
||||
|
||||
assert(term->selection.start.row != -1);
|
||||
|
||||
if (start->row > end->row) {
|
||||
const struct coord *tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
}
|
||||
|
||||
if (start->row == end->row)
|
||||
return end->col - start->col + 1;
|
||||
else {
|
||||
size_t cells = 0;
|
||||
|
||||
/* Add one extra column on each row, for \n */
|
||||
|
||||
cells += term->cols - start->col + 1;
|
||||
cells += (term->cols + 1) * (end->row - start->row - 1);
|
||||
cells += end->col + 1 + 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),
|
||||
};
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct extract {
|
||||
wchar_t *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++] = L'\n';
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
|
||||
if (cell->wc == 0) {
|
||||
ctx->empty_count++;
|
||||
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;
|
||||
}
|
||||
|
||||
static char *
|
||||
extract_selection(const struct terminal *term)
|
||||
{
|
||||
const size_t max_cells = min_bufsize_for_extraction(term);
|
||||
const size_t buf_size = max_cells + 1;
|
||||
|
||||
struct extract ctx = {
|
||||
.buf = malloc(buf_size * sizeof(wchar_t)),
|
||||
.size = buf_size,
|
||||
};
|
||||
|
||||
foreach_selected((struct terminal *)term, &extract_one, &ctx);
|
||||
|
||||
if (ctx.idx == 0) {
|
||||
/* Selection of empty cells only */
|
||||
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';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char *ret = malloc(len + 1);
|
||||
wcstombs(ret, ctx.buf, len + 1);
|
||||
free(ctx.buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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" : "<unknown>",
|
||||
row, col);
|
||||
term->selection.kind = kind;
|
||||
term->selection.start = (struct coord){col, term->grid->view + 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)
|
||||
{
|
||||
|
|
@ -149,24 +327,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);
|
||||
}
|
||||
|
||||
|
|
@ -207,20 +377,14 @@ 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;
|
||||
|
||||
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);
|
||||
|
||||
if (term->selection.start.row != -1 && term->selection.end.row != -1) {
|
||||
foreach_selected(term, &unmark_selected, NULL);
|
||||
render_refresh(term);
|
||||
}
|
||||
|
||||
term->selection.kind = SELECTION_NONE;
|
||||
term->selection.start = (struct coord){-1, -1};
|
||||
term->selection.end = (struct coord){-1, -1};
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -287,7 +451,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 +459,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -157,6 +158,8 @@ enum mouse_reporting {
|
|||
|
||||
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR };
|
||||
|
||||
enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK };
|
||||
|
||||
struct ptmx_buffer {
|
||||
void *data;
|
||||
size_t len;
|
||||
|
|
@ -248,6 +251,7 @@ struct terminal {
|
|||
const char *xcursor;
|
||||
|
||||
struct {
|
||||
enum selection_kind kind;
|
||||
struct coord start;
|
||||
struct coord end;
|
||||
} selection;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue