mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
The grid is now represented with an array of row *pointers*. Each row contains an array of cells (the row's columns). The main point of having row pointers is we can now move rows around almost for free. This is useful when scrolling with scroll margins for example, where we previously had to copy the lines in the margins. Now it's just a matter of swapping two pointers.
637 lines
17 KiB
C
637 lines
17 KiB
C
#include "terminal.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#define LOG_MODULE "terminal"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "log.h"
|
|
#include "grid.h"
|
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
#define max(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
#if 0
|
|
static bool
|
|
damage_merge_range(struct terminal *term, const struct damage *dmg)
|
|
{
|
|
if (tll_length(term->grid->damage) == 0)
|
|
return false;
|
|
|
|
struct damage *old = &tll_back(term->grid->damage);
|
|
if (old->type != dmg->type)
|
|
return false;
|
|
|
|
const int start = dmg->range.start;
|
|
const int end = start + dmg->range.length;
|
|
|
|
const int prev_start = old->range.start;
|
|
const int prev_end = prev_start + old->range.length;
|
|
|
|
if ((start >= prev_start && start <= prev_end) ||
|
|
(end >= prev_start && end <= prev_end) ||
|
|
(start <= prev_start && end >= prev_end))
|
|
{
|
|
/* The two damage ranges intersect */
|
|
int new_start = min(start, prev_start);
|
|
int new_end = max(end, prev_end);
|
|
|
|
old->range.start = new_start;
|
|
old->range.length = new_end - new_start;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
term_damage_update_or_erase(struct terminal *term, enum damage_type damage_type,
|
|
int start, int length)
|
|
{
|
|
#if 1
|
|
if (tll_length(term->grid->damage) > 1024) {
|
|
term_damage_all(term);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
struct damage dmg = {
|
|
.type = damage_type,
|
|
.range = {.start = term->grid->offset + start, .length = length},
|
|
};
|
|
|
|
if (damage_merge_range(term, &dmg))
|
|
return;
|
|
|
|
tll_push_back(term->grid->damage, dmg);
|
|
}
|
|
#endif
|
|
void
|
|
term_damage_update(struct terminal *term, int start, int length)
|
|
{
|
|
#if 0
|
|
assert(start + length <= term->rows * term->cols);
|
|
term_damage_update_or_erase(term, DAMAGE_UPDATE, start, length);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
term_damage_erase(struct terminal *term, int start, int length)
|
|
{
|
|
#if 0
|
|
assert(start + length <= term->rows * term->cols);
|
|
term_damage_update_or_erase(term, DAMAGE_ERASE, start, length);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
term_damage_all(struct terminal *term)
|
|
{
|
|
#if 0
|
|
tll_free(term->grid->damage);
|
|
tll_free(term->grid->scroll_damage);
|
|
term_damage_update(term, 0, term->rows * term->cols);
|
|
#else
|
|
for (int i = 0; i < term->rows; i++)
|
|
grid_row(term->grid, i)->dirty = true;
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
damage_adjust_after_scroll(struct terminal *term, enum damage_type damage_type,
|
|
struct scroll_region region, int lines)
|
|
{
|
|
tll_foreach(term->grid->damage, it) {
|
|
#if 0
|
|
if (it->item.range.start < term->grid->offset) {
|
|
int end = it->item.range.start + it->item.range.length;
|
|
if (end >= term->grid->offset) {
|
|
it->item.range.start = term->grid->offset;
|
|
it->item.range.length = end - it->item.range.start;
|
|
} else {
|
|
tll_remove(term->grid->damage, it);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int start = it->item.range.start;
|
|
int end = start + it->item.range.length;
|
|
|
|
if (start -
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
term_damage_scroll(struct terminal *term, enum damage_type damage_type,
|
|
struct scroll_region region, int lines)
|
|
{
|
|
#if 0
|
|
//damage_adjust_after_scroll(term, damage_type, region, lines);
|
|
if (damage_type == DAMAGE_SCROLL) {
|
|
tll_foreach(term->grid->damage, it) {
|
|
int start = it->item.range.start;
|
|
int length = it->item.range.length;
|
|
|
|
if (start < term->grid->offset) {
|
|
int end = start + length;
|
|
if (end >= term->grid->offset) {
|
|
it->item.range.start = term->grid->offset;
|
|
it->item.range.length = end - it->item.range.start;
|
|
} else
|
|
tll_remove(term->grid->damage, it);
|
|
} else
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
if (tll_length(term->grid->scroll_damage) > 0) {
|
|
struct damage *dmg = &tll_back(term->grid->scroll_damage);
|
|
|
|
if (dmg->type == damage_type &&
|
|
dmg->scroll.region.start == region.start &&
|
|
dmg->scroll.region.end == region.end)
|
|
{
|
|
dmg->scroll.lines += lines;
|
|
return;
|
|
}
|
|
}
|
|
struct damage dmg = {
|
|
.type = damage_type,
|
|
.scroll = {.region = region, .lines = lines},
|
|
};
|
|
tll_push_back(term->grid->scroll_damage, dmg);
|
|
}
|
|
|
|
#if 0
|
|
void
|
|
term_erase(struct terminal *term, int start, int end)
|
|
{
|
|
LOG_DBG("erase: %d-%d", start, end);
|
|
assert(end >= start);
|
|
assert(end <= term->rows * term->cols);
|
|
|
|
if (term->vt.attrs.have_background) {
|
|
int erase_start = start;
|
|
int left = end - erase_start;
|
|
|
|
while (left > 0) {
|
|
int len = left;
|
|
struct cell *cells = grid_get_range(term->grid, erase_start, &len);
|
|
|
|
memset(cells, 0, len * sizeof(cells[0]));
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
cells[i].attrs.background = term->vt.attrs.background;
|
|
cells[i].attrs.have_background = true;
|
|
}
|
|
|
|
erase_start += len;
|
|
left -= len;
|
|
}
|
|
|
|
term_damage_update(term, start, end - start);
|
|
} else {
|
|
grid_memclear(term->grid, start, end - start);
|
|
term_damage_erase(term, start, end - start);
|
|
}
|
|
}
|
|
#else
|
|
static inline void
|
|
erase_cell_range(struct terminal *term, struct row *row, int start, int end)
|
|
{
|
|
assert(start < term->cols);
|
|
assert(end < term->cols);
|
|
|
|
if (unlikely(term->vt.attrs.have_background)) {
|
|
for (int col = start; col <= end; col++) {
|
|
row->cells[col].c[0] = '\0';
|
|
row->cells[col].attrs.have_background = true;
|
|
row->cells[col].attrs.background = term->vt.attrs.background;
|
|
}
|
|
} else {
|
|
memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0]));
|
|
}
|
|
row->dirty = true;
|
|
}
|
|
|
|
static inline void
|
|
erase_line(struct terminal *term, struct row *row)
|
|
{
|
|
erase_cell_range(term, row, 0, term->cols - 1);
|
|
}
|
|
|
|
void
|
|
term_erase(struct terminal *term, const struct coord *start, const struct coord *end)
|
|
{
|
|
assert(start->row <= end->row);
|
|
assert(start->col <= end->col || start->row < end->row);
|
|
|
|
if (start->row == end->row) {
|
|
struct row *row = grid_row(term->grid, start->row);
|
|
erase_cell_range(term, row, start->col, end->col);
|
|
return;
|
|
}
|
|
|
|
assert(end->row > start->row);
|
|
|
|
erase_cell_range(
|
|
term, grid_row(term->grid, start->row), start->col, term->cols - 1);
|
|
|
|
for (int r = start->row + 1; r < end->row; r++)
|
|
erase_line(term, grid_row(term->grid, r));
|
|
|
|
erase_cell_range(term, grid_row(term->grid, end->row), 0, end->col);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
term_cursor_to(struct terminal *term, int row, int col)
|
|
{
|
|
assert(row < term->rows);
|
|
assert(col < term->cols);
|
|
|
|
term->print_needs_wrap = false;
|
|
|
|
term->cursor.col = col;
|
|
term->cursor.row = row;
|
|
|
|
term->grid->cur_row = grid_row(term->grid, row);
|
|
}
|
|
|
|
void
|
|
term_cursor_left(struct terminal *term, int count)
|
|
{
|
|
int move_amount = min(term->cursor.col, count);
|
|
term->cursor.col -= move_amount;
|
|
assert(term->cursor.col >= 0);
|
|
term->print_needs_wrap = false;
|
|
}
|
|
|
|
void
|
|
term_cursor_right(struct terminal *term, int count)
|
|
{
|
|
int move_amount = min(term->cols - term->cursor.col - 1, count);
|
|
term->cursor.col += move_amount;
|
|
assert(term->cursor.col < term->cols);
|
|
term->print_needs_wrap = false;
|
|
}
|
|
|
|
void
|
|
term_cursor_up(struct terminal *term, int count)
|
|
{
|
|
int move_amount = min(term->cursor.row, count);
|
|
term_cursor_to(term, term->cursor.row - move_amount, term->cursor.col);
|
|
}
|
|
|
|
void
|
|
term_cursor_down(struct terminal *term, int count)
|
|
{
|
|
int move_amount = min(term->rows - term->cursor.row - 1, count);
|
|
term_cursor_to(term, term->cursor.row + move_amount, term->cursor.col);
|
|
}
|
|
|
|
void
|
|
term_scroll_partial(struct terminal *term, struct scroll_region region, int rows)
|
|
{
|
|
LOG_DBG("scroll: %d rows", rows);
|
|
|
|
assert(rows < term->rows && "unimplemented");
|
|
|
|
for (int i = region.start - 1; i >= 0; i--)
|
|
grid_swap_row(term->grid, i, i + rows);
|
|
|
|
if (region.start > 0) {
|
|
#if 0
|
|
tll_foreach(term->grid->damage, it) {
|
|
int start = it->item.range.start - term->grid->offset;
|
|
int end __attribute__((unused)) = start + it->item.range.length;
|
|
|
|
if (start < region.start * term->cols) {
|
|
assert(end <= region.start * term->cols);
|
|
it->item.range.start += rows * term->cols;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for (int i = term->rows - 1; i >= region.end; i--)
|
|
grid_swap_row(term->grid, i, i + rows);
|
|
|
|
if (region.end < term->rows) {
|
|
#if 0
|
|
grid_memmove(
|
|
term->grid,
|
|
(region.end + rows) * term->cols,
|
|
region.end * term->cols,
|
|
(term->rows - region.end) * term->cols);
|
|
#endif
|
|
|
|
#if 0
|
|
tll_foreach(term->grid->damage, it) {
|
|
int start = it->item.range.start - term->grid->offset;
|
|
int end = start + it->item.range.length;
|
|
|
|
if (end > region.end * term->cols) {
|
|
assert(start >= region.end * term->cols);
|
|
it->item.range.start += rows * term->cols;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Offset grid origin */
|
|
term->grid->offset += rows;
|
|
term->grid->offset %= term->grid->num_rows;
|
|
|
|
#if 0
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, max(region.end - rows, 0)},
|
|
&(struct coord){term->cols - 1, region.end - 1});
|
|
#else
|
|
for (int r = max(region.end - rows, 0); r < region.end; r++)
|
|
erase_line(term, grid_row(term->grid, r));
|
|
#endif
|
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
|
|
|
term->grid->cur_row = grid_row(term->grid, term->cursor.row);
|
|
#if 0
|
|
int len = term->cols;
|
|
term->grid->cur_line = grid_get_range(
|
|
term->grid, term->cursor.linear - term->cursor.col, &len);
|
|
|
|
assert(len == term->cols);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
term_scroll(struct terminal *term, int rows)
|
|
{
|
|
term_scroll_partial(term, term->scroll_region, rows);
|
|
}
|
|
|
|
void
|
|
term_scroll_reverse_partial(struct terminal *term,
|
|
struct scroll_region region, int rows)
|
|
{
|
|
assert(rows < term->rows && "unimplemented");
|
|
|
|
if (region.end < term->rows) {
|
|
//assert(false);
|
|
#if 0
|
|
grid_memmove(
|
|
term->grid,
|
|
(region.end - rows) * term->cols,
|
|
region.end * term->cols,
|
|
(term->rows - region.end) * term->cols);
|
|
|
|
tll_foreach(term->grid->damage, it) {
|
|
int start = it->item.range.start - term->grid->offset;
|
|
int end = start + it->item.range.length;
|
|
|
|
if (end > region.end * term->cols) {
|
|
assert(start >= region.end * term->cols);
|
|
it->item.range.start -= rows * term->cols;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (region.start > 0) {
|
|
//assert(false);
|
|
#if 0
|
|
grid_memmove(
|
|
term->grid, -rows * term->cols, 0, region.start * term->cols);
|
|
|
|
tll_foreach(term->grid->damage, it) {
|
|
int start = it->item.range.start - term->grid->offset;
|
|
int end __attribute__((unused)) = start + it->item.range.length;
|
|
|
|
if (start < region.start * term->cols) {
|
|
if (end > region.start * term->cols) {
|
|
LOG_ERR("region.start = %d, rows = %d, damage.start = %d, damage.end = %d (%s)",
|
|
region.start, rows, start, end, it->item.type == DAMAGE_UPDATE ? "UPDATE" : "ERASE");
|
|
abort();
|
|
}
|
|
assert(end <= region.start * term->cols);
|
|
it->item.range.start -= rows * term->cols;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
term->grid->offset += term->grid->num_rows - rows;
|
|
term->grid->offset %= term->grid->num_rows;
|
|
|
|
for (int i = region.end + rows; i < term->rows + rows; i++)
|
|
grid_swap_row(term->grid, i, i - rows);
|
|
for (int i = 0 + rows; i < region.start + rows; i++)
|
|
grid_swap_row(term->grid, i, i - rows);
|
|
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, region.start},
|
|
&(struct coord){term->cols - 1, min(region.start + rows, region.end) - 1});
|
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
|
|
term->grid->cur_row = grid_row(term->grid, term->cursor.row);
|
|
#if 0
|
|
int len = term->cols;
|
|
term->grid->cur_line = grid_get_range(
|
|
term->grid, term->cursor.linear - term->cursor.col, &len);
|
|
|
|
assert(len == term->cols);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
term_scroll_reverse(struct terminal *term, int rows)
|
|
{
|
|
term_scroll_reverse_partial(term, term->scroll_region, rows);
|
|
}
|
|
|
|
#include <linux/input-event-codes.h>
|
|
|
|
static int
|
|
linux_mouse_button_to_x(int button)
|
|
{
|
|
switch (button) {
|
|
case BTN_LEFT: return 1;
|
|
case BTN_RIGHT: return 3;
|
|
case BTN_MIDDLE: return 2;
|
|
case BTN_SIDE: return 8;
|
|
case BTN_EXTRA: return 9;
|
|
case BTN_FORWARD: return 4;
|
|
case BTN_BACK: return 5;
|
|
case BTN_TASK: return -1; /* TODO: ??? */
|
|
|
|
default:
|
|
LOG_WARN("unrecognized mouse button: %d (0x%x)", button, button);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
encode_xbutton(int xbutton)
|
|
{
|
|
switch (xbutton) {
|
|
case 1: case 2: case 3:
|
|
return xbutton - 1;
|
|
|
|
case 4: case 5:
|
|
/* Like button 1 and 2, but with 64 added */
|
|
return xbutton - 4 + 64;
|
|
|
|
case 6: case 7:
|
|
/* Same as 4 and 5. Note: the offset should be something else? */
|
|
return xbutton - 6 + 64;
|
|
|
|
case 8: case 9: case 10: case 11:
|
|
/* Similar to 4 and 5, but adding 128 instead of 64 */
|
|
return xbutton - 8 + 128;
|
|
|
|
default:
|
|
LOG_ERR("cannot encode X mouse button: %d", xbutton);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
report_mouse_click(struct terminal *term, int encoded_button, int row, int col,
|
|
bool release)
|
|
{
|
|
char response[128];
|
|
|
|
switch (term->mouse_reporting) {
|
|
case MOUSE_NORMAL:
|
|
snprintf(response, sizeof(response), "\033[M%c%c%c",
|
|
32 + (release ? 3 : encoded_button), 32 + col + 1, 32 + row + 1);
|
|
break;
|
|
|
|
case MOUSE_SGR:
|
|
snprintf(response, sizeof(response), "\033[<%d;%d;%d%c",
|
|
encoded_button, col + 1, row + 1, release ? 'm' : 'M');
|
|
break;
|
|
|
|
case MOUSE_URXVT:
|
|
snprintf(response, sizeof(response), "\033[%d;%d;%dM",
|
|
32 + (release ? 3 : encoded_button), col + 1, row + 1);
|
|
break;
|
|
|
|
case MOUSE_UTF8:
|
|
/* Unimplemented */
|
|
return;
|
|
}
|
|
|
|
write(term->ptmx, response, strlen(response));
|
|
}
|
|
|
|
static void
|
|
report_mouse_motion(struct terminal *term, int encoded_button, int row, int col)
|
|
{
|
|
report_mouse_click(term, encoded_button, row, col, false);
|
|
}
|
|
|
|
void
|
|
term_mouse_down(struct terminal *term, int button, int row, int col,
|
|
bool shift, bool alt, bool ctrl)
|
|
{
|
|
/* Map libevent button event code to X button number */
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
if (xbutton == -1)
|
|
return;
|
|
|
|
int encoded = encode_xbutton(xbutton);
|
|
if (encoded == -1)
|
|
return;
|
|
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
switch (term->mouse_tracking) {
|
|
case MOUSE_NONE:
|
|
break;
|
|
|
|
case MOUSE_X10:
|
|
case MOUSE_CLICK:
|
|
case MOUSE_DRAG:
|
|
case MOUSE_MOTION:
|
|
report_mouse_click(term, encoded, row, col, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
term_mouse_up(struct terminal *term, int button, int row, int col,
|
|
bool shift, bool alt, bool ctrl)
|
|
{
|
|
/* Map libevent button event code to X button number */
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
if (xbutton == -1)
|
|
return;
|
|
|
|
if (xbutton == 4 || xbutton == 5) {
|
|
/* No release events for scroll buttons */
|
|
return;
|
|
}
|
|
|
|
int encoded = encode_xbutton(xbutton);
|
|
if (encoded == -1)
|
|
return;
|
|
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
switch (term->mouse_tracking) {
|
|
case MOUSE_NONE:
|
|
break;
|
|
|
|
case MOUSE_X10:
|
|
case MOUSE_CLICK:
|
|
case MOUSE_DRAG:
|
|
case MOUSE_MOTION:
|
|
report_mouse_click(term, encoded, row, col, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
term_mouse_motion(struct terminal *term, int button, int row, int col,
|
|
bool shift, bool alt, bool ctrl)
|
|
{
|
|
int encoded = 0;
|
|
|
|
if (button != 0) {
|
|
/* Map libevent button event code to X button number */
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
if (xbutton == -1)
|
|
return;
|
|
|
|
encoded = encode_xbutton(xbutton);
|
|
if (encoded == -1)
|
|
return;
|
|
} else
|
|
encoded = 3; /* "released" */
|
|
|
|
encoded += 32; /* Motion event */
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
switch (term->mouse_tracking) {
|
|
case MOUSE_NONE:
|
|
case MOUSE_X10:
|
|
case MOUSE_CLICK:
|
|
return;
|
|
|
|
case MOUSE_DRAG:
|
|
if (button == 0)
|
|
return;
|
|
/* FALLTHROUGH */
|
|
|
|
case MOUSE_MOTION:
|
|
report_mouse_motion(term, encoded, row, col);
|
|
break;
|
|
}
|
|
}
|