pe: decent console with erasing, scrolling and more unused potential

This commit is contained in:
Lephenixnoir 2022-10-24 00:53:53 +02:00
parent e33d85b1de
commit f9b4c1f844
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
6 changed files with 354 additions and 94 deletions

View File

@ -5,106 +5,282 @@
#include <string.h>
#include "console.h"
console_t *console_create(int max_lines)
//=== Dynamic console lines ===//
bool console_line_init(console_line_t *line, int prealloc_size)
{
console_t *cons = malloc(sizeof *cons);
cons->max_lines = max_lines;
cons->lines = calloc(max_lines, sizeof *cons->lines);
for(int i = 0; i < max_lines; i++)
cons->lines[i] = NULL;
console_new_line(cons);
return cons;
char *data = malloc(prealloc_size + 1);
if(!data)
return false;
data[0] = 0;
line->data = data;
line->size = 0;
line->alloc_size = prealloc_size + 1;
line->render_lines = 0;
return true;
}
void console_new_line(console_t *cons)
void console_line_deinit(console_line_t *line)
{
int n = cons->max_lines;
free(cons->lines[0]);
for(int i = 0; i < n - 1; i++)
cons->lines[i] = cons->lines[i+1];
cons->lines[n - 1] = strdup("");
free(line->data);
line->data = NULL;
line->size = 0;
line->alloc_size = 0;
}
static void console_append_to_last_line(console_t *cons, void const *buf,
int size)
bool console_line_alloc(console_line_t *line, int n)
{
int n = cons->max_lines;
char *l = cons->lines[n - 1];
int prev_size = l ? strlen(l) : 0;
int new_size = prev_size + size + 1;
if(line->alloc_size >= n + 1)
return true;
l = realloc(l, new_size);
if(!l)
/* Always increase the size by at least 16 so we can insert many times in a
row without worrying about excessive insertions. */
int newsize = max(line->alloc_size + 16, n + 1);
char *newdata = realloc(line->data, newsize);
if(!newdata)
return false;
line->data = newdata;
line->alloc_size = newsize;
return true;
}
int console_line_capacity(console_line_t *line)
{
return PE_CONSOLE_LINE_MAX_LENGTH - line->size;
}
bool console_line_insert(console_line_t *line, int p, char const *str, int n)
{
if(p < 0 || p > line->size || n > console_line_capacity(line))
return false;
if(!console_line_alloc(line, line->size + n))
return false;
/* Move the end of the string (plus the NUL) n bytes forward */
memmove(line->data + p + n, line->data + p, line->size - p + 1);
memcpy(line->data + p, str, n);
line->size += n;
return true;
}
void console_line_delete(console_line_t *line, int p, int n)
{
n = min(n, line->size - p);
/* Move the end of the string (plus the NUL) n bytes backwards */
memmove(line->data + p, line->data + p + n, line->size - n - p + 1);
line->size -= n;
}
void console_line_update_render_lines(console_line_t *line, int width)
{
line->render_lines = 0;
char const *p = line->data;
char const *endline = p + strlen(p);
if(endline == p) {
line->render_lines = 1;
return;
}
memcpy(l + prev_size, buf, size);
l[new_size - 1] = 0;
cons->lines[n - 1] = l;
}
void console_append_str(console_t *cons, char const *str)
{
console_append_buf(cons, str, strlen(str));
}
void console_append_buf(console_t *cons, void const *buf, size_t size_)
{
int offset = 0, size = size_;
while(offset < size) {
/* Find the first '\n' (or end of buffer) in the segment */
void const *endline = memchr(buf + offset, '\n', size - offset);
if(endline == NULL)
endline = buf + size;
int line_size = endline - (buf + offset);
console_append_to_last_line(cons, buf + offset, line_size);
offset += line_size;
/* Found a '\n' */
if(offset < size) {
console_new_line(cons);
offset++;
}
while(p < endline) {
line->render_lines++;
p = drsize(p, NULL, width, NULL);
}
}
void console_render(int x, int y, console_t *cons, int w, int h)
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
int show_from, int cursor)
{
int dy = 13;
char const *p = line->data;
char const *endline = p + strlen(p);
int line_offset = 0;
int line_number = 0;
for(int i = 0; i < cons->max_lines; i++) {
/* Skip initial unallocated lines */
if(cons->lines[i] == NULL)
continue;
char const *p = cons->lines[i];
char const *endline = p + strlen(p);
while(p < endline) {
if(p[0] == '\n') {
y += dy;
p++;
continue;
}
char const *endscreen = drsize(p, NULL, w, NULL);
char const *endline = strchrnul(p, '\n');
int len = min(endscreen - p, endline - p);
while(p < endline) {
char const *endscreen = drsize(p, NULL, w, NULL);
int len = endscreen - p;
if(line_number >= show_from) {
dtext_opt(x, y, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_TOP, p, len);
if(cursor >= line_offset && cursor <= line_offset + len) {
int w, h;
dnsize(p, cursor - line_offset, NULL, &w, &h);
dline(x+w, y, x+w, y+h-1, C_BLACK);
}
y += dy;
p += len + (p[len] == '\n');
}
p += len;
line_offset += len;
line_number++;
}
return y;
}
//=== Console data storage ===//
console_t *console_create(int backlog_size)
{
console_t *cons = malloc(sizeof *cons);
cons->lines = NULL;
cons->line_count = 0;
cons->backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH);
if(!console_new_line(cons)) {
free(cons);
return NULL;
}
return cons;
}
bool console_new_line(console_t *cons)
{
int newsize = (cons->line_count + 1) * sizeof *cons->lines;
console_line_t *newlines = realloc(cons->lines, newsize);
if(!newlines)
return false;
cons->lines = newlines;
/* If this fails we keep the extended lines buffer. The next realloc() will
be a no-op and we don't have to track anything. */
if(!console_line_init(&cons->lines[cons->line_count], 16))
return false;
cons->line_count++;
cons->cursor = 0;
/* Routinely clean the backlog every time a line is added. */
// console_clean_backlog(cons);
return true;
}
void console_clean_backlog(console_t *cons)
{
int total_size = 0, i = cons->line_count;
/* Determine how many lines (starting from the end) we can fit */
while(total_size < cons->backlog_size) {
i--;
total_size += cons->lines[i].size;
}
/* Make sure to keep at least one line */
int delete_until = i + (i < cons->line_count - 1);
int remains = cons->line_count - delete_until;
/* Remove `delete_until` lines */
for(int j = 0; j < delete_until; j++)
console_line_deinit(&cons->lines[j]);
/* Don't realloc() yet, it will happen soon enough anyway */
memmove(cons->lines, cons->lines + delete_until, remains);
cons->line_count = remains;
}
bool console_write_block_at_cursor(console_t *cons, char const *str, int n)
{
if(!cons->line_count && !console_new_line(cons))
return false;
/* Split up n characters into chunks that remain within each line's storage
capacity. */
while(n > 0) {
console_line_t *last_line = &cons->lines[cons->line_count - 1];
int capacity = console_line_capacity(last_line);
int round_size = min(n, capacity);
if(!console_line_insert(last_line, cons->cursor, str, round_size))
return false;
cons->cursor += round_size;
if(round_size < n && !console_new_line(cons))
return false;
n -= round_size;
}
return true;
}
bool console_write_at_cursor(console_t *cons, char const *buf, int n)
{
int offset = 0;
while(offset < n) {
/* Find the first '\n', '\e' or end of buffer */
char const *end = buf + offset;
while(end < buf + n && *end != '\n' && *end != '\e' && *end != 8)
end++;
int line_size = end - (buf + offset);
if(!console_write_block_at_cursor(cons, buf + offset, line_size))
return false;
offset += line_size;
if(offset >= n)
break;
if(buf[offset] == '\n') {
if(!console_new_line(cons))
return false;
offset++;
}
else if(buf[offset] == 8) {
offset++;
console_line_t *last_line = &cons->lines[cons->line_count - 1];
if(cons->cursor > 0) {
console_line_delete(last_line, cons->cursor-1, 1);
cons->cursor--;
}
}
else if(buf[offset] == '\e') {
console_line_t *last_line = &cons->lines[cons->line_count - 1];
offset++;
/* TODO: Handle more complex escape sequences */
if(offset + 2 <= n && buf[offset] == '[' && buf[offset+1] == 'K') {
console_line_delete(last_line, cons->cursor,
last_line->size - cons->cursor);
offset += 2;
}
if(offset + 2 <= n && buf[offset] == 1 && buf[offset+1] == 'D') {
cons->cursor -= (cons->cursor > 0);
}
if(offset + 2 <= n && buf[offset] == 1 && buf[offset+1] == 'C') {
cons->cursor += (cons->cursor < last_line->size);
}
}
}
return true;
}
void console_render(int x, int y, console_t *cons, int w, int lines)
{
int watermark = lines;
for(int i = 0; i < cons->line_count; i++) {
console_line_update_render_lines(&cons->lines[i], w);
watermark -= cons->lines[i].render_lines;
}
/* Show only visible lines */
for(int i = 0; i < cons->line_count; i++) {
console_line_t *line = &cons->lines[i];
bool show_cursor = (i == cons->line_count - 1);
if(watermark + line->render_lines > 0)
y = console_line_render(x, y, line, w, PE_CONSOLE_LINE_SPACING,
-watermark, show_cursor ? cons->cursor : -1);
watermark += line->render_lines;
}
}
void console_destroy(console_t *cons)
{
for(int i = 0; i < cons->max_lines; i++)
free(cons->lines[i]);
for(int i = 0; i < cons->line_count; i++)
console_line_deinit(&cons->lines[i]);
free(cons->lines);
free(cons);
}

View File

@ -1,27 +1,91 @@
#ifndef __PYTHONEXTRA_CONSOLE_H
#define __PYTHONEXTRA_CONSOLE_H
#include <stdbool.h>
/* Maximum line length, to ensure the console can threshold its memory usage
while cleaning only entire lines. Lines longer than this get split. */
#define PE_CONSOLE_LINE_MAX_LENGTH 1024
/* Line spacing in the console */
#define PE_CONSOLE_LINE_SPACING 13
//=== Dynamic console lines ===//
typedef struct
{
/* Line contents, NUL-terminated. The buffer might be larger. */
char *data;
/* Size of contents (not counting the NUL). */
int size;
/* Allocated size (always ≥ size+1). */
int alloc_size;
/* Number or render lines used (updated on-demand). */
int render_lines;
} console_line_t;
/* Create a new console line with at least init_chars characters of content
available. Returns false on error. Previous contents are not freed! */
bool console_line_init(console_line_t *line, int init_chars);
/* Clean up a line and free its contents. */
void console_line_deinit(console_line_t *line);
/* Realloc the line to ensure n characters plus a NUL can be written. */
bool console_line_alloc(console_line_t *line, int n);
/* Determine how many characters can be written before the line has to be
broken up. */
int console_line_capacity(console_line_t *line);
/* Insert n characters at position p. */
bool console_line_insert(console_line_t *line, int p, char const *str, int n);
/* Remove n characters at position p. */
void console_line_delete(console_line_t *line, int p, int n);
/* Update the number of render lines for the chosen width. */
void console_line_update_render_lines(console_line_t *line, int width);
//=== Console data storage ===//
typedef struct
{
/* An array lines[] of size [max_lines], filled from the end (always has a
list of NULL followed by line pointers. */
int max_lines;
char **lines;
/* A dynamic array of console_line_t. Never empty. */
console_line_t *lines;
int line_count;
/* Maximum (not 100% strict) amount of storage that is conserved in log.
When this limit is exceeded, old lines are cleaned. This must be more
than PE_CONSOLE_LINE_MAX_LENGTH. */
int backlog_size;
/* Cursor position within the last line. */
int cursor;
} console_t;
console_t *console_create(int max_lines);
/* Create a new console with the specified backlog size. */
console_t *console_create(int backlog_size);
void console_new_line(console_t *cons);
/* Create a new empty line at the bottom of the console, and move the cursor
there. Previous lines can no longer be edited. Returns false on error. */
bool console_new_line(console_t *cons);
void console_append_str(console_t *cons, char const *str);
/* Clean up backlog if the total memory usage is exceeded. */
void console_clean_backlog(console_t *cons);
void console_append_buf(console_t *cons, void const *buf, size_t size);
/* Write string at the cursor's position within the last line. This writes a
raw string without interpreting escape sequences and newlines. */
bool console_write_block_at_cursor(console_t *cons, char const *str, int n);
/* Write string at the cursor's position within the last line. This function
interprets escape sequences and newlines. */
bool console_write_at_cursor(console_t *cons, char const *str, int n);
/* TODO: Expand this function */
void console_render(int x, int y, console_t *cons, int w, int h);
void console_render(int x, int y, console_t *cons, int w, int lines);
void console_destroy(console_t *cons);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -24,7 +24,7 @@ int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind,
ssize_t stdouterr_write(void *data, void const *buf, size_t size)
{
console_t *cons = data;
console_append_buf(cons, buf, size);
console_write_at_cursor(cons, buf, size);
return size;
}
@ -42,9 +42,12 @@ static console_t *cons = NULL;
void pe_draw(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "PythonExtra, very much WIP :)");
dline(1, 16, DWIDTH-1, 16, C_BLACK);
console_render(1, 18, cons, DWIDTH-2, -1);
dprint(3, 3, C_BLACK, "PythonExtra, very much WIP :)");
dline(2, 16, DWIDTH-3, 16, C_BLACK);
int rows = 12;
console_render(3, 20, cons, DWIDTH-6, rows);
int y = 20 + PE_CONSOLE_LINE_SPACING * rows;
dline(2, y, DWIDTH-3, y, C_BLACK);
dupdate();
}
@ -59,7 +62,7 @@ void pe_exithandler(void)
int main(int argc, char **argv)
{
/* Set up standard streams */
cons = console_create(10);
cons = console_create(8192);
close(STDOUT_FILENO);
close(STDERR_FILENO);
open_generic(&stdouterr_type, cons, STDOUT_FILENO);

View File

@ -13,12 +13,29 @@ int mp_hal_stdin_rx_chr(void) {
key_event_t ev = getkey();
int key = ev.key;
/* (The following meanings are only for non-empty lines) */
/* TODO: Check cons->cursor before triggering them */
if(key == KEY_LEFT && ev.shift)
return CHAR_CTRL_A; /* go-to-start-of-line */
if(key == KEY_LEFT)
return CHAR_CTRL_B; /* go-back-one-char */
if(key == KEY_ACON)
return CHAR_CTRL_C;
return CHAR_CTRL_C; /* cancel */
if(key == KEY_DEL && !ev.shift)
return 8; /* delete-at-cursor */
if(key == KEY_RIGHT && ev.shift)
return CHAR_CTRL_E; /* go-to-end-of-line */
if(key == KEY_RIGHT)
return CHAR_CTRL_F; /* go-forward-one-char */
if(key == KEY_DEL && ev.shift)
return CHAR_CTRL_K; /* kill from cursor to end-of-line */
if(key == KEY_DOWN)
return CHAR_CTRL_N; /* go to next line in history */
if(key == KEY_UP)
return CHAR_CTRL_P; /* go to previous line in history */
if(key == KEY_EXE)
return '\r';
if(key == KEY_EXIT)
return CHAR_CTRL_D;
uint32_t code_point = keymap_translate(key, ev.shift, ev.alpha);
if(code_point != 0)