mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05: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',
|
||||
'csi.c', 'csi.h',
|
||||
'dcs.c', 'dcs.h',
|
||||
'extract.c', 'extract.h',
|
||||
'fdm.c', 'fdm.h',
|
||||
'grid.c', 'grid.h',
|
||||
'input.c', 'input.h',
|
||||
|
|
|
|||
192
selection.c
192
selection.c
|
|
@ -14,6 +14,7 @@
|
|||
#include "log.h"
|
||||
|
||||
#include "async.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "misc.h"
|
||||
#include "render.h"
|
||||
|
|
@ -100,7 +101,7 @@ selection_view_down(struct terminal *term, int new_view)
|
|||
static void
|
||||
foreach_selected_normal(
|
||||
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)
|
||||
{
|
||||
const struct coord *start = &_start;
|
||||
|
|
@ -134,7 +135,8 @@ foreach_selected_normal(
|
|||
c <= (r == end_row ? end_col : term->cols - 1);
|
||||
c++)
|
||||
{
|
||||
cb(term, row, &row->cells[c], c, data);
|
||||
if (!cb(term, row, &row->cells[c], c, data))
|
||||
return;
|
||||
}
|
||||
|
||||
start_col = 0;
|
||||
|
|
@ -144,7 +146,7 @@ foreach_selected_normal(
|
|||
static void
|
||||
foreach_selected_block(
|
||||
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)
|
||||
{
|
||||
const struct coord *start = &_start;
|
||||
|
|
@ -166,7 +168,8 @@ foreach_selected_block(
|
|||
assert(row != NULL);
|
||||
|
||||
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
|
||||
foreach_selected(
|
||||
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)
|
||||
{
|
||||
switch (term->selection.kind) {
|
||||
|
|
@ -192,173 +195,19 @@ foreach_selected(
|
|||
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 *
|
||||
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,
|
||||
};
|
||||
struct extraction_context *ctx = extract_begin(term->selection.kind);
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
|
||||
foreach_selected(
|
||||
(struct terminal *)term, term->selection.start, term->selection.end,
|
||||
&extract_one, &ctx);
|
||||
&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;
|
||||
char *text;
|
||||
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -377,41 +226,44 @@ selection_start(struct terminal *term, int col, int row,
|
|||
term->selection.end = (struct coord){-1, -1};
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
unmark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
if (cell->attrs.selected == 0 || (cell->attrs.selected & 2)) {
|
||||
/* Ignore if already deselected, or if premarked for updated selection */
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
row->dirty = true;
|
||||
cell->attrs.selected = 0;
|
||||
cell->attrs.clean = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
static bool
|
||||
premark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
/* Tell unmark to leave this be */
|
||||
cell->attrs.selected |= 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
mark_selected(struct terminal *term, struct row *row, struct cell *cell,
|
||||
int col, void *data)
|
||||
{
|
||||
if (cell->attrs.selected & 1) {
|
||||
cell->attrs.selected = 1; /* Clear the pre-mark bit */
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
row->dirty = true;
|
||||
cell->attrs.selected = 1;
|
||||
cell->attrs.clean = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue