Merge branch 'extend-selection-by-word'

Closes #267
This commit is contained in:
Daniel Eklöf 2021-01-04 20:27:57 +01:00
commit fcca3d3e55
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
7 changed files with 338 additions and 125 deletions

View file

@ -32,6 +32,8 @@
font glyphs (https://codeberg.org/dnkl/foot/issues/198)
* Trailing comments in `foot.ini` must now be preceded by a space or tab
(https://codeberg.org/dnkl/foot/issues/270)
* Double- or triple clicking then dragging now extends the selection
word- or line-wise (https://codeberg.org/dnkl/foot/issues/267).
### Deprecated

View file

@ -69,14 +69,17 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len)
/* Selection of empty cells only */
if (!ensure_size(ctx, 1))
goto out;
ctx->buf[ctx->idx] = L'\0';
ctx->buf[ctx->idx++] = L'\0';
} else {
assert(ctx->idx > 0);
assert(ctx->idx < ctx->size);
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';
else {
if (!ensure_size(ctx, 1))
goto out;
ctx->buf[ctx->idx++] = L'\0';
}
}
size_t _len = wcstombs(NULL, ctx->buf, 0);

19
input.c
View file

@ -275,7 +275,8 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SELECT_BEGIN:
if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_start(
term, seat->mouse.col, seat->mouse.row, SELECTION_NORMAL);
term, seat->mouse.col, seat->mouse.row,
SELECTION_NORMAL, SELECTION_SEMANTIC_NONE, false);
return true;
}
return false;
@ -283,7 +284,8 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SELECT_BEGIN_BLOCK:
if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_start(
term, seat->mouse.col, seat->mouse.row, SELECTION_BLOCK);
term, seat->mouse.col, seat->mouse.row,
SELECTION_BLOCK, SELECTION_SEMANTIC_NONE, false);
return true;
}
return false;
@ -298,23 +300,26 @@ execute_binding(struct seat *seat, struct terminal *term,
case BIND_ACTION_SELECT_WORD:
if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_mark_word(
seat, term, seat->mouse.col, seat->mouse.row, false, serial);
selection_start(
term, seat->mouse.col, seat->mouse.row,
SELECTION_NORMAL, SELECTION_SEMANTIC_WORD, false);
return true;
}
return false;
case BIND_ACTION_SELECT_WORD_WS:
if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_mark_word(
seat, term, seat->mouse.col, seat->mouse.row, true, serial);
selection_start(
term, seat->mouse.col, seat->mouse.row,
SELECTION_NORMAL, SELECTION_SEMANTIC_WORD, true);
return true;
}
return false;
case BIND_ACTION_SELECT_ROW:
if (selection_enabled(term, seat) && cursor_is_on_grid) {
selection_mark_row(seat, term, seat->mouse.row, serial);
selection_start(term, seat->mouse.col, seat->mouse.row,
SELECTION_NORMAL, SELECTION_SEMANTIC_ROW, false);
return true;
}
return false;

View file

@ -222,7 +222,8 @@ search_update_selection(struct terminal *term,
assert(selection_row >= 0 &&
selection_row < term->grid->num_rows);
selection_start(term, start_col, selection_row, SELECTION_NORMAL);
selection_start(term, start_col, selection_row,
SELECTION_NORMAL, SELECTION_SEMANTIC_NONE, false);
}
/* Update selection endpoint */

View file

@ -236,9 +236,157 @@ selection_to_text(const struct terminal *term)
return extract_finish(ctx, &text, NULL) ? text : NULL;
}
static void
find_word_boundary_left(struct terminal *term, struct coord *pos,
bool spaces_only)
{
const struct row *r = grid_row_in_view(term->grid, pos->row);
wchar_t c = r->cells[pos->col].wc;
while (c == CELL_MULT_COL_SPACER) {
assert(pos->col > 0);
if (pos->col == 0)
return;
pos->col--;
c = r->cells[pos->col].wc;
}
if (c >= CELL_COMB_CHARS_LO &&
c < (CELL_COMB_CHARS_LO + term->composed_count))
{
c = term->composed[c - CELL_COMB_CHARS_LO].base;
}
bool initial_is_space = c == 0 || iswspace(c);
bool initial_is_delim =
!initial_is_space && !isword(c, spaces_only, term->conf->word_delimiters);
bool initial_is_word =
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
while (true) {
int next_col = pos->col - 1;
int next_row = pos->row;
/* Linewrap */
if (next_col < 0) {
next_col = term->cols - 1;
if (--next_row < 0)
break;
}
const struct row *row = grid_row_in_view(term->grid, next_row);
c = row->cells[next_col].wc;
while (c == CELL_MULT_COL_SPACER) {
assert(next_col > 0);
if (--next_col < 0)
return;
c = row->cells[next_col].wc;
}
if (c >= CELL_COMB_CHARS_LO &&
c < (CELL_COMB_CHARS_LO + term->composed_count))
{
c = term->composed[c - CELL_COMB_CHARS_LO].base;
}
bool is_space = c == 0 || iswspace(c);
bool is_delim =
!is_space && !isword(c, spaces_only, term->conf->word_delimiters);
bool is_word =
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
if (initial_is_space && !is_space)
break;
if (initial_is_delim && !is_delim)
break;
if (initial_is_word && !is_word)
break;
pos->col = next_col;
pos->row = next_row;
}
}
static void
find_word_boundary_right(struct terminal *term, struct coord *pos,
bool spaces_only)
{
const struct row *r = grid_row_in_view(term->grid, pos->row);
wchar_t c = r->cells[pos->col].wc;
while (c == CELL_MULT_COL_SPACER) {
assert(pos->col > 0);
if (pos->col == 0)
return;
pos->col--;
c = r->cells[pos->col].wc;
}
if (c >= CELL_COMB_CHARS_LO &&
c < (CELL_COMB_CHARS_LO + term->composed_count))
{
c = term->composed[c - CELL_COMB_CHARS_LO].base;
}
bool initial_is_space = c == 0 || iswspace(c);
bool initial_is_delim =
!initial_is_space && !isword(c, spaces_only, term->conf->word_delimiters);
bool initial_is_word =
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
while (true) {
int next_col = pos->col + 1;
int next_row = pos->row;
/* Linewrap */
if (next_col >= term->cols) {
next_col = 0;
if (++next_row >= term->rows)
break;
}
const struct row *row = grid_row_in_view(term->grid, next_row);
c = row->cells[next_col].wc;
while (c == CELL_MULT_COL_SPACER) {
if (++next_col >= term->cols) {
next_col = 0;
if (++next_row >= term->rows)
return;
}
c = row->cells[next_col].wc;
}
if (c >= CELL_COMB_CHARS_LO &&
c < (CELL_COMB_CHARS_LO + term->composed_count))
{
c = term->composed[c - CELL_COMB_CHARS_LO].base;
}
bool is_space = c == 0 || iswspace(c);
bool is_delim =
!is_space && !isword(c, spaces_only, term->conf->word_delimiters);
bool is_word =
c != 0 && isword(c, spaces_only, term->conf->word_delimiters);
if (initial_is_space && !is_space)
break;
if (initial_is_delim && !is_delim)
break;
if (initial_is_word && !is_word)
break;
pos->col = next_col;
pos->row = next_row;
}
}
void
selection_start(struct terminal *term, int col, int row,
enum selection_kind kind)
enum selection_kind kind,
enum selection_semantic semantic,
bool spaces_only)
{
selection_cancel(term);
@ -248,9 +396,43 @@ selection_start(struct terminal *term, int col, int row,
row, col);
term->selection.kind = kind;
term->selection.start = (struct coord){col, term->grid->view + row};
term->selection.end = (struct coord){-1, -1};
term->selection.semantic = semantic;
term->selection.ongoing = true;
term->selection.spaces_only = spaces_only;
switch (semantic) {
case SELECTION_SEMANTIC_NONE:
term->selection.start = (struct coord){col, term->grid->view + row};
term->selection.end = (struct coord){-1, -1};
term->selection.pivot.start = term->selection.start;
term->selection.pivot.end = term->selection.end;
break;
case SELECTION_SEMANTIC_WORD: {
struct coord start = {col, row}, end = {col, row};
find_word_boundary_left(term, &start, spaces_only);
find_word_boundary_right(term, &end, spaces_only);
term->selection.start = (struct coord){
start.col, term->grid->view + start.row};
term->selection.pivot.start = term->selection.start;
term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row};
selection_update(term, end.col, end.row);
break;
}
case SELECTION_SEMANTIC_ROW:
term->selection.start = (struct coord){0, term->grid->view + row};
term->selection.pivot.start = term->selection.start;
term->selection.pivot.end = (struct coord){term->cols - 1, term->grid->view + row};
selection_update(term, term->cols - 1, row);
break;
}
}
/* Context used while (un)marking selected cells, to be able to
@ -379,61 +561,150 @@ selection_update(struct terminal *term, int col, int row)
struct coord new_start = term->selection.start;
struct coord new_end = {col, term->grid->view + row};
size_t start_row_idx = new_start.row & (term->grid->num_rows - 1);
size_t end_row_idx = new_end.row & (term->grid->num_rows - 1);
const struct row *row_start = term->grid->rows[start_row_idx];
const struct row *row_end = term->grid->rows[end_row_idx];
/* Adjust start point if the selection has changed 'direction' */
if (!(new_end.row == new_start.row && new_end.col == new_start.col)) {
enum selection_direction new_direction;
enum selection_direction new_direction = term->selection.direction;
if (new_end.row > new_start.row ||
(new_end.row == new_start.row && new_end.col > new_start.col))
struct coord *pivot_start = &term->selection.pivot.start;
struct coord *pivot_end = &term->selection.pivot.end;
if (new_end.row < pivot_start->row ||
(new_end.row == pivot_start->row && new_end.col < pivot_start->col))
{
/* New end point is after the start point */
new_direction = SELECTION_RIGHT;
} else {
/* The new end point is before the start point */
/* New end point is before the start point */
new_direction = SELECTION_LEFT;
} else {
/* The new end point is after the start point */
new_direction = SELECTION_RIGHT;
}
if (term->selection.direction != new_direction) {
if (term->selection.direction != SELECTION_UNDIR) {
if (new_direction == SELECTION_LEFT) {
if (term->selection.direction == SELECTION_UNDIR &&
pivot_end->row < 0)
{
/* First, make sure start isnt in the middle of a
* multi-column character */
while (true) {
const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)];
const struct cell *cell = &row->cells[pivot_start->col];
if (cell->wc != CELL_MULT_COL_SPACER)
break;
/* Multi-column chars dont cross rows */
assert(pivot_start->col > 0);
if (pivot_start->col == 0)
break;
pivot_start->col--;
}
/*
* Setup pivot end to be one character *before* start
* Which one we move, the end or start point, depends
* on the initial selection direction.
*/
*pivot_end = *pivot_start;
if (new_direction == SELECTION_RIGHT) {
bool keep_going = true;
while (keep_going) {
const wchar_t wc = row_start->cells[new_start.col].wc;
const struct row *row = term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)];
const wchar_t wc = row->cells[pivot_end->col].wc;
keep_going = wc == CELL_MULT_COL_SPACER;
new_start.col--;
if (new_start.col < 0) {
new_start.col = term->cols - 1;
new_start.row--;
}
if (pivot_end->col == 0) {
if (pivot_end->row - term->grid->view <= 0)
break;
pivot_end->col = term->cols - 1;
pivot_end->row--;
} else
pivot_end->col--;
}
} else {
bool keep_going = true;
while (keep_going) {
const wchar_t wc = new_start.col < term->cols - 1
? row_start->cells[new_start.col + 1].wc
: 0;
const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)];
const wchar_t wc = pivot_start->col < term->cols - 1
? row->cells[pivot_start->col + 1].wc : 0;
keep_going = wc == CELL_MULT_COL_SPACER;
new_start.col++;
if (new_start.col >= term->cols) {
new_start.col = 0;
new_start.row++;
}
if (pivot_start->col >= term->cols - 1) {
if (pivot_start->row - term->grid->view >= term->rows - 1)
break;
pivot_start->col = 0;
pivot_start->row++;
} else
pivot_start->col++;
}
}
assert(term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)]->
cells[pivot_start->col].wc != CELL_MULT_COL_SPACER);
assert(term->grid->rows[pivot_end->row & (term->grid->num_rows - 1)]->
cells[pivot_end->col].wc != CELL_MULT_COL_SPACER);
}
if (new_direction == SELECTION_LEFT) {
assert(pivot_end->row >= 0);
new_start = *pivot_end;
} else
new_start = *pivot_start;
term->selection.direction = new_direction;
}
}
switch (term->selection.semantic) {
case SELECTION_SEMANTIC_NONE:
break;
case SELECTION_SEMANTIC_WORD:
switch (term->selection.direction) {
case SELECTION_LEFT: {
struct coord end = {col, row};
find_word_boundary_left(term, &end, term->selection.spaces_only);
new_end = (struct coord){end.col, term->grid->view + end.row};
break;
}
case SELECTION_RIGHT: {
struct coord end = {col, row};
find_word_boundary_right(term, &end, term->selection.spaces_only);
new_end = (struct coord){end.col, term->grid->view + end.row};
break;
}
case SELECTION_UNDIR:
break;
}
break;
case SELECTION_SEMANTIC_ROW:
switch (term->selection.direction) {
case SELECTION_LEFT:
new_end.col = 0;
break;
case SELECTION_RIGHT:
new_end.col = term->cols - 1;
break;
case SELECTION_UNDIR:
break;
}
break;
}
size_t start_row_idx = new_start.row & (term->grid->num_rows - 1);
size_t end_row_idx = new_end.row & (term->grid->num_rows - 1);
const struct row *row_start = term->grid->rows[start_row_idx];
const struct row *row_end = term->grid->rows[end_row_idx];
/* If an end point is in the middle of a multi-column character,
* expand the selection to cover the entire character */
if (new_start.row < new_end.row ||
@ -677,6 +948,8 @@ selection_cancel(struct terminal *term)
term->selection.kind = SELECTION_NONE;
term->selection.start = (struct coord){-1, -1};
term->selection.end = (struct coord){-1, -1};
term->selection.pivot.start = (struct coord){-1, -1};
term->selection.pivot.end = (struct coord){-1, -1};
term->selection.direction = SELECTION_UNDIR;
term->selection.ongoing = false;
}
@ -733,81 +1006,6 @@ selection_primary_unset(struct seat *seat)
primary->text = NULL;
}
void
selection_mark_word(struct seat *seat, struct terminal *term, int col, int row,
bool spaces_only, uint32_t serial)
{
selection_cancel(term);
struct coord start = {col, row};
struct coord end = {col, row};
const struct row *r = grid_row_in_view(term->grid, start.row);
wchar_t c = r->cells[start.col].wc;
if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) {
while (true) {
int next_col = start.col - 1;
int next_row = start.row;
/* Linewrap */
if (next_col < 0) {
next_col = term->cols - 1;
if (--next_row < 0)
break;
}
const struct row *row = grid_row_in_view(term->grid, next_row);
c = row->cells[next_col].wc;
if (c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))
break;
start.col = next_col;
start.row = next_row;
}
}
r = grid_row_in_view(term->grid, end.row);
c = r->cells[end.col].wc;
if (!(c == 0 || !isword(c, spaces_only, term->conf->word_delimiters))) {
while (true) {
int next_col = end.col + 1;
int next_row = end.row;
/* Linewrap */
if (next_col >= term->cols) {
next_col = 0;
if (++next_row >= term->rows)
break;
}
const struct row *row = grid_row_in_view(term->grid, next_row);
c = row->cells[next_col].wc;
if (c == '\0' || !isword(c, spaces_only, term->conf->word_delimiters))
break;
end.col = next_col;
end.row = next_row;
}
}
selection_start(term, start.col, start.row, SELECTION_NORMAL);
selection_update(term, end.col, end.row);
selection_finalize(seat, term, serial);
}
void
selection_mark_row(
struct seat *seat, struct terminal *term, int row, uint32_t serial)
{
selection_start(term, 0, row, SELECTION_NORMAL);
selection_update(term, term->cols - 1, row);
selection_finalize(seat, term, serial);
}
static bool
fdm_scroll_timer(struct fdm *fdm, int fd, int events, void *data)
{

View file

@ -10,7 +10,9 @@ extern const struct zwp_primary_selection_device_v1_listener primary_selection_d
bool selection_enabled(const struct terminal *term, struct seat *seat);
void selection_start(
struct terminal *term, int col, int row, enum selection_kind kind);
struct terminal *term, int col, int row,
enum selection_kind kind, enum selection_semantic semantic,
bool spaces_only);
void selection_update(struct terminal *term, int col, int row);
void selection_finalize(
struct seat *seat, struct terminal *term, uint32_t serial);
@ -24,12 +26,6 @@ bool selection_on_rows(const struct terminal *term, int start, int end);
void selection_view_up(struct terminal *term, int new_view);
void selection_view_down(struct terminal *term, int new_view);
void selection_mark_word(
struct seat *seat, struct terminal *term, int col, int row,
bool spaces_only, uint32_t serial);
void selection_mark_row(
struct seat *seat, struct terminal *term, int row, uint32_t serial);
void selection_clipboard_unset(struct seat *seat);
void selection_primary_unset(struct seat *seat);

View file

@ -181,6 +181,7 @@ enum mouse_reporting {
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR };
enum selection_kind { SELECTION_NONE, SELECTION_NORMAL, SELECTION_BLOCK };
enum selection_semantic { SELECTION_SEMANTIC_NONE, SELECTION_SEMANTIC_WORD, SELECTION_SEMANTIC_ROW};
enum selection_direction {SELECTION_UNDIR, SELECTION_LEFT, SELECTION_RIGHT};
enum selection_scroll_direction {SELECTION_SCROLL_NOT, SELECTION_SCROLL_UP, SELECTION_SCROLL_DOWN};
@ -359,10 +360,17 @@ struct terminal {
struct {
enum selection_kind kind;
enum selection_semantic semantic;
enum selection_direction direction;
struct coord start;
struct coord end;
bool ongoing;
bool spaces_only; /* SELECTION_SEMANTIC_WORD */
struct {
struct coord start;
struct coord end;
} pivot;
struct {
int fd;