Implement EDIT statement and ON TIMER/ON KEY event trapping, update to v0.9.0

Add event-driven programming: ON TIMER(n) GOSUB with TIMER ON/OFF/STOP,
ON KEY(n) GOSUB with KEY(n) ON/OFF/STOP for F1-F10. Fix F-key escape
sequence parser (F9/F10 detection, push back consumed bytes on unmatched
sequences). Add EDIT statement for TUI line editing. Guard key trap
polling so keystrokes aren't consumed when no traps are configured.
This commit is contained in:
Eremey Valetov
2026-02-27 17:29:09 -05:00
parent 5105ecafd6
commit 3fa8c6f034
14 changed files with 453 additions and 11 deletions
+4 -3
View File
@@ -23,7 +23,7 @@ Interactive mode launches the authentic GW-BASIC full-screen editor:
```
$ ./gwbasic
GW-BASIC 2026 0.8.0
GW-BASIC 2026 0.9.0
(C) Eremey Valetov 2026. MIT License.
Based on Microsoft GW-BASIC assembly source.
Ok
@@ -67,10 +67,11 @@ SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$
| Variables | LET, DIM, ERASE, SWAP, DEFINT/SNG/DBL/STR |
| Control flow | GOTO, GOSUB/RETURN, FOR/NEXT, IF/THEN/ELSE, WHILE/WEND, ON...GOTO/GOSUB |
| Input | INPUT, LINE INPUT, DATA/READ/RESTORE, INKEY$ |
| Program | RUN, RUN "file", CONT, STOP, END, NEW, LIST, CLEAR, AUTO, RENUM, DELETE |
| Program | RUN, RUN "file", CONT, STOP, END, NEW, LIST, CLEAR, AUTO, RENUM, DELETE, EDIT |
| Sequential I/O | OPEN, CLOSE, PRINT#, WRITE#, INPUT#, LINE INPUT# |
| Random-access I/O | FIELD, LSET, RSET, PUT, GET, CVI/CVS/CVD, MKI$/MKS$/MKD$ |
| Program I/O | SAVE, LOAD, MERGE, CHAIN, COMMON |
| Event trapping | ON TIMER(n) GOSUB, TIMER ON/OFF/STOP, ON KEY(n) GOSUB, KEY(n) ON/OFF/STOP |
| Error handling | ON ERROR GOTO, RESUME, ERROR, ERR, ERL |
| User functions | DEF FN, RANDOMIZE |
| File management | KILL, NAME, FILES, MKDIR, RMDIR, CHDIR, SHELL |
@@ -160,7 +161,7 @@ Key design differences from the original:
## Tests
54 test programs in `tests/programs/`, with CI via GitHub Actions:
56 test programs in `tests/programs/`, with CI via GitHub Actions:
```bash
bash tests/run_tests.sh
+3 -2
View File
@@ -12,10 +12,11 @@
| 0.6.0 | `ece018d` | DATE$/TIME$/TIMER, FILES, SHELL, CHDIR, MKDIR, RMDIR |
| 0.7.0 | `da6b513` | AUTO, RENUM (with GOTO/GOSUB patching), DELETE, COMMON, LIST range fix |
| 0.8.0 | `c68167c` | Dynamic TUI screen buffer, `--full` flag, LPRINT/LLIST with `--lpt` |
| 0.9.0 | | EDIT statement, ON TIMER/ON KEY event trapping, F-key escape parser fixes |
## Tests
54 test programs in `tests/programs/`. Run the full suite:
56 test programs in `tests/programs/`. Run the full suite:
```bash
bash tests/run_tests.sh
@@ -40,6 +41,6 @@ bash tests/run_compat.sh
## CI
GitHub Actions runs on every push to `main` and on pull requests. The workflow
builds the project with PulseAudio support and runs all 54 test programs.
builds the project with PulseAudio support and runs all 56 test programs.
See [`.github/workflows/ci.yml`](https://github.com/evvaletov/gw-basic-2026/blob/main/.github/workflows/ci.yml).
+1 -1
View File
@@ -37,7 +37,7 @@ Running `./gwbasic` with no arguments launches the full-screen editor:
```
$ ./gwbasic
GW-BASIC 2026 0.8.0
GW-BASIC 2026 0.9.0
(C) Eremey Valetov 2026. MIT License.
Based on Microsoft GW-BASIC assembly source.
Ok
+33 -1
View File
@@ -47,10 +47,11 @@ type suffixes (`%`, `!`, `#`)
| Variables | `LET`, `DIM`, `ERASE`, `SWAP`, `DEFINT`, `DEFSNG`, `DEFDBL`, `DEFSTR` |
| Control flow | `GOTO`, `GOSUB`/`RETURN`, `FOR`/`NEXT`, `IF`/`THEN`/`ELSE`, `WHILE`/`WEND`, `ON...GOTO`, `ON...GOSUB` |
| Input | `INPUT`, `LINE INPUT`, `DATA`/`READ`/`RESTORE`, `INKEY$` |
| Program control | `RUN`, `RUN "file"`, `CONT`, `STOP`, `END`, `NEW`, `LIST`, `CLEAR`, `AUTO`, `RENUM`, `DELETE` |
| Program control | `RUN`, `RUN "file"`, `CONT`, `STOP`, `END`, `NEW`, `LIST`, `CLEAR`, `AUTO`, `RENUM`, `DELETE`, `EDIT` |
| Sequential I/O | `OPEN`, `CLOSE`, `PRINT#`, `WRITE#`, `INPUT#`, `LINE INPUT#` |
| Random-access I/O | `FIELD`, `LSET`, `RSET`, `PUT`, `GET`, `CVI`/`CVS`/`CVD`, `MKI$`/`MKS$`/`MKD$` |
| Program I/O | `SAVE`, `LOAD`, `MERGE`, `CHAIN`, `COMMON` |
| Event trapping | `ON TIMER(n) GOSUB`, `TIMER ON`/`OFF`/`STOP`, `ON KEY(n) GOSUB`, `KEY(n) ON`/`OFF`/`STOP` |
| Error handling | `ON ERROR GOTO`, `RESUME`, `RESUME NEXT`, `RESUME n`, `ERROR`, `ERR`, `ERL` |
| User functions | `DEF FN`, `RANDOMIZE` |
| File management | `KILL`, `NAME`, `FILES`, `MKDIR`, `RMDIR`, `CHDIR`, `SHELL` |
@@ -145,6 +146,37 @@ suitable for scripting and test harnesses.
### Program Editing
- `EDIT [linenum]` — display a program line for editing in the TUI; press Enter to re-store it
- `AUTO [start][,increment]` — automatic line numbering mode
- `RENUM [new][,[old][,increment]]` — renumber program lines (patches all GOTO/GOSUB references)
- `DELETE range` — delete program lines (`DELETE 10-50`, `DELETE -100`, `DELETE 200-`)
## Event Trapping
GW-BASIC supports event-driven programming through trap handlers that fire
between statements during program execution.
### Timer Events
```
ON TIMER(n) GOSUB line ' register handler (fires every n seconds)
TIMER ON ' enable timer trapping
TIMER STOP ' suspend trapping (events are queued)
TIMER OFF ' disable trapping (events are discarded)
```
### Function Key Events
```
ON KEY(n) GOSUB line ' register handler for F-key n (1-10)
KEY(n) ON ' enable trapping for key n
KEY(n) STOP ' suspend trapping (events are queued)
KEY(n) OFF ' disable trapping
```
Event handlers execute as implicit GOSUBs. The `RETURN` statement returns
to the interrupted code and clears the handler's in-progress flag. Events do
not fire inside their own handler (re-entrant protection).
`TIMER STOP` / `KEY(n) STOP` queue events while stopped; switching to
`TIMER ON` / `KEY(n) ON` fires the pending event immediately.
-2
View File
@@ -2,8 +2,6 @@
## Planned Features
- **EDIT statement** — edit a specific program line in the TUI
- **ON KEY / ON TIMER event trapping** — periodic event handlers for interactive programs
- **BSAVE / BLOAD** — binary file save/load for screen buffers and data
- **DEF SEG** — memory segment declaration for PEEK/POKE/BSAVE/BLOAD
- **PRINT USING edge cases** — `**` asterisk fill, `**$` combined
+1 -1
View File
@@ -10,7 +10,7 @@
#include "gw_math.h"
#include "strings.h"
#define GW_VERSION "0.8.0"
#define GW_VERSION "0.9.0"
#define GW_BANNER "GW-BASIC " GW_VERSION
/* Tokenizer */
+4
View File
@@ -76,6 +76,10 @@ typedef struct {
/* COMMON variable list for CHAIN */
int common_count;
struct { char name[2]; gw_valtype_t type; } common_vars[64];
/* Event trapping */
timer_trap_t timer_trap;
event_trap_t key_traps[10]; /* KEY(1)-KEY(10) */
} interp_state_t;
extern interp_state_t gw;
+14
View File
@@ -45,6 +45,9 @@ typedef struct {
#define TK_CTRL_C 0x03
#define TK_CTRL_BREAK 0x200
/* Key buffer for event trapping (keys consumed from HAL but not yet processed) */
#define TUI_KEYBUF_SIZE 16
/* TUI state */
typedef struct {
tui_cell_t *screen; /* dynamically allocated [rows * cols] */
@@ -60,6 +63,9 @@ typedef struct {
volatile bool break_flag; /* set by SIGINT handler */
int view_top; /* top of scrollable area */
int view_bottom; /* bottom row (rows-1 normally, rows-2 with key bar) */
int keybuf[TUI_KEYBUF_SIZE]; /* ring buffer for pushed-back keys */
int keybuf_head;
int keybuf_tail;
} tui_state_t;
extern tui_state_t tui;
@@ -93,6 +99,14 @@ void tui_key_on(void);
void tui_key_off(void);
void tui_key_list(void);
/* EDIT statement */
void tui_edit_line(const char *prefill);
/* Key buffer for event trapping */
void tui_push_key(int key);
int tui_pop_key(void);
bool tui_keybuf_empty(void);
/* Ctrl+Break */
void tui_check_break(void);
void tui_install_break_handler(void);
+19
View File
@@ -64,11 +64,30 @@ typedef struct {
uint16_t line_num;
} for_entry_t;
/* Event trap mode */
typedef enum { TRAP_OFF = 0, TRAP_ON = 1, TRAP_STOP = 2 } trap_mode_t;
/* Event trap state (shared by TIMER/KEY traps) */
typedef struct event_trap {
trap_mode_t mode;
uint16_t gosub_line;
bool pending;
bool in_handler;
} event_trap_t;
/* ON TIMER trap */
typedef struct {
event_trap_t trap;
float interval; /* seconds */
double last_fire; /* monotonic timestamp */
} timer_trap_t;
/* GOSUB stack entry */
typedef struct {
uint8_t *ret_text;
struct program_line *ret_line;
uint16_t line_num;
struct event_trap *event_source; /* non-NULL for event handler GOSUB */
} gosub_entry_t;
/* WHILE stack entry */
+7 -1
View File
@@ -1,4 +1,5 @@
#include "gwbasic.h"
#include "tui.h"
#include "graphics.h"
#include <ctype.h>
#include <string.h>
@@ -879,7 +880,12 @@ static gw_value_t eval_atom(void)
gw_chrget();
gw_value_t v;
v.type = VT_STR;
if (gw_hal && gw_hal->kbhit()) {
/* Check key buffer first (keys pushed back by event trapping) */
if (!tui_keybuf_empty()) {
int ch = tui_pop_key();
v.sval = gw_str_alloc(1);
v.sval.data[0] = (char)ch;
} else if (gw_hal && gw_hal->kbhit()) {
int ch = gw_hal->getch();
v.sval = gw_str_alloc(1);
v.sval.data[0] = ch;
+286
View File
@@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
/*
* Execution loop and control flow - the heart of the interpreter.
@@ -1087,6 +1088,28 @@ void gw_exec_stmt(void)
free(path);
return;
}
/* TIMER ON/OFF/STOP */
if (xstmt == XSTMT_TIMER) {
gw_chrget();
gw_skip_spaces();
if (gw_chrgot() == TOK_ON) {
gw_chrget();
gw.timer_trap.trap.mode = TRAP_ON;
return;
}
if (gw_chrgot() == TOK_OFF) {
gw_chrget();
gw.timer_trap.trap.mode = TRAP_OFF;
gw.timer_trap.trap.pending = false;
return;
}
if (gw_chrgot() == TOK_STOP) {
gw_chrget();
gw.timer_trap.trap.mode = TRAP_STOP;
return;
}
gw_error(ERR_SN);
}
/* Stubs: VIEW, WINDOW, PALETTE */
if (xstmt == XSTMT_VIEW ||
xstmt == XSTMT_WINDOW || xstmt == XSTMT_PALETTE) {
@@ -1216,6 +1239,8 @@ void gw_exec_stmt(void)
gw.on_error_line = 0;
gw.in_error_handler = false;
gw.option_base = 0;
memset(&gw.timer_trap, 0, sizeof(gw.timer_trap));
memset(gw.key_traps, 0, sizeof(gw.key_traps));
gw.cur_line = start;
gw.text_ptr = start->tokens;
@@ -1287,6 +1312,40 @@ void gw_exec_stmt(void)
return;
}
/* EDIT [linenum] */
if (tok == TOK_EDIT) {
gw_chrget();
gw_skip_spaces();
uint16_t num;
if (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE) {
num = gw_eval_uint16();
} else if (gw.cont_line) {
num = gw.cont_line->num;
} else if (gw.err_line_num) {
num = gw.err_line_num;
} else {
gw_error(ERR_SN);
}
program_line_t *line = gw_find_line(num);
if (!line) gw_error(ERR_UL);
char listbuf[512];
gw_list_line(line->tokens, line->len, listbuf, sizeof(listbuf));
char formatted[560];
snprintf(formatted, sizeof(formatted), "%u %s", line->num, listbuf);
if (tui.active) {
tui_edit_line(formatted);
} else {
/* Non-interactive: just display the line */
if (gw_hal) {
gw_hal->puts(formatted);
gw_hal->puts("\n");
} else {
printf("%s\n", formatted);
}
}
return;
}
/* AUTO [start[,increment]] */
if (tok == TOK_AUTO) {
gw_chrget();
@@ -1521,6 +1580,7 @@ void gw_exec_stmt(void)
gw.gosub_stack[gw.gosub_sp].ret_text = gw.text_ptr;
gw.gosub_stack[gw.gosub_sp].ret_line = gw.cur_line;
gw.gosub_stack[gw.gosub_sp].line_num = gw.cur_line_num;
gw.gosub_stack[gw.gosub_sp].event_source = NULL;
gw.gosub_sp++;
gw.cur_line = target;
@@ -1539,6 +1599,10 @@ void gw_exec_stmt(void)
gw.cur_line = gw.gosub_stack[gw.gosub_sp].ret_line;
gw.cur_line_num = gw.gosub_stack[gw.gosub_sp].line_num;
/* Clear event handler flag if returning from event trap */
if (gw.gosub_stack[gw.gosub_sp].event_source)
gw.gosub_stack[gw.gosub_sp].event_source->in_handler = false;
/* Optional line number: RETURN <linenum> */
gw_skip_spaces();
if (gw_chrgot() >= TOK_INT2 && gw_chrgot() <= TOK_CONST_DBL) {
@@ -1859,6 +1923,49 @@ void gw_exec_stmt(void)
return;
}
/* ON TIMER(n) GOSUB line */
if (gw_chrgot() == TOK_PREFIX_FE && gw.text_ptr[1] == XSTMT_TIMER) {
gw.text_ptr += 2;
gw_expect('(');
gw_value_t v = gw_eval_num();
float interval;
if (v.type == VT_INT) interval = (float)v.ival;
else if (v.type == VT_SNG) interval = v.fval;
else interval = (float)v.dval;
if (interval <= 0) gw_error(ERR_FC);
gw_expect(')');
gw_skip_spaces();
if (gw_chrgot() != TOK_GOSUB) gw_error(ERR_SN);
gw_chrget();
uint16_t line = gw_eval_uint16();
gw.timer_trap.interval = interval;
gw.timer_trap.trap.gosub_line = line;
gw.timer_trap.trap.pending = false;
gw.timer_trap.trap.in_handler = false;
/* Reset the clock */
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
gw.timer_trap.last_fire = ts.tv_sec + ts.tv_nsec / 1e9;
return;
}
/* ON KEY(n) GOSUB line */
if (gw_chrgot() == TOK_KEY) {
gw_chrget();
gw_expect('(');
int n = gw_eval_int();
if (n < 1 || n > 10) gw_error(ERR_FC);
gw_expect(')');
gw_skip_spaces();
if (gw_chrgot() != TOK_GOSUB) gw_error(ERR_SN);
gw_chrget();
uint16_t line = gw_eval_uint16();
gw.key_traps[n - 1].gosub_line = line;
gw.key_traps[n - 1].pending = false;
gw.key_traps[n - 1].in_handler = false;
return;
}
int idx = gw_eval_int();
gw_skip_spaces();
@@ -1899,6 +2006,7 @@ void gw_exec_stmt(void)
gw.gosub_stack[gw.gosub_sp].ret_text = gw.text_ptr;
gw.gosub_stack[gw.gosub_sp].ret_line = gw.cur_line;
gw.gosub_stack[gw.gosub_sp].line_num = gw.cur_line_num;
gw.gosub_stack[gw.gosub_sp].event_source = NULL;
gw.gosub_sp++;
}
@@ -2277,6 +2385,31 @@ void gw_exec_stmt(void)
if (tok == TOK_KEY) {
gw_chrget();
gw_skip_spaces();
/* KEY(n) ON/OFF/STOP — event trapping */
if (gw_chrgot() == '(') {
gw_chrget();
int n = gw_eval_int();
if (n < 1 || n > 10) gw_error(ERR_FC);
gw_expect(')');
gw_skip_spaces();
if (gw_chrgot() == TOK_ON) {
gw_chrget();
gw.key_traps[n - 1].mode = TRAP_ON;
return;
}
if (gw_chrgot() == TOK_OFF) {
gw_chrget();
gw.key_traps[n - 1].mode = TRAP_OFF;
gw.key_traps[n - 1].pending = false;
return;
}
if (gw_chrgot() == TOK_STOP) {
gw_chrget();
gw.key_traps[n - 1].mode = TRAP_STOP;
return;
}
gw_error(ERR_SN);
}
if (gw_chrgot() == TOK_ON) {
gw_chrget();
tui_key_on();
@@ -2450,6 +2583,156 @@ void gw_exec_stmt(void)
gw_error(ERR_SN);
}
/* ================================================================
* Event Trapping
* ================================================================ */
static void fire_event_trap(event_trap_t *trap)
{
trap->in_handler = true;
trap->pending = false;
program_line_t *target = gw_find_line(trap->gosub_line);
if (!target) return;
if (gw.gosub_sp >= MAX_GOSUB_DEPTH)
gw_error(ERR_OM);
gw.gosub_stack[gw.gosub_sp].ret_text = gw.text_ptr;
gw.gosub_stack[gw.gosub_sp].ret_line = gw.cur_line;
gw.gosub_stack[gw.gosub_sp].line_num = gw.cur_line_num;
gw.gosub_stack[gw.gosub_sp].event_source = trap;
gw.gosub_sp++;
gw.cur_line = target;
gw.text_ptr = target->tokens;
gw.cur_line_num = target->num;
}
static void gw_check_events(void)
{
/* Timer trap */
if (gw.timer_trap.trap.gosub_line && !gw.timer_trap.trap.in_handler) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
double now = ts.tv_sec + ts.tv_nsec / 1e9;
double elapsed = now - gw.timer_trap.last_fire;
if (elapsed >= gw.timer_trap.interval) {
if (gw.timer_trap.trap.mode == TRAP_ON) {
gw.timer_trap.last_fire = now;
fire_event_trap(&gw.timer_trap.trap);
return;
} else if (gw.timer_trap.trap.mode == TRAP_STOP) {
gw.timer_trap.trap.pending = true;
}
}
}
/* Check for pending timer event after TIMER ON */
if (gw.timer_trap.trap.pending && gw.timer_trap.trap.mode == TRAP_ON &&
!gw.timer_trap.trap.in_handler) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
gw.timer_trap.last_fire = ts.tv_sec + ts.tv_nsec / 1e9;
fire_event_trap(&gw.timer_trap.trap);
return;
}
/* Key traps: only consume keystrokes when at least one trap is configured */
bool any_key_trap = false;
for (int i = 0; i < 10; i++) {
if (gw.key_traps[i].gosub_line) { any_key_trap = true; break; }
}
if (any_key_trap && gw_hal && gw_hal->kbhit()) {
int ch = gw_hal->getch();
int fkey = -1;
if (ch == 27 && gw_hal->kbhit()) {
int seq1 = gw_hal->getch();
if (seq1 == 'O') {
int seq2 = gw_hal->getch();
switch (seq2) {
case 'P': fkey = 0; break; /* F1 */
case 'Q': fkey = 1; break; /* F2 */
case 'R': fkey = 2; break; /* F3 */
case 'S': fkey = 3; break; /* F4 */
default:
tui_push_key(27);
tui_push_key('O');
tui_push_key(seq2);
return;
}
} else if (seq1 == '[') {
int seq2 = gw_hal->getch();
if ((seq2 == '1' || seq2 == '2') && gw_hal->kbhit()) {
int seq3 = gw_hal->getch();
if (gw_hal->kbhit()) {
int seq4 = gw_hal->getch();
if (seq4 == '~') {
int code = (seq2 - '0') * 10 + (seq3 - '0');
switch (code) {
case 15: fkey = 4; break; /* F5 */
case 17: fkey = 5; break; /* F6 */
case 18: fkey = 6; break; /* F7 */
case 19: fkey = 7; break; /* F8 */
case 20: fkey = 8; break; /* F9 */
case 21: fkey = 9; break; /* F10 */
}
}
if (fkey < 0) {
tui_push_key(27);
tui_push_key('[');
tui_push_key(seq2);
tui_push_key(seq3);
tui_push_key(seq4);
return;
}
} else {
tui_push_key(27);
tui_push_key('[');
tui_push_key(seq2);
tui_push_key(seq3);
return;
}
} else {
tui_push_key(27);
tui_push_key('[');
tui_push_key(seq2);
return;
}
} else {
tui_push_key(27);
tui_push_key(seq1);
return;
}
}
if (fkey >= 0 && fkey < 10) {
event_trap_t *kt = &gw.key_traps[fkey];
if (kt->gosub_line && kt->mode == TRAP_ON && !kt->in_handler) {
fire_event_trap(kt);
return;
} else if (kt->gosub_line && kt->mode == TRAP_STOP) {
kt->pending = true;
return;
}
return;
}
/* Not an F-key: push into key buffer for INKEY$/input */
tui_push_key(ch);
}
/* Check for pending key traps after KEY(n) ON */
for (int i = 0; i < 10; i++) {
event_trap_t *kt = &gw.key_traps[i];
if (kt->pending && kt->mode == TRAP_ON && !kt->in_handler) {
fire_event_trap(kt);
return;
}
}
}
/* ================================================================
* NEWSTT Loop (Run Loop)
* ================================================================ */
@@ -2471,6 +2754,9 @@ void gw_run_loop(void)
if (tui.active)
tui_check_break();
/* Check event traps (ON TIMER, ON KEY) */
gw_check_events();
gw_skip_spaces();
uint8_t ch = gw_chrgot();
+53
View File
@@ -167,6 +167,11 @@ void tui_update_cursor(void)
int tui_read_key(void)
{
/* Drain key buffer first (keys pushed back by event trapping) */
int buffered = tui_pop_key();
if (buffered >= 0)
return buffered;
gw_hal->enable_raw();
int ch = gw_hal->getch();
@@ -484,6 +489,54 @@ void tui_key_list(void)
tui_update_cursor();
}
/* Key buffer ring buffer operations */
void tui_push_key(int key)
{
int next = (tui.keybuf_head + 1) % TUI_KEYBUF_SIZE;
if (next == tui.keybuf_tail)
return; /* buffer full, drop key */
tui.keybuf[tui.keybuf_head] = key;
tui.keybuf_head = next;
}
int tui_pop_key(void)
{
if (tui.keybuf_head == tui.keybuf_tail)
return -1;
int key = tui.keybuf[tui.keybuf_tail];
tui.keybuf_tail = (tui.keybuf_tail + 1) % TUI_KEYBUF_SIZE;
return key;
}
bool tui_keybuf_empty(void)
{
return tui.keybuf_head == tui.keybuf_tail;
}
void tui_edit_line(const char *prefill)
{
/* Advance to new line */
tui.cursor_col = 0;
tui.cursor_row++;
if (tui.cursor_row > tui.view_bottom) {
scroll_up();
tui.cursor_row = tui.view_bottom;
}
/* Write prefill text into screen buffer */
for (int i = 0; prefill[i] && i < tui.cols; i++) {
TUI_CELL(tui.cursor_row, i).ch = (uint8_t)prefill[i];
TUI_CELL(tui.cursor_row, i).attr = tui.current_attr;
}
tui_refresh_row(tui.cursor_row);
tui_update_cursor();
char *result = tui_read_line();
if (result && result[0])
gw_exec_direct(result);
}
void tui_set_cursor_block(void)
{
printf("\033[1 q");
+12
View File
@@ -0,0 +1,12 @@
10 REM ON TIMER event trapping test
20 COUNT = 0
30 ON TIMER(1) GOSUB 100
40 TIMER ON
50 T0 = TIMER
60 IF TIMER - T0 < 3 THEN 60
70 TIMER OFF
80 PRINT "Timer fired"; COUNT; "times"
90 IF COUNT >= 2 THEN PRINT "PASS" ELSE PRINT "FAIL"
95 END
100 COUNT = COUNT + 1
110 RETURN
+16
View File
@@ -0,0 +1,16 @@
10 REM TIMER STOP queues events, TIMER ON fires pending
20 COUNT = 0
30 ON TIMER(1) GOSUB 200
40 TIMER STOP
50 T0 = TIMER
60 IF TIMER - T0 < 2 THEN 60
70 PRINT "Before TIMER ON: count ="; COUNT
80 TIMER ON
90 REM Small busy-wait for pending event to fire
95 FOR I = 1 TO 10000: NEXT I
100 PRINT "After TIMER ON: count ="; COUNT
110 TIMER OFF
120 IF COUNT >= 1 THEN PRINT "PASS" ELSE PRINT "FAIL"
130 END
200 COUNT = COUNT + 1
210 RETURN