forked from Lephenixnoir/PythonExtra
Compare commits
4 Commits
b664e0ab9c
...
d7d1df46bb
Author | SHA1 | Date |
---|---|---|
Lephenixnoir | d7d1df46bb | |
Lephenixnoir | 40a2de1a5f | |
Lephenixnoir | 8f5af62f9f | |
Lephenixnoir | edf57c6f07 |
|
@ -20,6 +20,8 @@ Bugs to fix:
|
|||
- Fix not resetting the shell when importing a file from command-line
|
||||
- Fix current working directory not changing during a module import (for
|
||||
relative imports)
|
||||
- Fix accumulated events being processed when the program "ends" (if we can
|
||||
detect that)
|
||||
|
||||
Python features:
|
||||
- Compare features with existing implementations and other brands
|
||||
|
|
|
@ -103,7 +103,7 @@ void console_line_update_render_lines(console_line_t *line, int width)
|
|||
}
|
||||
|
||||
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
||||
int show_from, int cursor)
|
||||
int show_from, int show_until, int cursor)
|
||||
{
|
||||
char const *p = line->data;
|
||||
char const *endline = p + strlen(p);
|
||||
|
@ -114,7 +114,7 @@ int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
|||
char const *endscreen = drsize(p, NULL, w, NULL);
|
||||
int len = endscreen - p;
|
||||
|
||||
if(line_number >= show_from) {
|
||||
if(line_number >= show_from && line_number < show_until) {
|
||||
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;
|
||||
|
@ -139,8 +139,16 @@ console_t *console_create(int backlog_size)
|
|||
console_t *cons = malloc(sizeof *cons);
|
||||
cons->lines = NULL;
|
||||
cons->line_count = 0;
|
||||
|
||||
cons->absolute_lineno = 1;
|
||||
cons->backlog_size = max(backlog_size, PE_CONSOLE_LINE_MAX_LENGTH);
|
||||
|
||||
cons->render_needed = true;
|
||||
cons->render_font = NULL;
|
||||
cons->render_width = 0;
|
||||
cons->render_lines = 0;
|
||||
cons->render_total_lines = 0;
|
||||
|
||||
if(!console_new_line(cons)) {
|
||||
free(cons);
|
||||
return NULL;
|
||||
|
@ -169,49 +177,39 @@ bool console_new_line(console_t *cons)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* static console_line_t *console_get_absolute_line(console_t const *cons,
|
||||
int lineno)
|
||||
{
|
||||
int i = lineno - cons->absolute_lineno;
|
||||
return (i >= 0 && i < cons->line_count) ? &cons->lines[i] : NULL;
|
||||
} */
|
||||
|
||||
void console_clean_backlog(console_t *cons)
|
||||
{
|
||||
int total_size = 0, i = cons->line_count;
|
||||
/* Find how many lines we can keep while using at most cons->backlog_size
|
||||
bytes to store them. The console must always have at least one line. */
|
||||
int first_kept = cons->line_count;
|
||||
int line_size = 0;
|
||||
int total_size = 0;
|
||||
|
||||
/* Determine how many lines (starting from the end) we can fit */
|
||||
while(total_size < cons->backlog_size) {
|
||||
i--;
|
||||
total_size += cons->lines[i].size;
|
||||
do {
|
||||
total_size += line_size;
|
||||
first_kept--;
|
||||
line_size = cons->lines[first_kept].size;
|
||||
}
|
||||
while(first_kept > 0 && total_size + line_size <= cons->backlog_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++)
|
||||
/* Remove old lines */
|
||||
for(int j = 0; j < first_kept; 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;
|
||||
}
|
||||
memmove(cons->lines, cons->lines + first_kept,
|
||||
(cons->line_count - first_kept) * sizeof cons->lines[0]);
|
||||
cons->line_count -= first_kept;
|
||||
|
||||
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;
|
||||
}
|
||||
/* Update the absolute number of the first line */
|
||||
cons->absolute_lineno += first_kept;
|
||||
}
|
||||
|
||||
void console_clear_render_flag(console_t *cons)
|
||||
|
@ -227,6 +225,104 @@ void console_destroy(console_t *cons)
|
|||
free(cons);
|
||||
}
|
||||
|
||||
//=== Rendering functions ===//
|
||||
|
||||
/* Compute the shell's horizontal layout as:
|
||||
1. Text width (region where the text is renderer)
|
||||
2. Spacing left of the scrollbar
|
||||
3. Width of the scrollbar */
|
||||
static void view_params(int w,
|
||||
int *text_w, int *scroll_spacing, int *scroll_width)
|
||||
{
|
||||
#ifdef FX9860G
|
||||
if(text_w)
|
||||
*text_w = w - 2;
|
||||
if(scroll_spacing)
|
||||
*scroll_spacing = 1;
|
||||
if(scroll_width)
|
||||
*scroll_width = 1;
|
||||
#else
|
||||
if(text_w)
|
||||
*text_w = w - 4;
|
||||
if(scroll_spacing)
|
||||
*scroll_spacing = 2;
|
||||
if(scroll_width)
|
||||
*scroll_width = 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void console_compute_view(console_t *cons, font_t const *font,
|
||||
int width, int lines)
|
||||
{
|
||||
cons->render_font = font;
|
||||
cons->render_width = width;
|
||||
cons->render_lines = lines;
|
||||
cons->render_total_lines = 0;
|
||||
|
||||
int text_w;
|
||||
view_params(width, &text_w, NULL, NULL);
|
||||
|
||||
for(int i = 0; i < cons->line_count; i++) {
|
||||
console_line_update_render_lines(&cons->lines[i], text_w);
|
||||
cons->render_total_lines += cons->lines[i].render_lines;
|
||||
}
|
||||
}
|
||||
|
||||
console_scrollpos_t console_clamp_scrollpos(console_t const *cons,
|
||||
console_scrollpos_t pos)
|
||||
{
|
||||
/* No scrolling case */
|
||||
if(cons->render_total_lines < cons->render_lines)
|
||||
return 0;
|
||||
|
||||
/* Normal clamp */
|
||||
return max(0, min(pos, cons->render_total_lines - cons->render_lines));
|
||||
}
|
||||
|
||||
void console_render(int x, int y0, console_t const *cons, int dy,
|
||||
console_scrollpos_t pos)
|
||||
{
|
||||
int total_lines = cons->render_total_lines;
|
||||
int visible_lines = cons->render_lines;
|
||||
int w = cons->render_width;
|
||||
int watermark = visible_lines - total_lines + pos;
|
||||
int y = y0;
|
||||
|
||||
int text_w, scroll_spacing, scroll_w;
|
||||
view_params(w, &text_w, &scroll_spacing, &scroll_w);
|
||||
|
||||
font_t const *old_font = dfont(cons->render_font);
|
||||
|
||||
/* 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, text_w, dy, -watermark,
|
||||
visible_lines - watermark, show_cursor ? cons->cursor : -1);
|
||||
watermark += line->render_lines;
|
||||
}
|
||||
|
||||
dfont(old_font);
|
||||
|
||||
/* Scrollbar */
|
||||
if(total_lines > visible_lines) {
|
||||
int first_shown = total_lines - visible_lines - pos;
|
||||
int h = dy * visible_lines;
|
||||
int y1 = y0 + h * first_shown / total_lines;
|
||||
int y2 = y0 + h * (first_shown + visible_lines) / total_lines;
|
||||
|
||||
int color = C_BLACK;
|
||||
#ifdef FXCG50
|
||||
if(pos == 0) color = C_RGB(24, 24, 24);
|
||||
#endif
|
||||
drect(x + text_w + scroll_spacing, y1,
|
||||
x + text_w + scroll_spacing + scroll_w - 1, y2,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
//=== Edition functions ===//
|
||||
|
||||
bool console_write_block_at_cursor(console_t *cons, char const *str, int n)
|
||||
|
|
|
@ -12,12 +12,18 @@
|
|||
// * Dynamically-sized lines with reflow
|
||||
// * Cap memory usage based on the total amount of text, not just line count
|
||||
// * Basic ANSI-escape-based edition features (but only on the last line)
|
||||
//
|
||||
// The console tries fairly hard to focus on text manipulation and separate
|
||||
// rendering. To render, one must first compute a "view" of the terminal, which
|
||||
// essentially determines line wrapping and scrolling bounds, and then use that
|
||||
// view and a valid scroll position within it to render.
|
||||
//---
|
||||
|
||||
#ifndef __PYTHONEXTRA_CONSOLE_H
|
||||
#define __PYTHONEXTRA_CONSOLE_H
|
||||
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/display.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Maximum line length, to ensure the console can threshold its memory usage
|
||||
|
@ -62,6 +68,10 @@ 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);
|
||||
|
||||
/* Render a vertical slice of the wrapped line. */
|
||||
int console_line_render(int x, int y, console_line_t *line, int w, int dy,
|
||||
int show_from, int show_until, int cursor);
|
||||
|
||||
//=== Terminal emulator ===//
|
||||
|
||||
typedef struct
|
||||
|
@ -75,14 +85,26 @@ typedef struct
|
|||
than PE_CONSOLE_LINE_MAX_LENGTH. */
|
||||
int backlog_size;
|
||||
|
||||
/* Absolute number of the first line in the dynamic line array. This is
|
||||
the line number in the user-facing sense (it includes lines that were
|
||||
cleaned up to free memory). */
|
||||
int absolute_lineno;
|
||||
/* Cursor position within the last line. */
|
||||
int cursor;
|
||||
|
||||
/* Whether new data has been added and a frame should be rendered. */
|
||||
bool render_needed;
|
||||
/* View parameters from last console_compute_view() */
|
||||
font_t const *render_font;
|
||||
int render_width;
|
||||
int render_lines;
|
||||
int render_total_lines;
|
||||
|
||||
} console_t;
|
||||
|
||||
/* Scroll position measured as a number of lines up from the bottom. */
|
||||
typedef int console_scrollpos_t;
|
||||
|
||||
/* Create a new console with the specified backlog size. */
|
||||
console_t *console_create(int backlog_size);
|
||||
|
||||
|
@ -93,15 +115,33 @@ bool console_new_line(console_t *cons);
|
|||
/* Clean up backlog if the total memory usage is exceeded. */
|
||||
void console_clean_backlog(console_t *cons);
|
||||
|
||||
/* Render the console with the current font, at (x,y) in a rectangle of width
|
||||
`w` and `lines` total lines separated by `dy` pixels. */
|
||||
void console_render(int x, int y, console_t const *cons, int w, int dy,
|
||||
int lines);
|
||||
|
||||
/* Clear the console's render flag, which is used to notify of changes. This
|
||||
function is used when handing control of the display from the console to a
|
||||
Python program so the console doesn't override the program's output. */
|
||||
void console_clear_render_flag(console_t *cons);
|
||||
|
||||
/* Destroy the console and free all associated memory. */
|
||||
void console_destroy(console_t *cons);
|
||||
|
||||
//=== Rendering interface ===//
|
||||
|
||||
/* Compute a view of the console for rendering and scrolling.
|
||||
@font Font to render text with (use to compute line wrapping)
|
||||
@width View width in pixels
|
||||
@lines Number of text lines (spacing can be changed later) */
|
||||
void console_compute_view(console_t *cons, font_t const *font,
|
||||
int width, int lines);
|
||||
|
||||
/* Clamp a scrolling position to the range valid of the last computed view. */
|
||||
console_scrollpos_t console_clamp_scrollpos(console_t const *cons,
|
||||
console_scrollpos_t pos);
|
||||
|
||||
/* Render the console at (x,y). The render `width`, the number of `lines` and
|
||||
the text `font` are all as specified by the latest console_compute_view().
|
||||
`dy` indicates line height. */
|
||||
void console_render(int x, int y, console_t const *cons, int dy,
|
||||
console_scrollpos_t pos);
|
||||
|
||||
//=== Edition functions ===//
|
||||
|
||||
/* Write string at the cursor's position within the last line. This writes a
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
#include <gint/kmalloc.h>
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
#include <gint/timer.h>
|
||||
#include <gint/cpu.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "py/mpstate.h"
|
||||
|
||||
void pe_debug_panic(char const *msg)
|
||||
{
|
||||
|
@ -23,44 +25,53 @@ void pe_debug_panic(char const *msg)
|
|||
|
||||
#if PE_DEBUG
|
||||
|
||||
#if 0 // Timeout fxlink not supported yet
|
||||
static bool timeout_popup(void)
|
||||
{
|
||||
dclear(C_BLACK);
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 - 21, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_MIDDLE, "An fxlink message timed out!");
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 - 7, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_MIDDLE, "Start fxlink and press [EXE]:");
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 + 7, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_MIDDLE, "% fxlink -iqw");
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 + 21, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_MIDDLE, "or press [EXIT] to drop the message");
|
||||
dupdate();
|
||||
|
||||
while(1) {
|
||||
int key = getkey().key;
|
||||
if(key == KEY_EXE)
|
||||
return false;
|
||||
if(key == KEY_EXIT)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void pe_debug_init(void)
|
||||
{
|
||||
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
|
||||
usb_open(intf, GINT_CALL_NULL);
|
||||
|
||||
dclear(C_BLACK);
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 - 3, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_BOTTOM, "Waiting for USB connection...");
|
||||
dtext_opt(DWIDTH/2, DHEIGHT/2 + 3, C_WHITE, C_NONE, DTEXT_MIDDLE,
|
||||
DTEXT_TOP, "% fxlink -iqw");
|
||||
dupdate();
|
||||
|
||||
while(!usb_is_open()) sleep();
|
||||
usb_open_wait();
|
||||
}
|
||||
|
||||
void pe_debug_printf(char const *fmt, ...)
|
||||
int pe_debug_printf(char const *fmt, ...)
|
||||
{
|
||||
char str_default[256];
|
||||
|
||||
char str[512];
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
char *str;
|
||||
vasprintf(&str, fmt, args);
|
||||
int rc = vsnprintf(str, sizeof str, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if(str == NULL) {
|
||||
va_start(args, fmt);
|
||||
str = str_default;
|
||||
vsnprintf(str, sizeof str_default, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
if(usb_is_open())
|
||||
usb_fxlink_text(str, 0);
|
||||
usb_open_wait();
|
||||
usb_fxlink_text(str, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* This function is used in MicroPython. */
|
||||
void DEBUG_printf(char const *fmt, ...)
|
||||
int DEBUG_printf(char const *fmt, ...)
|
||||
__attribute__((alias("pe_debug_printf")));
|
||||
|
||||
void pe_debug_kmalloc(void)
|
||||
|
@ -78,8 +89,8 @@ void pe_debug_kmalloc(void)
|
|||
|
||||
void pe_debug_screenshot(void)
|
||||
{
|
||||
if(usb_is_open())
|
||||
usb_fxlink_screenshot(true);
|
||||
usb_open_wait();
|
||||
usb_fxlink_screenshot(true);
|
||||
}
|
||||
|
||||
#endif /* PE_DEBUG */
|
||||
|
|
|
@ -25,7 +25,7 @@ __attribute__((noreturn));
|
|||
|
||||
/* Print to the debug stream. This function is also called DEBUG_printf in
|
||||
MicroPython code. */
|
||||
void pe_debug_printf(char const *fmt, ...);
|
||||
int pe_debug_printf(char const *fmt, ...);
|
||||
|
||||
/* Print information about allocation status. */
|
||||
void pe_debug_kmalloc(void);
|
||||
|
|
|
@ -113,13 +113,17 @@ static char *path_to_module(char const *path)
|
|||
interrupt MicroPython instead. */
|
||||
static bool async_filter(key_event_t ev)
|
||||
{
|
||||
if(mp_interrupt_char < 0)
|
||||
return true;
|
||||
if(ev.type == KEYEV_DOWN && ev.key == KEY_ACON) {
|
||||
/* This function is designed to be called asynchronously. */
|
||||
mp_sched_keyboard_interrupt();
|
||||
/* Gobble all events related to AC/ON to make sure that the keyboard driver
|
||||
treats them as handled. Otherwise, we'd run the risk of filling the
|
||||
event queue (if the user doesn't read from it) thus preventing the
|
||||
driver from handling AC/ON releases, which disables further presses. */
|
||||
if(mp_interrupt_char >= 0 && ev.key == KEY_ACON) {
|
||||
/* This function supports asynchronous calls, by design. */
|
||||
if(ev.type == KEYEV_DOWN)
|
||||
mp_sched_keyboard_interrupt();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ widget_shell *widget_shell_create(console_t *console, void *parent)
|
|||
s->font = dfont_default();
|
||||
s->color = C_BLACK;
|
||||
s->line_spacing = 0;
|
||||
s->scroll = 0;
|
||||
s->lines = 0;
|
||||
|
||||
s->shift = MOD_IDLE;
|
||||
|
@ -167,11 +168,11 @@ static void widget_shell_poly_render(void *s0, int x, int y)
|
|||
widget_shell *s = s0;
|
||||
int line_height = s->font->line_height + s->line_spacing;
|
||||
|
||||
font_t const *old_font = dfont(s->font);
|
||||
console_render(x, y, s->console, jwidget_content_width(s), line_height,
|
||||
console_compute_view(s->console, s->font, jwidget_content_width(s),
|
||||
s->lines);
|
||||
s->scroll = console_clamp_scrollpos(s->console, s->scroll);
|
||||
console_render(x, y, s->console, line_height, s->scroll);
|
||||
console_clear_render_flag(s->console);
|
||||
dfont(old_font);
|
||||
}
|
||||
|
||||
static void widget_shell_update_mod(widget_shell *s, key_event_t ev)
|
||||
|
@ -206,6 +207,16 @@ static bool widget_shell_poly_event(void *s0, jevent e)
|
|||
widget_shell_update_mod(s, ev);
|
||||
return true;
|
||||
}
|
||||
if(ev.type != KEYEV_UP && ev.key == KEY_UP) {
|
||||
s->scroll++;
|
||||
s->widget.update = true;
|
||||
return true;
|
||||
}
|
||||
if(ev.type != KEYEV_UP && ev.key == KEY_DOWN) {
|
||||
s->scroll--;
|
||||
s->widget.update = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ev.type == KEYEV_UP)
|
||||
return false;
|
||||
|
|
|
@ -56,6 +56,8 @@ typedef struct {
|
|||
uint16_t color;
|
||||
/* Extra line spacing */
|
||||
int8_t line_spacing;
|
||||
/* Current scroll position */
|
||||
console_scrollpos_t scroll;
|
||||
|
||||
/* Internal information */
|
||||
int timer_id;
|
||||
|
|
Loading…
Reference in New Issue