mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-14 04:27:57 -05:00
commit
3da8dc123f
8 changed files with 179 additions and 106 deletions
|
|
@ -75,6 +75,8 @@
|
|||
* Improved performance on compositors that does not release shm
|
||||
buffers immediately, e.g. KWin
|
||||
(https://codeberg.org/dnkl/foot/issues/478).
|
||||
* `ctrl + w` (_extend-to-word-boundary_) can now be used across lines
|
||||
(https://codeberg.org/dnkl/foot/issues/421).
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
|
@ -153,6 +155,7 @@
|
|||
* PTY not being drained when the client application terminates.
|
||||
* `auto_left_margin` not being limited to `cub1`
|
||||
(https://codeberg.org/dnkl/foot/issues/441).
|
||||
* Crash in scrollback search mode when searching beyond the last output.
|
||||
|
||||
|
||||
### Contributors
|
||||
|
|
|
|||
73
extract.c
73
extract.c
|
|
@ -11,6 +11,7 @@ struct extraction_context {
|
|||
size_t idx;
|
||||
size_t empty_count;
|
||||
size_t newline_count;
|
||||
bool strip_trailing_empty;
|
||||
bool failed;
|
||||
const struct row *last_row;
|
||||
const struct cell *last_cell;
|
||||
|
|
@ -18,7 +19,7 @@ struct extraction_context {
|
|||
};
|
||||
|
||||
struct extraction_context *
|
||||
extract_begin(enum selection_kind kind)
|
||||
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
|
||||
{
|
||||
struct extraction_context *ctx = malloc(sizeof(*ctx));
|
||||
if (unlikely(ctx == NULL)) {
|
||||
|
|
@ -28,6 +29,7 @@ extract_begin(enum selection_kind kind)
|
|||
|
||||
*ctx = (struct extraction_context){
|
||||
.selection_kind = kind,
|
||||
.strip_trailing_empty = strip_trailing_empty,
|
||||
};
|
||||
return ctx;
|
||||
}
|
||||
|
|
@ -51,10 +53,8 @@ ensure_size(struct extraction_context *ctx, size_t additional_chars)
|
|||
}
|
||||
|
||||
bool
|
||||
extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
||||
extract_finish_wide(struct extraction_context *ctx, wchar_t **text, size_t *len)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (text == NULL)
|
||||
return false;
|
||||
|
||||
|
|
@ -63,12 +63,24 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
|||
*len = 0;
|
||||
|
||||
if (ctx->failed)
|
||||
goto out;
|
||||
goto err;
|
||||
|
||||
if (!ctx->strip_trailing_empty) {
|
||||
/* Insert pending newlines, and replace empty cells with spaces */
|
||||
if (!ensure_size(ctx, ctx->newline_count + ctx->empty_count))
|
||||
goto err;
|
||||
|
||||
for (size_t i = 0; i < ctx->newline_count; i++)
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
|
||||
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||
ctx->buf[ctx->idx++] = L' ';
|
||||
}
|
||||
|
||||
if (ctx->idx == 0) {
|
||||
/* Selection of empty cells only */
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto out;
|
||||
goto err;
|
||||
ctx->buf[ctx->idx++] = L'\0';
|
||||
} else {
|
||||
xassert(ctx->idx > 0);
|
||||
|
|
@ -77,12 +89,38 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
|||
ctx->buf[ctx->idx - 1] = L'\0';
|
||||
else {
|
||||
if (!ensure_size(ctx, 1))
|
||||
goto out;
|
||||
goto err;
|
||||
ctx->buf[ctx->idx++] = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
size_t _len = wcstombs(NULL, ctx->buf, 0);
|
||||
*text = ctx->buf;
|
||||
if (len != NULL)
|
||||
*len = ctx->idx - 1;
|
||||
free(ctx);
|
||||
return true;
|
||||
|
||||
err:
|
||||
free(ctx->buf);
|
||||
free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
||||
{
|
||||
if (text == NULL)
|
||||
return false;
|
||||
if (len != NULL)
|
||||
*len = 0;
|
||||
|
||||
wchar_t *wtext;
|
||||
if (!extract_finish_wide(ctx, &wtext, NULL))
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
size_t _len = wcstombs(NULL, wtext, 0);
|
||||
if (_len == (size_t)-1) {
|
||||
LOG_ERRNO("failed to convert selection to UTF-8");
|
||||
goto out;
|
||||
|
|
@ -94,7 +132,7 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
|||
goto out;
|
||||
}
|
||||
|
||||
wcstombs(*text, ctx->buf, _len + 1);
|
||||
wcstombs(*text, wtext, _len + 1);
|
||||
|
||||
if (len != NULL)
|
||||
*len = _len;
|
||||
|
|
@ -102,8 +140,7 @@ extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
|||
ret = true;
|
||||
|
||||
out:
|
||||
free(ctx->buf);
|
||||
free(ctx);
|
||||
free(wtext);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +167,13 @@ extract_one(const struct terminal *term, const struct row *row,
|
|||
/* Don't emit newline just yet - only if there are
|
||||
* non-empty cells following it */
|
||||
ctx->newline_count++;
|
||||
|
||||
if (!ctx->strip_trailing_empty) {
|
||||
if (!ensure_size(ctx, ctx->empty_count))
|
||||
goto err;
|
||||
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||
ctx->buf[ctx->idx++] = L' ';
|
||||
}
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -138,6 +182,13 @@ extract_one(const struct terminal *term, const struct row *row,
|
|||
goto err;
|
||||
|
||||
ctx->buf[ctx->idx++] = L'\n';
|
||||
|
||||
if (!ctx->strip_trailing_empty) {
|
||||
if (!ensure_size(ctx, ctx->empty_count))
|
||||
goto err;
|
||||
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||
ctx->buf[ctx->idx++] = L' ';
|
||||
}
|
||||
ctx->empty_count = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
struct extraction_context;
|
||||
|
||||
struct extraction_context *extract_begin(enum selection_kind kind);
|
||||
struct extraction_context *extract_begin(
|
||||
enum selection_kind kind, bool strip_trailing_empty);
|
||||
|
||||
bool extract_one(
|
||||
const struct terminal *term, const struct row *row, const struct cell *cell,
|
||||
|
|
@ -15,3 +17,5 @@ bool extract_one(
|
|||
|
||||
bool extract_finish(
|
||||
struct extraction_context *context, char **text, size_t *len);
|
||||
bool extract_finish_wide(
|
||||
struct extraction_context *context, wchar_t **text, size_t *len);
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ render_worker_thread(void *_ctx)
|
|||
}
|
||||
|
||||
struct extraction_context *
|
||||
extract_begin(enum selection_kind kind)
|
||||
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
164
search.c
164
search.c
|
|
@ -11,6 +11,7 @@
|
|||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "input.h"
|
||||
#include "misc.h"
|
||||
|
|
@ -77,6 +78,16 @@ search_ensure_size(struct terminal *term, size_t wanted_size)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_wrapped_around(const struct terminal *term, int abs_row_no)
|
||||
{
|
||||
int scrollback_start = term->grid->offset + term->rows;
|
||||
int rebased_row = abs_row_no - scrollback_start + term->grid->num_rows;
|
||||
rebased_row &= term->grid->num_rows - 1;
|
||||
|
||||
return rebased_row == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
search_cancel_keep_selection(struct terminal *term)
|
||||
{
|
||||
|
|
@ -327,13 +338,12 @@ search_find_next(struct terminal *term)
|
|||
|
||||
for (size_t i = 0; i < term->search.len;) {
|
||||
if (end_col >= term->cols) {
|
||||
if (end_row + 1 > grid_row_absolute(term->grid, term->grid->offset + term->rows - 1)) {
|
||||
/* Don't continue past end of the world */
|
||||
break;
|
||||
}
|
||||
|
||||
end_row++;
|
||||
end_row = (end_row + 1) & (term->grid->num_rows - 1);
|
||||
end_col = 0;
|
||||
|
||||
if (has_wrapped_around(term, end_row))
|
||||
break;
|
||||
|
||||
row = term->grid->rows[end_row];
|
||||
}
|
||||
|
||||
|
|
@ -428,91 +438,85 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only)
|
|||
if (term->search.match_len == 0)
|
||||
return;
|
||||
|
||||
xassert(term->search.match.row != -1);
|
||||
xassert(term->search.match.col != -1);
|
||||
xassert(term->selection.end.row != -1);
|
||||
|
||||
int end_row = term->search.match.row;
|
||||
int end_col = term->search.match.col;
|
||||
size_t len = term->search.match_len;
|
||||
const bool move_cursor = term->search.cursor == term->search.len;
|
||||
|
||||
/* Calculate end coord - note: assumed to be valid */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
for (size_t j = 0; j < wcwidth(term->search.buf[i]); j++) {
|
||||
if (++end_col >= term->cols) {
|
||||
end_row = (end_row + 1) & (term->grid->num_rows - 1);
|
||||
end_col = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const struct coord old_end = term->selection.end;
|
||||
struct coord new_end = old_end;
|
||||
struct row *row = NULL;
|
||||
|
||||
tll(wchar_t) new_chars = tll_init();
|
||||
#define newline(coord) __extension__ \
|
||||
({ \
|
||||
bool wrapped_around = false; \
|
||||
if (++(coord).col >= term->cols) { \
|
||||
(coord).row = ((coord).row + 1) & (term->grid->num_rows - 1); \
|
||||
(coord).col = 0; \
|
||||
row = term->grid->rows[(coord).row]; \
|
||||
if (has_wrapped_around(term, (coord.row))) \
|
||||
wrapped_around = true; \
|
||||
} \
|
||||
wrapped_around; \
|
||||
})
|
||||
|
||||
/* Always append at least one character *if* possible */
|
||||
bool first = true;
|
||||
|
||||
for (size_t r = 0;
|
||||
r < term->grid->num_rows;
|
||||
end_row = (end_row + 1) & (term->grid->num_rows - 1), r++)
|
||||
{
|
||||
const struct row *row = term->grid->rows[end_row];
|
||||
if (row == NULL)
|
||||
break;
|
||||
|
||||
bool done = false;
|
||||
for (; end_col < term->cols; end_col++) {
|
||||
wchar_t wc = row->cells[end_col].wc;
|
||||
if (wc >= CELL_SPACER)
|
||||
continue;
|
||||
|
||||
const struct composed *composed = NULL;
|
||||
if (wc >= CELL_COMB_CHARS_LO &&
|
||||
wc < (CELL_COMB_CHARS_LO + term->composed_count))
|
||||
{
|
||||
composed = &term->composed[wc - CELL_COMB_CHARS_LO];
|
||||
wc = composed->base;
|
||||
}
|
||||
|
||||
if (wc == 0 || (!first && !isword(wc, spaces_only, term->conf->word_delimiters))) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
first = false;
|
||||
tll_push_back(new_chars, wc);
|
||||
|
||||
if (composed != NULL) {
|
||||
for (size_t i = 0; i < composed->count; i++)
|
||||
tll_push_back(new_chars, composed->combining[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (done)
|
||||
break;
|
||||
|
||||
end_col = 0;
|
||||
}
|
||||
|
||||
if (tll_length(new_chars) == 0)
|
||||
/* First character to consider is the *next* character */
|
||||
if (newline(new_end))
|
||||
return;
|
||||
|
||||
if (!search_ensure_size(term, term->search.len + tll_length(new_chars)))
|
||||
xassert(term->grid->rows[new_end.row] != NULL);
|
||||
|
||||
/* Find next word boundary */
|
||||
new_end.row -= term->grid->view;
|
||||
selection_find_word_boundary_right(term, &new_end, spaces_only);
|
||||
new_end.row += term->grid->view;
|
||||
|
||||
struct coord pos = old_end;
|
||||
row = term->grid->rows[pos.row];
|
||||
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
|
||||
/* Keep cursor at the end, but don't move it if not */
|
||||
bool move_cursor = term->search.cursor == term->search.len;
|
||||
do {
|
||||
if (newline(pos))
|
||||
break;
|
||||
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
|
||||
break;
|
||||
} while (pos.col != new_end.col || pos.row != new_end.row);
|
||||
|
||||
wchar_t *new_text;
|
||||
size_t new_len;
|
||||
|
||||
if (!extract_finish_wide(ctx, &new_text, &new_len))
|
||||
return;
|
||||
|
||||
if (!search_ensure_size(term, term->search.len + new_len))
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < new_len; i++) {
|
||||
if (new_text[i] == L'\n') {
|
||||
/* extract() adds newlines, which we never match against */
|
||||
continue;
|
||||
}
|
||||
|
||||
term->search.buf[term->search.len++] = new_text[i];
|
||||
}
|
||||
|
||||
/* Append newly found characters to the search buffer */
|
||||
tll_foreach(new_chars, it)
|
||||
term->search.buf[term->search.len++] = it->item;
|
||||
term->search.buf[term->search.len] = L'\0';
|
||||
free(new_text);
|
||||
|
||||
if (move_cursor)
|
||||
term->search.cursor += tll_length(new_chars);
|
||||
|
||||
tll_free(new_chars);
|
||||
term->search.cursor = term->search.len;
|
||||
|
||||
/* search_update_selection() expected end coordinate to be *exclusive* */
|
||||
newline(new_end);
|
||||
search_update_selection(
|
||||
term, term->search.match.row, term->search.match.col, end_row, end_col);
|
||||
term, term->search.match.row, term->search.match.col,
|
||||
new_end.row, new_end.col);
|
||||
|
||||
term->search.match_len = term->search.len;
|
||||
|
||||
#undef newline
|
||||
}
|
||||
|
||||
static size_t
|
||||
|
|
@ -755,12 +759,14 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD:
|
||||
search_match_to_end_of_word(term, false);
|
||||
*update_search_result = *redraw = true;
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD_WS:
|
||||
search_match_to_end_of_word(term, true);
|
||||
*update_search_result = *redraw = true;
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
|
||||
|
|
|
|||
30
selection.c
30
selection.c
|
|
@ -215,7 +215,7 @@ selection_to_text(const struct terminal *term)
|
|||
if (term->selection.end.row == -1)
|
||||
return NULL;
|
||||
|
||||
struct extraction_context *ctx = extract_begin(term->selection.kind);
|
||||
struct extraction_context *ctx = extract_begin(term->selection.kind, true);
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
|
||||
|
|
@ -227,9 +227,9 @@ 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)
|
||||
void
|
||||
selection_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;
|
||||
|
|
@ -299,9 +299,9 @@ find_word_boundary_left(struct terminal *term, struct coord *pos,
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
find_word_boundary_right(struct terminal *term, struct coord *pos,
|
||||
bool spaces_only)
|
||||
void
|
||||
selection_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;
|
||||
|
|
@ -403,8 +403,8 @@ selection_start(struct terminal *term, int col, int row,
|
|||
|
||||
case SELECTION_WORD_WISE: {
|
||||
struct coord start = {col, row}, end = {col, row};
|
||||
find_word_boundary_left(term, &start, spaces_only);
|
||||
find_word_boundary_right(term, &end, spaces_only);
|
||||
selection_find_word_boundary_left(term, &start, spaces_only);
|
||||
selection_find_word_boundary_right(term, &end, spaces_only);
|
||||
|
||||
term->selection.start = (struct coord){
|
||||
start.col, term->grid->view + start.row};
|
||||
|
|
@ -694,14 +694,16 @@ selection_update(struct terminal *term, int col, int row)
|
|||
switch (term->selection.direction) {
|
||||
case SELECTION_LEFT: {
|
||||
struct coord end = {col, row};
|
||||
find_word_boundary_left(term, &end, term->selection.spaces_only);
|
||||
selection_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);
|
||||
selection_find_word_boundary_right(
|
||||
term, &end, term->selection.spaces_only);
|
||||
new_end = (struct coord){end.col, term->grid->view + end.row};
|
||||
break;
|
||||
}
|
||||
|
|
@ -840,8 +842,10 @@ selection_extend_normal(struct terminal *term, int col, int row,
|
|||
struct coord pivot_start = {new_start.col, new_start.row - term->grid->view};
|
||||
struct coord pivot_end = pivot_start;
|
||||
|
||||
find_word_boundary_left(term, &pivot_start, term->selection.spaces_only);
|
||||
find_word_boundary_right(term, &pivot_end, term->selection.spaces_only);
|
||||
selection_find_word_boundary_left(
|
||||
term, &pivot_start, term->selection.spaces_only);
|
||||
selection_find_word_boundary_right(
|
||||
term, &pivot_end, term->selection.spaces_only);
|
||||
|
||||
term->selection.pivot.start =
|
||||
(struct coord){pivot_start.col, term->grid->view + pivot_start.row};
|
||||
|
|
|
|||
|
|
@ -74,3 +74,8 @@ void selection_start_scroll_timer(
|
|||
struct terminal *term, int interval_ns,
|
||||
enum selection_scroll_direction direction, int col);
|
||||
void selection_stop_scroll_timer(struct terminal *term);
|
||||
|
||||
void selection_find_word_boundary_left(
|
||||
struct terminal *term, struct coord *pos, bool spaces_only);
|
||||
void selection_find_word_boundary_right(
|
||||
struct terminal *term, struct coord *pos, bool spaces_only);
|
||||
|
|
|
|||
|
|
@ -2900,7 +2900,7 @@ static bool
|
|||
rows_to_text(const struct terminal *term, int start, int end,
|
||||
char **text, size_t *len)
|
||||
{
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE);
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE, true);
|
||||
if (ctx == NULL)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue