361 lines
10 KiB
C
361 lines
10 KiB
C
//---------------------------------------------------------------------------//
|
|
// ____ PythonExtra //
|
|
//.-'`_ o `;__, A community port of MicroPython for CASIO calculators. //
|
|
//.-'` `---` ' License: MIT (except some files; see LICENSE) //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include <gint/keyboard.h>
|
|
#include <gint/display.h>
|
|
#include <gint/defs/util.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "console.h"
|
|
|
|
#include "py/mphal.h"
|
|
#include "../../shared/readline/readline.h"
|
|
#include "keymap.h"
|
|
|
|
//=== Dynamic console lines ===//
|
|
|
|
bool console_line_init(console_line_t *line, int prealloc_size)
|
|
{
|
|
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_line_deinit(console_line_t *line)
|
|
{
|
|
free(line->data);
|
|
line->data = NULL;
|
|
line->size = 0;
|
|
line->alloc_size = 0;
|
|
}
|
|
|
|
bool console_line_alloc(console_line_t *line, int n)
|
|
{
|
|
if(line->alloc_size >= n + 1)
|
|
return true;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
while(p < endline) {
|
|
line->render_lines++;
|
|
p = drsize(p, NULL, width, NULL);
|
|
}
|
|
}
|
|
|
|
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
|
int show_from, int cursor)
|
|
{
|
|
char const *p = line->data;
|
|
char const *endline = p + strlen(p);
|
|
int line_offset = 0;
|
|
int line_number = 0;
|
|
|
|
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;
|
|
line_offset += len;
|
|
line_number++;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
//=== Terminal emulator ===//
|
|
|
|
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);
|
|
cons->render_needed = true;
|
|
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;
|
|
cons->render_needed = true;
|
|
|
|
/* 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;
|
|
cons->render_needed = true;
|
|
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);
|
|
}
|
|
}
|
|
|
|
cons->render_needed = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void console_render(int x, int y, console_t const *cons, int w, int dy,
|
|
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, dy, -watermark,
|
|
show_cursor ? cons->cursor : -1);
|
|
watermark += line->render_lines;
|
|
}
|
|
}
|
|
|
|
void console_clear_render_flag(console_t *cons)
|
|
{
|
|
cons->render_needed = false;
|
|
}
|
|
|
|
void console_destroy(console_t *cons)
|
|
{
|
|
for(int i = 0; i < cons->line_count; i++)
|
|
console_line_deinit(&cons->lines[i]);
|
|
free(cons->lines);
|
|
free(cons);
|
|
}
|
|
|
|
//=== Input method ===//
|
|
|
|
/* Features needed to bypass MicroPython's readline:
|
|
* Undefined MICROPY_HAL_HAS_VT100
|
|
* Multi-line input
|
|
- Provide PS1 and PS2
|
|
- Auto-indent
|
|
- TODO: How is it stored?
|
|
* History
|
|
- Let's use `MP_STATE_PORT(readline_hist)` for a start
|
|
- Keep track of history browsing state
|
|
- While at it do the zsh history search, which is goated
|
|
- Use readline_push_history()
|
|
* Cursor movement (fairly easy)
|
|
* Handle special inputs
|
|
- ^C, ^D, backspace
|
|
- Erase line (^K)
|
|
* Autocompletion
|
|
- Use mp_repl_autocomplete() which should hook just fine */
|
|
|
|
int console_key_event_to_char(key_event_t ev)
|
|
{
|
|
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; /* 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_EXIT)
|
|
return CHAR_CTRL_D; /* eof */
|
|
if(key == KEY_EXE)
|
|
return '\r';
|
|
|
|
return keymap_translate(key, ev.shift, ev.alpha);
|
|
}
|