mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-13 05:33:51 -04:00
selection: refactor: break out text extraction to a separate file
This commit is contained in:
parent
85c50099af
commit
aafa120f92
4 changed files with 203 additions and 170 deletions
163
extract.c
Normal file
163
extract.c
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
#include "extract.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "extract"
|
||||||
|
#define LOG_ENABLE_DBG 1
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
struct extraction_context {
|
||||||
|
wchar_t *buf;
|
||||||
|
size_t size;
|
||||||
|
size_t idx;
|
||||||
|
size_t empty_count;
|
||||||
|
const struct row *last_row;
|
||||||
|
const struct cell *last_cell;
|
||||||
|
enum selection_kind selection_kind;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct extraction_context *
|
||||||
|
extract_begin(enum selection_kind kind)
|
||||||
|
{
|
||||||
|
struct extraction_context *ctx = malloc(sizeof(*ctx));
|
||||||
|
*ctx = (struct extraction_context){
|
||||||
|
.selection_kind = kind,
|
||||||
|
};
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extract_finish(struct extraction_context *ctx, char **text, size_t *len)
|
||||||
|
{
|
||||||
|
if (text == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
size_t _len = wcstombs(NULL, ctx->buf, 0);
|
||||||
|
if (_len == (size_t)-1) {
|
||||||
|
LOG_ERRNO("failed to convert selection to UTF-8");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
*text = malloc(_len + 1);
|
||||||
|
wcstombs(*text, ctx->buf, _len + 1);
|
||||||
|
|
||||||
|
if (len != NULL)
|
||||||
|
*len = _len;
|
||||||
|
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(ctx->buf);
|
||||||
|
free(ctx);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
ensure_size(struct extraction_context *ctx, size_t additional_chars)
|
||||||
|
{
|
||||||
|
while (ctx->size < ctx->idx + additional_chars) {
|
||||||
|
size_t new_size = ctx->size == 0 ? 512 : ctx->size * 2;
|
||||||
|
wchar_t *new_buf = realloc(ctx->buf, new_size * sizeof(wchar_t));
|
||||||
|
|
||||||
|
if (new_buf == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx->buf = new_buf;
|
||||||
|
ctx->size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(ctx->size >= ctx->idx + additional_chars);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
extract_one(struct terminal *term, struct row *row, struct cell *cell,
|
||||||
|
int col, void *context)
|
||||||
|
{
|
||||||
|
struct extraction_context *ctx = context;
|
||||||
|
|
||||||
|
if (cell->wc == CELL_MULT_COL_SPACER)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ctx->last_row != NULL && row != ctx->last_row) {
|
||||||
|
/* New row - determine if we should insert a newline or not */
|
||||||
|
|
||||||
|
if (ctx->selection_kind == SELECTION_NONE ||
|
||||||
|
ctx->selection_kind == SELECTION_NORMAL)
|
||||||
|
{
|
||||||
|
if (ctx->last_row->linebreak ||
|
||||||
|
ctx->empty_count > 0 ||
|
||||||
|
cell->wc == 0)
|
||||||
|
{
|
||||||
|
/* Row has a hard linebreak, or either last cell or
|
||||||
|
* current cell is empty */
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx->buf[ctx->idx++] = L'\n';
|
||||||
|
ctx->empty_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (ctx->selection_kind == SELECTION_BLOCK) {
|
||||||
|
/* Always insert a linebreak */
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replace empty cells with spaces when followed by non-empty cell */
|
||||||
|
if (!ensure_size(ctx, ctx->empty_count))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ctx->empty_count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = L' ';
|
||||||
|
ctx->empty_count = 0;
|
||||||
|
|
||||||
|
if (cell->wc >= CELL_COMB_CHARS_LO &&
|
||||||
|
cell->wc < (CELL_COMB_CHARS_LO + term->composed_count))
|
||||||
|
{
|
||||||
|
const struct composed *composed
|
||||||
|
= &term->composed[cell->wc - CELL_COMB_CHARS_LO];
|
||||||
|
|
||||||
|
if (!ensure_size(ctx, 1 + composed->count))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx->buf[ctx->idx++] = composed->base;
|
||||||
|
for (size_t i = 0; i < composed->count; i++)
|
||||||
|
ctx->buf[ctx->idx++] = composed->combining[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (!ensure_size(ctx, 1))
|
||||||
|
return false;
|
||||||
|
ctx->buf[ctx->idx++] = cell->wc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->last_row = row;
|
||||||
|
ctx->last_cell = cell;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
17
extract.h
Normal file
17
extract.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
|
struct extraction_context;
|
||||||
|
|
||||||
|
struct extraction_context *extract_begin(enum selection_kind kind);
|
||||||
|
|
||||||
|
bool extract_one(
|
||||||
|
struct terminal *term, struct row *row, struct cell *cell, int col,
|
||||||
|
void *context);
|
||||||
|
|
||||||
|
bool extract_finish(
|
||||||
|
struct extraction_context *context, char **text, size_t *len);
|
||||||
|
|
@ -105,6 +105,7 @@ executable(
|
||||||
'commands.c', 'commands.h',
|
'commands.c', 'commands.h',
|
||||||
'csi.c', 'csi.h',
|
'csi.c', 'csi.h',
|
||||||
'dcs.c', 'dcs.h',
|
'dcs.c', 'dcs.h',
|
||||||
|
'extract.c', 'extract.h',
|
||||||
'fdm.c', 'fdm.h',
|
'fdm.c', 'fdm.h',
|
||||||
'grid.c', 'grid.h',
|
'grid.c', 'grid.h',
|
||||||
'input.c', 'input.h',
|
'input.c', 'input.h',
|
||||||
|
|
|
||||||
192
selection.c
192
selection.c
|
|
@ -14,6 +14,7 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#include "async.h"
|
#include "async.h"
|
||||||
|
#include "extract.h"
|
||||||
#include "grid.h"
|
#include "grid.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
@ -100,7 +101,7 @@ selection_view_down(struct terminal *term, int new_view)
|
||||||
static void
|
static void
|
||||||
foreach_selected_normal(
|
foreach_selected_normal(
|
||||||
struct terminal *term, struct coord _start, struct coord _end,
|
struct terminal *term, struct coord _start, struct coord _end,
|
||||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
const struct coord *start = &_start;
|
const struct coord *start = &_start;
|
||||||
|
|
@ -134,7 +135,8 @@ foreach_selected_normal(
|
||||||
c <= (r == end_row ? end_col : term->cols - 1);
|
c <= (r == end_row ? end_col : term->cols - 1);
|
||||||
c++)
|
c++)
|
||||||
{
|
{
|
||||||
cb(term, row, &row->cells[c], c, data);
|
if (!cb(term, row, &row->cells[c], c, data))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
start_col = 0;
|
start_col = 0;
|
||||||
|
|
@ -144,7 +146,7 @@ foreach_selected_normal(
|
||||||
static void
|
static void
|
||||||
foreach_selected_block(
|
foreach_selected_block(
|
||||||
struct terminal *term, struct coord _start, struct coord _end,
|
struct terminal *term, struct coord _start, struct coord _end,
|
||||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
const struct coord *start = &_start;
|
const struct coord *start = &_start;
|
||||||
|
|
@ -166,7 +168,8 @@ foreach_selected_block(
|
||||||
assert(row != NULL);
|
assert(row != NULL);
|
||||||
|
|
||||||
for (int c = top_left.col; c <= bottom_right.col; c++) {
|
for (int c = top_left.col; c <= bottom_right.col; c++) {
|
||||||
cb(term, row, &row->cells[c], c, data);
|
if (!cb(term, row, &row->cells[c], c, data))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +177,7 @@ foreach_selected_block(
|
||||||
static void
|
static void
|
||||||
foreach_selected(
|
foreach_selected(
|
||||||
struct terminal *term, struct coord start, struct coord end,
|
struct terminal *term, struct coord start, struct coord end,
|
||||||
void (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
bool (*cb)(struct terminal *term, struct row *row, struct cell *cell, int col, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
switch (term->selection.kind) {
|
switch (term->selection.kind) {
|
||||||
|
|
@ -192,173 +195,19 @@ foreach_selected(
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
const size_t chars_per_cell = 1 + ALEN(term->composed[0].combining);
|
|
||||||
|
|
||||||
switch (term->selection.kind) {
|
|
||||||
case SELECTION_NONE:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case SELECTION_NORMAL:
|
|
||||||
if (term->selection.end.row < 0)
|
|
||||||
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) * chars_per_cell;
|
|
||||||
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 * chars_per_cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 * chars_per_cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct extract {
|
|
||||||
wchar_t *buf;
|
|
||||||
size_t size;
|
|
||||||
size_t idx;
|
|
||||||
size_t empty_count;
|
|
||||||
const struct row *last_row;
|
|
||||||
const struct cell *last_cell;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
extract_one(struct terminal *term, struct row *row, struct cell *cell,
|
|
||||||
int col, void *data)
|
|
||||||
{
|
|
||||||
struct extract *ctx = data;
|
|
||||||
|
|
||||||
if (cell->wc == CELL_MULT_COL_SPACER)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (ctx->last_row != NULL && row != ctx->last_row) {
|
|
||||||
/* New row - determine if we should insert a newline or not */
|
|
||||||
|
|
||||||
if (term->selection.kind == SELECTION_NORMAL) {
|
|
||||||
if (ctx->last_row->linebreak ||
|
|
||||||
ctx->empty_count > 0 ||
|
|
||||||
cell->wc == 0)
|
|
||||||
{
|
|
||||||
/* Row has a hard linebreak, or either last cell or
|
|
||||||
* current cell is empty */
|
|
||||||
ctx->buf[ctx->idx++] = L'\n';
|
|
||||||
ctx->empty_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (term->selection.kind == SELECTION_BLOCK) {
|
|
||||||
/* Always insert a linebreak */
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (cell->wc >= CELL_COMB_CHARS_LO &&
|
|
||||||
cell->wc < (CELL_COMB_CHARS_LO + term->composed_count)) {
|
|
||||||
const struct composed *composed = &term->composed[cell->wc - CELL_COMB_CHARS_LO];
|
|
||||||
|
|
||||||
ctx->buf[ctx->idx++] = composed->base;
|
|
||||||
|
|
||||||
assert(ctx->idx + composed->count <= ctx->size);
|
|
||||||
for (size_t i = 0; i < composed->count; i++)
|
|
||||||
ctx->buf[ctx->idx++] = composed->combining[i];
|
|
||||||
} else
|
|
||||||
ctx->buf[ctx->idx++] = cell->wc;
|
|
||||||
|
|
||||||
ctx->last_row = row;
|
|
||||||
ctx->last_cell = cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
extract_selection(const struct terminal *term)
|
extract_selection(const struct terminal *term)
|
||||||
{
|
{
|
||||||
const size_t max_cells = min_bufsize_for_extraction(term);
|
struct extraction_context *ctx = extract_begin(term->selection.kind);
|
||||||
const size_t buf_size = max_cells + 1;
|
if (ctx == NULL)
|
||||||
|
return NULL;
|
||||||
struct extract ctx = {
|
|
||||||
.buf = malloc(buf_size * sizeof(wchar_t)),
|
|
||||||
.size = buf_size,
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach_selected(
|
foreach_selected(
|
||||||
(struct terminal *)term, term->selection.start, term->selection.end,
|
(struct terminal *)term, term->selection.start, term->selection.end,
|
||||||
&extract_one, &ctx);
|
&extract_one, ctx);
|
||||||
|
|
||||||
if (ctx.idx == 0) {
|
char *text;
|
||||||
/* Selection of empty cells only */
|
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
||||||
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
|
void
|
||||||
|
|
@ -377,41 +226,44 @@ selection_start(struct terminal *term, int col, int row,
|
||||||
term->selection.end = (struct coord){-1, -1};
|
term->selection.end = (struct coord){-1, -1};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||||
int col, void *data)
|
int col, void *data)
|
||||||
{
|
{
|
||||||
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
|
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
|
||||||
/* Ignore if already deselected, or if premarked for updated selection */
|
/* Ignore if already deselected, or if premarked for updated selection */
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
row->dirty = true;
|
row->dirty = true;
|
||||||
cell->attrs.selected = 0;
|
cell->attrs.selected = 0;
|
||||||
cell->attrs.clean = 0;
|
cell->attrs.clean = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||||
int col, void *data)
|
int col, void *data)
|
||||||
{
|
{
|
||||||
/* Tell unmark to leave this be */
|
/* Tell unmark to leave this be */
|
||||||
cell->attrs.selected |= 2;
|
cell->attrs.selected |= 2;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||||
int col, void *data)
|
int col, void *data)
|
||||||
{
|
{
|
||||||
if (cell->attrs.selected & 1) {
|
if (cell->attrs.selected & 1) {
|
||||||
cell->attrs.selected = 1; /* Clear the pre-mark bit */
|
cell->attrs.selected = 1; /* Clear the pre-mark bit */
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
row->dirty = true;
|
row->dirty = true;
|
||||||
cell->attrs.selected = 1;
|
cell->attrs.selected = 1;
|
||||||
cell->attrs.clean = 0;
|
cell->attrs.clean = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue