mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-07 04:06:07 -05:00
Merge branch 'scrollback'
This commit is contained in:
commit
0bc3b29b0f
9 changed files with 263 additions and 23 deletions
121
commands.c
Normal file
121
commands.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#include "commands.h"
|
||||
|
||||
#define LOG_MODULE "commands"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
#include "terminal.h"
|
||||
#include "render.h"
|
||||
#include "grid.h"
|
||||
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
#define min(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
void
|
||||
cmd_scrollback_up(struct terminal *term, int rows)
|
||||
{
|
||||
if (term->grid == &term->alt)
|
||||
return;
|
||||
|
||||
rows = min(rows, term->grid->num_rows - term->rows);
|
||||
|
||||
assert(term->grid->offset >= 0);
|
||||
assert(rows <= term->rows);
|
||||
|
||||
int new_view = (term->grid->view + term->grid->num_rows - rows) % term->grid->num_rows;
|
||||
assert(new_view >= 0);
|
||||
assert(new_view < term->grid->num_rows);
|
||||
|
||||
/* Avoid scrolling in uninitialized rows */
|
||||
while (!term->grid->rows[new_view]->initialized)
|
||||
new_view = (new_view + 1) % term->grid->num_rows;
|
||||
|
||||
if (new_view == term->grid->view) {
|
||||
/*
|
||||
* This happens when scrolling up in a newly opened terminal;
|
||||
* every single line (except those already visible) are
|
||||
* uninitalized, and the loop above will bring us back to
|
||||
* where we started.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't scroll past scrollback history */
|
||||
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
|
||||
if (end >= term->grid->offset) {
|
||||
/* Not wrapped */
|
||||
if (new_view >= term->grid->offset && new_view <= end)
|
||||
new_view = end + 1;
|
||||
} else {
|
||||
if (new_view >= term->grid->offset || new_view <= end)
|
||||
new_view = end + 1;
|
||||
}
|
||||
|
||||
LOG_DBG("scrollback UP: %d -> %d (offset = %d, end = %d, rows = %d)",
|
||||
term->grid->view, new_view, term->grid->offset, end, term->grid->num_rows);
|
||||
|
||||
if (new_view == term->grid->view)
|
||||
return;
|
||||
|
||||
term->grid->view = new_view;
|
||||
|
||||
term_damage_view(term);
|
||||
if (term->frame_callback == NULL)
|
||||
grid_render(term);
|
||||
}
|
||||
|
||||
void
|
||||
cmd_scrollback_down(struct terminal *term, int rows)
|
||||
{
|
||||
if (term->grid == &term->alt)
|
||||
return;
|
||||
|
||||
if (term->grid->view == term->grid->offset)
|
||||
return;
|
||||
|
||||
rows = min(rows, term->grid->num_rows - term->rows);
|
||||
|
||||
assert(term->grid->offset >= 0);
|
||||
|
||||
int new_view = (term->grid->view + rows) % term->grid->num_rows;
|
||||
assert(new_view >= 0);
|
||||
assert(new_view < term->grid->num_rows);
|
||||
|
||||
/* Prevent scrolling in uninitialized rows */
|
||||
bool all_initialized = false;
|
||||
do {
|
||||
all_initialized = true;
|
||||
|
||||
for (int i = 0; i < term->rows; i++) {
|
||||
int row_no = (new_view + i) % term->grid->num_rows;
|
||||
if (!term->grid->rows[row_no]->initialized) {
|
||||
all_initialized = false;
|
||||
new_view--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!all_initialized);
|
||||
|
||||
/* Don't scroll past scrollback history */
|
||||
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
|
||||
if (end >= term->grid->offset) {
|
||||
/* Not wrapped */
|
||||
if (new_view >= term->grid->offset && new_view <= end)
|
||||
new_view = term->grid->offset;
|
||||
} else {
|
||||
if (new_view >= term->grid->offset || new_view <= end)
|
||||
new_view = term->grid->offset;
|
||||
}
|
||||
|
||||
|
||||
LOG_DBG("scrollback DOWN: %d -> %d (offset = %d, end = %d, rows = %d)",
|
||||
term->grid->view, new_view, term->grid->offset, end, term->grid->num_rows);
|
||||
|
||||
if (new_view == term->grid->view)
|
||||
return;
|
||||
|
||||
term->grid->view = new_view;
|
||||
|
||||
term_damage_view(term);
|
||||
if (term->frame_callback == NULL)
|
||||
grid_render(term);
|
||||
}
|
||||
6
commands.h
Normal file
6
commands.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
void cmd_scrollback_up(struct terminal *term, int rows);
|
||||
void cmd_scrollback_down(struct terminal *term, int rows);
|
||||
7
grid.h
7
grid.h
|
|
@ -10,6 +10,13 @@ grid_row(struct grid *grid, int row)
|
|||
return grid->rows[(grid->offset + row + grid->num_rows) % grid->num_rows];
|
||||
}
|
||||
|
||||
static inline struct row *
|
||||
grid_row_in_view(struct grid *grid, int row)
|
||||
{
|
||||
assert(grid->view >= 0);
|
||||
return grid->rows[(grid->view + row + grid->num_rows) % grid->num_rows];
|
||||
}
|
||||
|
||||
static inline void
|
||||
grid_swap_row(struct grid *grid, int row_a, int row_b)
|
||||
{
|
||||
|
|
|
|||
51
input.c
51
input.c
|
|
@ -7,6 +7,8 @@
|
|||
#include <locale.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <linux/input-event-codes.h>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-keysyms.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
|
|
@ -17,6 +19,7 @@
|
|||
#include "terminal.h"
|
||||
#include "render.h"
|
||||
#include "keymap.h"
|
||||
#include "commands.h"
|
||||
|
||||
static void
|
||||
keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
|
||||
|
|
@ -140,6 +143,18 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
keymap_mods |= term->kbd.alt ? MOD_ALT : MOD_NONE;
|
||||
keymap_mods |= term->kbd.ctrl ? MOD_CTRL : MOD_NONE;
|
||||
|
||||
if (effective_mods == shift) {
|
||||
if (sym == XKB_KEY_Page_Up) {
|
||||
cmd_scrollback_up(term, term->rows);
|
||||
found_map = true;
|
||||
}
|
||||
|
||||
else if (sym == XKB_KEY_Page_Down) {
|
||||
cmd_scrollback_down(term, term->rows);
|
||||
found_map = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(key_map) / sizeof(key_map[0]) && !found_map; i++) {
|
||||
const struct key_map *k = &key_map[i];
|
||||
if (k->sym != sym)
|
||||
|
|
@ -160,6 +175,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
|
||||
write(term->ptmx, info->seq, strlen(info->seq));
|
||||
found_map = true;
|
||||
|
||||
if (term->grid->view != term->grid->offset) {
|
||||
term->grid->view = term->grid->offset;
|
||||
/* TODO: damage view */
|
||||
term_damage_all(term);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -182,6 +204,13 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
write(term->ptmx, "\x1b", 1);
|
||||
|
||||
write(term->ptmx, buf, count);
|
||||
|
||||
if (term->grid->view != term->grid->offset) {
|
||||
term->grid->view = term->grid->offset;
|
||||
/* TODO: damage view */
|
||||
term_damage_all(term);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,6 +344,28 @@ static void
|
|||
wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
|
||||
uint32_t time, uint32_t axis, wl_fixed_t value)
|
||||
{
|
||||
struct terminal *term = data;
|
||||
|
||||
/* TODO: generate button event for BTN_FORWARD/BTN_BACK? */
|
||||
|
||||
if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
return;
|
||||
|
||||
int amount = wl_fixed_to_int(value);
|
||||
|
||||
if (amount < 0) {
|
||||
for (int i = 0; i < -amount; i++) {
|
||||
term_mouse_down(term, BTN_BACK, term->mouse.row, term->mouse.col,
|
||||
term->kbd.shift, term->kbd.alt, term->kbd.ctrl);
|
||||
}
|
||||
cmd_scrollback_up(term, -amount);
|
||||
} else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
term_mouse_down(term, BTN_FORWARD, term->mouse.row, term->mouse.col,
|
||||
term->kbd.shift, term->kbd.alt, term->kbd.ctrl);
|
||||
}
|
||||
cmd_scrollback_down(term, amount);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
2
main.c
2
main.c
|
|
@ -495,7 +495,7 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
}
|
||||
|
||||
if (ret == 0 || !(timeout_ms != -1 && fds[1].revents & POLLIN)) {
|
||||
if (ret == 0 || (timeout_ms != -1 && !(fds[1].revents & POLLIN))) {
|
||||
/* Delayed rendering */
|
||||
if (term.frame_callback == NULL)
|
||||
grid_render(&term);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ endforeach
|
|||
|
||||
executable(
|
||||
'f00ter',
|
||||
'commands.c', 'commands.h',
|
||||
'csi.c', 'csi.h',
|
||||
'font.c', 'font.h',
|
||||
'grid.c', 'grid.h',
|
||||
|
|
|
|||
72
render.c
72
render.c
|
|
@ -35,13 +35,8 @@ static struct glyph_sequence gseq;
|
|||
|
||||
static void
|
||||
render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell,
|
||||
int col, int row)
|
||||
int col, int row, bool has_cursor)
|
||||
{
|
||||
/* Cursor here? */
|
||||
bool has_cursor
|
||||
= (!term->hide_cursor &&
|
||||
(term->cursor.col == col && term->cursor.row == row));
|
||||
|
||||
double width = term->cell_width;
|
||||
double height = term->cell_height;
|
||||
double x = col * width;
|
||||
|
|
@ -243,7 +238,7 @@ grid_render(struct terminal *term)
|
|||
gseq.count = 0;
|
||||
|
||||
for (int r = 0; r < term->rows; r++) {
|
||||
struct row *row = grid_row(term->grid, r);
|
||||
struct row *row = grid_row_in_view(term->grid, r);
|
||||
|
||||
if (!row->dirty)
|
||||
continue;
|
||||
|
|
@ -251,7 +246,7 @@ grid_render(struct terminal *term)
|
|||
//LOG_WARN("rendering line: %d", r);
|
||||
|
||||
for (int col = 0; col < term->cols; col++)
|
||||
render_cell(term, buf, &row->cells[col], col, r);
|
||||
render_cell(term, buf, &row->cells[col], col, r, false);
|
||||
|
||||
row->dirty = false;
|
||||
all_clean = false;
|
||||
|
|
@ -269,7 +264,7 @@ grid_render(struct terminal *term)
|
|||
int row = last_cursor / term->cols - term->grid->offset;
|
||||
int col = last_cursor % term->cols;
|
||||
if (row >= 0 && row < term->rows) {
|
||||
render_cell(term, buf, &grid_row(term->grid, row)->cells[col], col, row);
|
||||
render_cell(term, buf, &grid_row_in_view(term->grid, row)->cells[col], col, row, false);
|
||||
all_clean = false;
|
||||
|
||||
wl_surface_damage_buffer(
|
||||
|
|
@ -284,16 +279,31 @@ grid_render(struct terminal *term)
|
|||
return;
|
||||
}
|
||||
|
||||
render_cell(
|
||||
term, buf,
|
||||
&grid_row(term->grid, term->cursor.row)->cells[term->cursor.col],
|
||||
term->cursor.col, term->cursor.row);
|
||||
bool cursor_is_visible = false;
|
||||
int view_end = (term->grid->view + term->rows - 1) % term->grid->num_rows;
|
||||
int cursor_row = (term->grid->offset + term->cursor.row) % term->grid->num_rows;
|
||||
if (view_end >= term->grid->view) {
|
||||
/* Not wrapped */
|
||||
if (cursor_row >= term->grid->view && cursor_row <= view_end)
|
||||
cursor_is_visible = true;
|
||||
} else {
|
||||
/* Wrapped */
|
||||
if (cursor_row >= term->grid->view || cursor_row <= view_end)
|
||||
cursor_is_visible = true;
|
||||
}
|
||||
|
||||
wl_surface_damage_buffer(
|
||||
term->wl.surface,
|
||||
term->cursor.col * term->cell_width,
|
||||
term->cursor.row * term->cell_height,
|
||||
term->cell_width, term->cell_height);
|
||||
if (cursor_is_visible) {
|
||||
render_cell(
|
||||
term, buf,
|
||||
&grid_row_in_view(term->grid, term->cursor.row)->cells[term->cursor.col],
|
||||
term->cursor.col, term->cursor.row, true);
|
||||
|
||||
wl_surface_damage_buffer(
|
||||
term->wl.surface,
|
||||
term->cursor.col * term->cell_width,
|
||||
term->cursor.row * term->cell_height,
|
||||
term->cell_width, term->cell_height);
|
||||
}
|
||||
|
||||
if (gseq.count > 0) {
|
||||
cairo_set_scaled_font(buf->cairo, attrs_to_font(term, &gseq.attrs));
|
||||
|
|
@ -304,6 +314,7 @@ grid_render(struct terminal *term)
|
|||
}
|
||||
|
||||
assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows);
|
||||
assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows);
|
||||
|
||||
cairo_surface_flush(buf->cairo_surface);
|
||||
wl_surface_attach(term->wl.surface, buf->wl_buf, 0, 0);
|
||||
|
|
@ -338,12 +349,18 @@ reflow(struct row **new_grid, int new_cols, int new_rows,
|
|||
struct cell *new_cells = new_grid[r]->cells;
|
||||
const struct cell *old_cells = old_grid[r]->cells;
|
||||
|
||||
new_grid[r]->initialized = old_grid[r]->initialized;
|
||||
new_grid[r]->dirty = old_grid[r]->dirty;
|
||||
|
||||
memcpy(new_cells, old_cells, copy_cols * sizeof(new_cells[0]));
|
||||
memset(&new_cells[copy_cols], 0, clear_cols * sizeof(new_cells[0]));
|
||||
}
|
||||
|
||||
for (int r = min(new_rows, old_rows); r < new_rows; r++)
|
||||
for (int r = min(new_rows, old_rows); r < new_rows; r++) {
|
||||
new_grid[r]->initialized = false;
|
||||
new_grid[r]->dirty = false;
|
||||
memset(new_grid[r]->cells, 0, new_cols * sizeof(new_grid[r]->cells[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Move to terminal.c? */
|
||||
|
|
@ -356,6 +373,8 @@ render_resize(struct terminal *term, int width, int height)
|
|||
term->width = width;
|
||||
term->height = height;
|
||||
|
||||
const int scrollback_lines = 10000;
|
||||
|
||||
const int old_cols = term->cols;
|
||||
const int old_rows = term->rows;
|
||||
const int old_normal_grid_rows = term->normal.num_rows;
|
||||
|
|
@ -363,7 +382,7 @@ render_resize(struct terminal *term, int width, int height)
|
|||
|
||||
const int new_cols = term->width / term->cell_width;
|
||||
const int new_rows = term->height / term->cell_height;
|
||||
const int new_normal_grid_rows = new_rows;
|
||||
const int new_normal_grid_rows = new_rows + scrollback_lines;
|
||||
const int new_alt_grid_rows = new_rows;
|
||||
|
||||
/* Allocate new 'normal' grid */
|
||||
|
|
@ -388,6 +407,11 @@ render_resize(struct terminal *term, int width, int height)
|
|||
reflow(alt, new_cols, new_alt_grid_rows,
|
||||
term->alt.rows, old_cols, old_alt_grid_rows);
|
||||
|
||||
for (int r = 0; r < new_rows; r++) {
|
||||
normal[r]->initialized = true;
|
||||
alt[r]->initialized = true;
|
||||
}
|
||||
|
||||
/* Free old 'normal' grid */
|
||||
for (int r = 0; r < term->normal.num_rows; r++) {
|
||||
free(term->normal.rows[r]->cells);
|
||||
|
|
@ -430,11 +454,19 @@ render_resize(struct terminal *term, int width, int height)
|
|||
if (term->scroll_region.end >= old_rows)
|
||||
term->scroll_region.end = term->rows;
|
||||
|
||||
term->normal.offset %= term->normal.num_rows;
|
||||
term->normal.view %= term->normal.num_rows;
|
||||
|
||||
term->alt.offset %= term->alt.num_rows;
|
||||
term->alt.view %= term->alt.num_rows;
|
||||
|
||||
term_cursor_to(
|
||||
term,
|
||||
min(term->cursor.row, term->rows - 1),
|
||||
min(term->cursor.col, term->cols - 1));
|
||||
|
||||
term_damage_all(term);
|
||||
term_damage_view(term);
|
||||
|
||||
if (term->frame_callback == NULL)
|
||||
grid_render(term);
|
||||
|
|
|
|||
23
terminal.c
23
terminal.c
|
|
@ -21,6 +21,13 @@ term_damage_all(struct terminal *term)
|
|||
grid_row(term->grid, i)->dirty = true;
|
||||
}
|
||||
|
||||
void
|
||||
term_damage_view(struct terminal *term)
|
||||
{
|
||||
for (int i = 0; i < term->rows; i++)
|
||||
grid_row_in_view(term->grid, i)->dirty = true;
|
||||
}
|
||||
|
||||
void
|
||||
term_damage_scroll(struct terminal *term, enum damage_type damage_type,
|
||||
struct scroll_region region, int lines)
|
||||
|
|
@ -152,11 +159,18 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
|
|||
grid_swap_row(term->grid, i, i + rows);
|
||||
|
||||
/* Offset grid origin */
|
||||
bool view_follows = term->grid->view == term->grid->offset;
|
||||
term->grid->offset += rows;
|
||||
term->grid->offset %= term->grid->num_rows;
|
||||
|
||||
for (int r = max(region.end - rows, 0); r < region.end; r++)
|
||||
erase_line(term, grid_row(term->grid, r));
|
||||
if (view_follows)
|
||||
term->grid->view = term->grid->offset;
|
||||
|
||||
for (int r = max(region.end - rows, 0); r < region.end; r++) {
|
||||
struct row *row = grid_row(term->grid, r);
|
||||
erase_line(term, row);
|
||||
row->initialized = true;
|
||||
}
|
||||
|
||||
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
||||
term->grid->cur_row = grid_row(term->grid, term->cursor.row);
|
||||
|
|
@ -174,9 +188,14 @@ term_scroll_reverse_partial(struct terminal *term,
|
|||
{
|
||||
assert(rows < term->rows && "unimplemented");
|
||||
|
||||
bool view_follows = term->grid->view == term->grid->offset;
|
||||
|
||||
term->grid->offset += term->grid->num_rows - rows;
|
||||
term->grid->offset %= term->grid->num_rows;
|
||||
|
||||
if (view_follows)
|
||||
term->grid->view = term->grid->offset;
|
||||
|
||||
/* Bottom non-scrolling region */
|
||||
for (int i = region.end + rows; i < term->rows + rows; i++)
|
||||
grid_swap_row(term->grid, i, i - rows);
|
||||
|
|
|
|||
|
|
@ -94,11 +94,13 @@ struct damage {
|
|||
struct row {
|
||||
struct cell *cells;
|
||||
bool dirty;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
struct grid {
|
||||
int num_rows;
|
||||
int offset;
|
||||
int view;
|
||||
|
||||
struct row **rows;
|
||||
struct row *cur_row;
|
||||
|
|
@ -248,6 +250,7 @@ struct terminal {
|
|||
};
|
||||
|
||||
void term_damage_all(struct terminal *term);
|
||||
void term_damage_view(struct terminal *term);
|
||||
void term_damage_scroll(
|
||||
struct terminal *term, enum damage_type damage_type,
|
||||
struct scroll_region region, int lines);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue