diff --git a/README.md b/README.md index e432396fe..7f6ad1c45 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ Most of the code is in `ports/sh` and is shared between the platforms. **TODO list** Bugs to fix: -- Don't refresh screen at every low-level console print - Fix not world switching during filesystem accesses (very unstable) Python features: @@ -25,6 +24,8 @@ Python features: - Interrupt with AC/ON UI: +- Shell escapes: move cursor, history +- Better input system in the shell - Use [JustUI](/Lephenixnoir/JustUI) to get a file browser (already available) - Add an option for fixed-width font which also sets $COLUMNS properly so that MicroPython paginates (requires better getenv/setenv support in fxlib) diff --git a/ports/sh/Makefile b/ports/sh/Makefile index 90e4db887..f5223a9c7 100644 --- a/ports/sh/Makefile +++ b/ports/sh/Makefile @@ -15,6 +15,7 @@ SRC_C = \ ports/sh/keymap.c \ ports/sh/modgint.c \ ports/sh/mphalport.c \ + ports/sh/shell.c \ shared/readline/readline.c \ shared/runtime/gchelper_generic.c \ shared/runtime/pyexec.c \ diff --git a/ports/sh/main.c b/ports/sh/main.c index 3d18f9bfc..4ae0eee57 100644 --- a/ports/sh/main.c +++ b/ports/sh/main.c @@ -13,7 +13,9 @@ #include #include #include + #include "console.h" +#include "shell.h" //=== Console-based standard streams ===// @@ -33,33 +35,15 @@ fs_descriptor_type_t stdouterr_type = { //=== Main function ===// -static console_t *cons = NULL; - -void pe_draw(void) -{ - dclear(C_WHITE); -#ifdef FX9860G - int rows = 8; - console_render(1, 1, cons, DWIDTH-2, rows); -#else - 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); -#endif - dupdate(); -} - int main(int argc, char **argv) { + pe_shell_init(); + /* Set up standard streams */ - cons = console_create(8192); close(STDOUT_FILENO); close(STDERR_FILENO); - open_generic(&stdouterr_type, cons, STDOUT_FILENO); - open_generic(&stdouterr_type, cons, STDERR_FILENO); + open_generic(&stdouterr_type, pe_shell_console, STDOUT_FILENO); + open_generic(&stdouterr_type, pe_shell_console, STDERR_FILENO); /* Initialize MicroPython */ #define HEAP_SIZE 32768 @@ -86,11 +70,10 @@ int main(int argc, char **argv) // Start a normal REPL; will exit when ctrl-D is entered on a blank line. pyexec_friendly_repl(); - console_destroy(cons); - // Deinitialise the runtime. gc_sweep_all(); mp_deinit(); + pe_shell_deinit(); return 0; } diff --git a/ports/sh/modgint.c b/ports/sh/modgint.c index 5c4a644c4..a0e33fe89 100644 --- a/ports/sh/modgint.c +++ b/ports/sh/modgint.c @@ -2,6 +2,7 @@ #include "py/objtuple.h" #include #include +#include "shell.h" #define FUN_0(NAME) \ MP_DEFINE_CONST_FUN_OBJ_0(modgint_ ## NAME ## _obj, modgint_ ## NAME) @@ -121,6 +122,7 @@ STATIC mp_obj_t modgint_dclear(mp_obj_t arg1) STATIC mp_obj_t modgint_dupdate(void) { + pe_shell_graphics_mode(); dupdate(); return mp_const_none; } diff --git a/ports/sh/mphalport.c b/ports/sh/mphalport.c index 08e6461c6..b1e29600c 100644 --- a/ports/sh/mphalport.c +++ b/ports/sh/mphalport.c @@ -4,11 +4,13 @@ #include "../../shared/readline/readline.h" #include "keymap.h" +#include "shell.h" #include #include // Receive single character, blocking until one is available. -int mp_hal_stdin_rx_chr(void) { +int mp_hal_stdin_rx_chr(void) +{ while(1) { key_event_t ev = getkey(); int key = ev.key; @@ -44,10 +46,10 @@ int mp_hal_stdin_rx_chr(void) { } // Send the string of given length. -void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { +void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) +{ int r = write(STDOUT_FILENO, str, len); (void)r; - extern void pe_draw(void); - pe_draw(); + pe_shell_schedule_update(); } diff --git a/ports/sh/shell.c b/ports/sh/shell.c new file mode 100644 index 000000000..073fd4daa --- /dev/null +++ b/ports/sh/shell.c @@ -0,0 +1,63 @@ +#include "shell.h" +#include +#include + +/* The global terminal. */ +console_t *pe_shell_console; +/* Timer handle for the refresh clock. */ +static int pe_shell_timer = -1; +/* Whether there is new shell data to be shown on the next frame. */ +static bool pe_shell_update = true; + +static void pe_shell_draw(void) +{ + dclear(C_WHITE); + +#ifdef FX9860G + int rows = 8; + console_render(1, 1, pe_shell_console, DWIDTH-2, rows); +#else + dprint(3, 3, C_BLACK, "PythonExtra, very much WIP :)"); + dline(2, 16, DWIDTH-3, 16, C_BLACK); + int rows = 12; + console_render(3, 20, pe_shell_console, DWIDTH-6, rows); + int y = 20 + PE_CONSOLE_LINE_SPACING * rows; + dline(2, y, DWIDTH-3, y, C_BLACK); +#endif + + dupdate(); +} + +static int pe_shell_timer_handler(void) +{ + if(pe_shell_update) { + pe_shell_draw(); + pe_shell_update = false; + } + return TIMER_CONTINUE; +} + +void pe_shell_schedule_update(void) +{ + pe_shell_update = true; +} + +void pe_shell_graphics_mode(void) +{ + pe_shell_update = false; +} + +void pe_shell_init(void) +{ + pe_shell_console = console_create(8192); + + pe_shell_timer = timer_configure(TIMER_ANY, 1000000 / PE_SHELL_FPS, + GINT_CALL(pe_shell_timer_handler)); + timer_start(pe_shell_timer); +} + +void pe_shell_deinit(void) +{ + timer_stop(pe_shell_timer); + console_destroy(pe_shell_console); +} diff --git a/ports/sh/shell.h b/ports/sh/shell.h new file mode 100644 index 000000000..8ea5ece20 --- /dev/null +++ b/ports/sh/shell.h @@ -0,0 +1,46 @@ +// pe.shell: Shell integration +// +// This header covers application-integration aspects of the shell. For the +// implementation of the console itself (input, escape sequences, etc). see +// instead. +// +// The shell consists of a terminal-emulating `console_t` along with some input +// UI. During execution, the shell is redrawn only (1) when new data is printed +// or requested, and (2) a fixed refresh timer hits. Condition 1 avoids +// needless updates for non-printing programs, while condition 2 prevents +// performance issues for sequences that print one character at a time (which +// is quite common even within MicroPython builtins). +// +// If the built-in function gint.dupdate() is used, the shell also hands over +// display updates to "graphics mode", where only manual dupdate() calls push +// the VRAM. Switching to graphics mode cancels any scheduled shell updates. +// Graphics mode is exited once a new shell update is explicity scheduled, +// typically by a call to print() or input(). +//--- + +#ifndef __PYTHONEXTRA_SHELL_H +#define __PYTHONEXTRA_SHELL_H + +#include "console.h" + +/* Shell frame frequency, ie. cap on the number of shell redraws per second. */ +#define PE_SHELL_FPS 30 + +/* The terminal. */ +extern console_t *pe_shell_console; + +/* Schedule a redraw of the shell interface at the next shell frame. This is + called whenever data is printed into the shell or input() is used. The + scheduled redraw might be canceled by switching to graphics mode before the + frame timer fires. */ +void pe_shell_schedule_update(void); + +/* Switch to graphics mode. This cancels pending shell updates. */ +void pe_shell_graphics_mode(void); + +//=== Internal functions ===// + +void pe_shell_init(void); +void pe_shell_deinit(void); + +#endif /* __PYTHONEXTRA_SHELL_H */