Ver Fonte

editor: the command keys from a text file (TP3.KEY)

The other half of what TINST configures - without patching the binary.  The
editor's single-key command map is now data:

  - Keys (new): TP3.KEY, one NAME=value per line, values decoded exactly like
    TP3.TRM's (\e, \r, \n, \t, \\, ^X) and required to be ONE byte.  Unknown
    names ignored; missing/invalid file leaves the defaults, which are the keys
    the editor used before there was a key file (so behaviour does not move).
  - Editor.MainLoop dispatches on keys.quit / keys.charLeft / ... instead of
    C_Q / C_S / ..., so the whole single-key table - cursor, insert/delete,
    block prefix, literal, repeat, auto-tab - is configurable.  `Del' and `^J'
    remain aliases of DELETELEFT/NEWLINE and are not configurable in this slice.
  - shell/TP3.KEY is the committed default map.
  - KeysTest + key_default/key_custom fixtures assert the loader (including an
    unknown key being ignored) byte for byte; tests/term_config.sh now runs
    four cases: term_ansi, term_vt52, key_default, key_custom.

NOT in this slice: the Ctrl-K sub-commands (KCommand) and the Esc/ANSI cursor
sequences (HandleEsc) are still hard-coded; Keys is the pattern to extend to
them.  TERMINAL.md says so.

run_all.sh: ALL PASS - compile matrix 71/71, terminal config 4/4, execution
60/60 under qemu and Exec86, and all three UI paths (the editor behaves
identically with the default map).
Eric Streit há 2 dias atrás
pai
commit
35d5012135

+ 1 - 0
.gitignore

@@ -25,3 +25,4 @@ tmp/
 
 
 # terminal-config test harness (tests/term_config.sh)
 # terminal-config test harness (tests/term_config.sh)
 shell/tests/termcfgtest
 shell/tests/termcfgtest
+shell/tests/keycfgtest

+ 4 - 3
SUMMARY.md

@@ -2074,9 +2074,10 @@ change. Items 1-4 below are independent of both.
 
 
 5. Terminal configuration is a **text file** now, not a patched binary:
 5. Terminal configuration is a **text file** now, not a patched binary:
    `shell/TP3.TRM` is read by `tpshell` at startup, and `tp3cfg` writes it
    `shell/TP3.TRM` is read by `tpshell` at startup, and `tp3cfg` writes it
-   (ANSI/VT100/VT52 so far). See `TERMINAL.md`. `tests/term_config.sh` asserts
-   the loader/decoder byte for byte. Key remapping (the rest of TINST) is a
-   separate step.
+   (ANSI/VT100/VT52 so far). The editor's **single-key command map** is
+   `TP3.KEY`, read by `Editor.Run` (defaults = the old hard-coded keys); the
+   Ctrl-K sub-commands and Esc/ANSI sequences are the remaining slice. See
+   `TERMINAL.md`; `tests/term_config.sh` asserts both loaders byte for byte.
 
 
 The 4 KiB code window is no longer a documented limitation but an enforced
 The 4 KiB code window is no longer a documented limitation but an enforced
 one (`v-TP3-CODEWINDOW`): `Ebyte` reports TP3's error 98 when the code
 one (`v-TP3-CODEWINDOW`): `Ebyte` reports TP3's error 98 when the code

+ 53 - 5
TERMINAL.md

@@ -61,6 +61,54 @@ A missing `TP3.TRM` means the built-in ANSI defaults (the sequences the shell
 had before there was a config). A malformed line leaves the defaults (and
 had before there was a config). A malformed line leaves the defaults (and
 anything read before it) in place; it never silently half-configure the screen.
 anything read before it) in place; it never silently half-configure the screen.
 
 
+## The editor's keys: `TP3.KEY`
+
+The same design extends to the editor's command keys. `Editor.Run` reads
+`TP3.KEY` (or keeps the built-in defaults) and the main command dispatch uses
+the loaded map instead of compiled-in constants.
+
+```
+; TP3.KEY
+QUIT=^Q
+CANCEL=^U
+PREFIX=^K
+LITERAL=^P
+WORDLEFT=^A
+CHARLEFT=^S
+CHARRIGHT=^D
+WORDRIGHT=^F
+LINEUP=^E
+LINEDOWN=^X
+SCROLLUP=^W
+SCROLLDOWN=^Z
+PAGEUP=^R
+PAGEDOWN=^C
+INSERTTOGGLE=^V
+DELETECHAR=^G
+DELETELEFT=^H
+DELETEWORD=^T
+INSERTBREAK=^N
+DELETELINE=^Y
+REPEAT=^L
+AUTOTAB=^I
+NEWLINE=^M
+```
+
+Values decode exactly like `TP3.TRM` values and must be **one byte**; a value
+that is not one byte makes the load report not-ok (the defaults stand).
+Unknown names are ignored. `Del` and `^J` remain aliases of `DELETELEFT` and
+`NEWLINE` and are not configurable in this slice.
+
+`tests/term_config.sh` asserts both the escape loader and the key loader byte
+for byte (`term_ansi`, `term_vt52`, `key_default`, `key_custom`).
+
+### Not in this slice
+
+TINST also remaps the **Ctrl-K sub-commands** and the **Esc/ANSI cursor
+sequences** (`HandleEsc`). Those are two further tables in `Editor.mod`; the
+same `Keys` pattern extends to them, as a separate step. The single-key command
+map above is the bulk of them and is what this slice covers.
+
 ## `tp3cfg`
 ## `tp3cfg`
 
 
 The configurator - TINST's role, without the patch. It lists the terminals it
 The configurator - TINST's role, without the patch. It lists the terminals it
@@ -95,9 +143,9 @@ That is deliberately not done by transcription: the sequences are what the
 original writes, and the diff is the evidence. Until it is done, the table
 original writes, and the diff is the evidence. Until it is done, the table
 above is the curated subset the shell needs.
 above is the curated subset the shell needs.
 
 
-## Not in this step: key remapping
+## Not in this step: the Ctrl-K and Esc sub-tables
 
 
-TINST also remaps the **editing keys** (the cursor/insert/delete/block command
-table visible in `TINST.MSG`). The shell's editor hard-codes its keys today.
-The same design extends to it - a `KEYS` section in the config read by
-`Editor.mod` - and is a separate step.
+The editor's **Ctrl-K sub-commands** and its **Esc/ANSI cursor sequences**
+(`KCommand`, `HandleEsc`) are still hard-coded.  The single-key command map is
+configurable now; those two tables are the remaining part of TINST's editor
+configuration, and `Keys` is the pattern to extend.

+ 31 - 26
shell/Editor.mod

@@ -12,6 +12,8 @@ FROM Posix IMPORT
 
 
 FROM SYSTEM IMPORT ADR, BYTE ;
 FROM SYSTEM IMPORT ADR, BYTE ;
 
 
+IMPORT Keys ;
+
 CONST
 CONST
    Broad    = 80 ;                (* columns on the screen *)
    Broad    = 80 ;                (* columns on the screen *)
    TopRow   = 1 ;
    TopRow   = 1 ;
@@ -34,6 +36,7 @@ VAR
    drive      : CHAR ;
    drive      : CHAR ;
    fname      : ARRAY [0..255] OF CHAR ;
    fname      : ARRAY [0..255] OF CHAR ;
    modified   : BOOLEAN ;
    modified   : BOOLEAN ;
+   keys       : Keys.Map ;        (* the key map, from TP3.KEY *)
 
 
    curLine, curCol : CARDINAL ;   (* 0-based cursor *)
    curLine, curCol : CARDINAL ;   (* 0-based cursor *)
    topLine    : CARDINAL ;        (* first visible line *)
    topLine    : CARDINAL ;        (* first visible line *)
@@ -1508,59 +1511,57 @@ BEGIN
       GetCh (ch) ;
       GetCh (ch) ;
       IF ch = Esc THEN
       IF ch = Esc THEN
          HandleEsc
          HandleEsc
-      ELSIF ch = C_Q THEN
+      ELSIF ch = keys.quit THEN
          QCommand
          QCommand
-      ELSIF ch = C_K THEN
+      ELSIF ch = keys.prefix THEN
          KCommand
          KCommand
-      ELSIF ch = C_P THEN
+      ELSIF ch = keys.literal THEN
          GetCh (ch) ;
          GetCh (ch) ;
-         IF NOT ((ch = Esc) OR (ch = C_U)) THEN
+         IF NOT ((ch = Esc) OR (ch = keys.charLeft)) THEN
             BufInsert (CursorOff (), ch) ;
             BufInsert (CursorOff (), ch) ;
             INC (curCol) ;
             INC (curCol) ;
             restoreOn := FALSE ;
             restoreOn := FALSE ;
             modified := TRUE
             modified := TRUE
          END
          END
-      ELSIF ch = C_A THEN
+      ELSIF ch = keys.wordLeft THEN
          WordLeft
          WordLeft
-      ELSIF ch = C_S THEN
+      ELSIF ch = keys.charLeft THEN
          CharLeft
          CharLeft
-      ELSIF ch = C_D THEN
+      ELSIF ch = keys.charRight THEN
          CharRight
          CharRight
-      ELSIF ch = C_F THEN
+      ELSIF ch = keys.wordRight THEN
          WordRight
          WordRight
-      ELSIF ch = C_E THEN
+      ELSIF ch = keys.lineUp THEN
          LineUp
          LineUp
-      ELSIF ch = C_X THEN
+      ELSIF ch = keys.lineDown THEN
          LineDown
          LineDown
-      ELSIF ch = C_W THEN
+      ELSIF ch = keys.scrollUp THEN
          ScrollUp
          ScrollUp
-      ELSIF ch = C_Z THEN
+      ELSIF ch = keys.scrollDown THEN
          ScrollDown
          ScrollDown
-      ELSIF ch = C_R THEN
+      ELSIF ch = keys.pageUp THEN
          PageUp
          PageUp
-      ELSIF ch = C_C THEN
+      ELSIF ch = keys.pageDown THEN
          PageDown
          PageDown
-      ELSIF ch = C_V THEN
+      ELSIF ch = keys.insertToggle THEN
          insertMode := NOT insertMode
          insertMode := NOT insertMode
-      ELSIF ch = C_G THEN
+      ELSIF ch = keys.deleteChar THEN
          DeleteChar
          DeleteChar
-      ELSIF ch = Del THEN
-         DeleteLeft
-      ELSIF ch = C_H THEN
+      ELSIF (ch = Del) OR (ch = keys.deleteLeft) THEN
          DeleteLeft
          DeleteLeft
-      ELSIF ch = C_T THEN
+      ELSIF ch = keys.deleteWord THEN
          DeleteWord
          DeleteWord
-      ELSIF ch = C_N THEN
+      ELSIF ch = keys.insertBreak THEN
          InsertBreak
          InsertBreak
-      ELSIF ch = C_Y THEN
+      ELSIF ch = keys.deleteLine THEN
          DeleteLine
          DeleteLine
-      ELSIF ch = C_L THEN
+      ELSIF ch = keys.repeatLast THEN
          RepeatLast
          RepeatLast
-      ELSIF ch = C_U THEN
+      ELSIF ch = keys.cancel THEN
          Beep
          Beep
-      ELSIF ch = C_I THEN
+      ELSIF ch = keys.autoTab THEN
          AutoTab
          AutoTab
-      ELSIF (ch = C_M) OR (ch = C_J) THEN
+      ELSIF (ch = keys.newLine) OR (ch = C_J) THEN
          NewLine
          NewLine
       ELSIF (ch >= " ") AND (ch <= "~") THEN
       ELSIF (ch >= " ") AND (ch <= "~") THEN
          TypeChar (ch)
          TypeChar (ch)
@@ -1574,7 +1575,11 @@ END MainLoop ;
 PROCEDURE Run (drv : CHAR ; VAR fileName : ARRAY OF CHAR ;
 PROCEDURE Run (drv : CHAR ; VAR fileName : ARRAY OF CHAR ;
                VAR changed : BOOLEAN) ;
                VAR changed : BOOLEAN) ;
 VAR i : CARDINAL ;
 VAR i : CARDINAL ;
+    kok : BOOLEAN ;
 BEGIN
 BEGIN
+   (* The key map: TP3.KEY beside the shell, or the built-in defaults.  Read
+      once per Run (the file can change between edits without restarting). *)
+   Keys.Load ("TP3.KEY", keys, kok) ;
    drive := drv ;
    drive := drv ;
    i := 0 ;
    i := 0 ;
    LOOP
    LOOP

+ 52 - 0
shell/Keys.def

@@ -0,0 +1,52 @@
+DEFINITION MODULE Keys ;
+
+(* The editor's key map, loaded from a TEXT file.  This is the other half of
+   what TINST configures: TINST patches TURBO.COM's editor command table; this
+   port reads TP3.KEY.
+
+   FILE FORMAT - one NAME=value per line, ';' comments, values decoded by
+   TermCfg.ParseValue (\e, \r, \n, \t, \\, ^X).  Each value must be exactly ONE
+   byte.  Names are case-insensitive; unknown names are ignored.
+
+      QUIT=^Q
+      PREFIX=^K
+      WORDLEFT=^A
+      ...
+
+   A missing file leaves the defaults, which are exactly the keys the editor
+   used before there was a key file. *)
+
+TYPE
+   Map = RECORD
+      quit        : CHAR ;
+      cancel      : CHAR ;       (* ^U *)
+      prefix      : CHAR ;       (* Ctrl-K *)
+      literal     : CHAR ;       (* Ctrl-P: insert the next key literally *)
+      wordLeft    : CHAR ;
+      charLeft    : CHAR ;
+      charRight   : CHAR ;
+      wordRight   : CHAR ;
+      lineUp      : CHAR ;
+      lineDown    : CHAR ;
+      scrollUp    : CHAR ;
+      scrollDown  : CHAR ;
+      pageUp      : CHAR ;
+      pageDown    : CHAR ;
+      insertToggle: CHAR ;
+      deleteChar  : CHAR ;
+      deleteLeft  : CHAR ;
+      deleteWord  : CHAR ;
+      insertBreak : CHAR ;
+      deleteLine  : CHAR ;
+      repeatLast  : CHAR ;
+      autoTab     : CHAR ;
+      newLine     : CHAR ;
+   END ;
+
+PROCEDURE Default (VAR m : Map) ;
+
+PROCEDURE Load (path : ARRAY OF CHAR ; VAR m : Map ; VAR ok : BOOLEAN) ;
+(* Default(m), then every NAME=value in path.  ok is FALSE if the file could
+   not be opened or a value is not exactly one byte. *)
+
+END Keys.

+ 179 - 0
shell/Keys.mod

@@ -0,0 +1,179 @@
+IMPLEMENTATION MODULE Keys ;
+
+FROM Posix IMPORT open, read, close ;
+FROM SYSTEM IMPORT ADR, BYTE ;
+FROM TermCfg IMPORT ParseValue ;
+
+CONST
+   MaxFile = 4095 ;
+   CR = 13 ;
+   LF = 10 ;
+
+PROCEDURE Up (ch : CHAR) : CHAR ;
+BEGIN
+   IF (ch >= 'a') AND (ch <= 'z') THEN
+      RETURN CHR (ORD (ch) - 32)
+   END ;
+   RETURN ch
+END Up ;
+
+PROCEDURE Eq (a, b : ARRAY OF CHAR) : BOOLEAN ;
+VAR i : CARDINAL ;
+BEGIN
+   i := 0 ;
+   LOOP
+      IF (i > HIGH (a)) OR (i > HIGH (b)) THEN
+         RETURN FALSE
+      END ;
+      IF Up (a [i]) # Up (b [i]) THEN
+         RETURN FALSE
+      END ;
+      IF b [i] = 0C THEN
+         RETURN TRUE
+      END ;
+      INC (i)
+   END
+END Eq ;
+
+PROCEDURE One (val : ARRAY OF CHAR ; VAR ch : CHAR ; VAR good : BOOLEAN) ;
+(* exactly one decoded byte *)
+VAR tmp : ARRAY [0..63] OF CHAR ;
+BEGIN
+   ParseValue (val, tmp) ;
+   IF (tmp [0] # 0C) AND (tmp [1] = 0C) THEN
+      ch := tmp [0] ;
+      good := TRUE
+   ELSE
+      good := FALSE
+   END
+END One ;
+
+PROCEDURE Apply (VAR m : Map ; name, val : ARRAY OF CHAR ; VAR bad : BOOLEAN) ;
+VAR good : BOOLEAN ;
+BEGIN
+   good := TRUE ;
+   IF Eq (name, "QUIT") THEN One (val, m.quit, good)
+   ELSIF Eq (name, "CANCEL") THEN One (val, m.cancel, good)
+   ELSIF Eq (name, "PREFIX") THEN One (val, m.prefix, good)
+   ELSIF Eq (name, "LITERAL") THEN One (val, m.literal, good)
+   ELSIF Eq (name, "WORDLEFT") THEN One (val, m.wordLeft, good)
+   ELSIF Eq (name, "CHARLEFT") THEN One (val, m.charLeft, good)
+   ELSIF Eq (name, "CHARRIGHT") THEN One (val, m.charRight, good)
+   ELSIF Eq (name, "WORDRIGHT") THEN One (val, m.wordRight, good)
+   ELSIF Eq (name, "LINEUP") THEN One (val, m.lineUp, good)
+   ELSIF Eq (name, "LINEDOWN") THEN One (val, m.lineDown, good)
+   ELSIF Eq (name, "SCROLLUP") THEN One (val, m.scrollUp, good)
+   ELSIF Eq (name, "SCROLLDOWN") THEN One (val, m.scrollDown, good)
+   ELSIF Eq (name, "PAGEUP") THEN One (val, m.pageUp, good)
+   ELSIF Eq (name, "PAGEDOWN") THEN One (val, m.pageDown, good)
+   ELSIF Eq (name, "INSERTTOGGLE") THEN One (val, m.insertToggle, good)
+   ELSIF Eq (name, "DELETECHAR") THEN One (val, m.deleteChar, good)
+   ELSIF Eq (name, "DELETELEFT") THEN One (val, m.deleteLeft, good)
+   ELSIF Eq (name, "DELETEWORD") THEN One (val, m.deleteWord, good)
+   ELSIF Eq (name, "INSERTBREAK") THEN One (val, m.insertBreak, good)
+   ELSIF Eq (name, "DELETELINE") THEN One (val, m.deleteLine, good)
+   ELSIF Eq (name, "REPEAT") THEN One (val, m.repeatLast, good)
+   ELSIF Eq (name, "AUTOTAB") THEN One (val, m.autoTab, good)
+   ELSIF Eq (name, "NEWLINE") THEN One (val, m.newLine, good)
+   END ;
+   IF NOT good THEN
+      bad := TRUE
+   END
+END Apply ;
+
+PROCEDURE Default (VAR m : Map) ;
+BEGIN
+   m.quit         := CHR (17) ;   (* ^Q *)
+   m.cancel       := CHR (21) ;   (* ^U *)
+   m.prefix       := CHR (11) ;   (* ^K *)
+   m.literal      := CHR (16) ;   (* ^P *)
+   m.wordLeft     := CHR (1) ;    (* ^A *)
+   m.charLeft     := CHR (19) ;   (* ^S *)
+   m.charRight    := CHR (4) ;    (* ^D *)
+   m.wordRight    := CHR (6) ;    (* ^F *)
+   m.lineUp       := CHR (5) ;    (* ^E *)
+   m.lineDown     := CHR (24) ;   (* ^X *)
+   m.scrollUp     := CHR (23) ;   (* ^W *)
+   m.scrollDown   := CHR (26) ;   (* ^Z *)
+   m.pageUp       := CHR (18) ;   (* ^R *)
+   m.pageDown     := CHR (3) ;    (* ^C *)
+   m.insertToggle := CHR (22) ;   (* ^V *)
+   m.deleteChar   := CHR (7) ;    (* ^G *)
+   m.deleteLeft   := CHR (8) ;    (* ^H *)
+   m.deleteWord   := CHR (20) ;   (* ^T *)
+   m.insertBreak  := CHR (14) ;   (* ^N *)
+   m.deleteLine   := CHR (25) ;   (* ^Y *)
+   m.repeatLast   := CHR (12) ;   (* ^L *)
+   m.autoTab      := CHR (9) ;    (* ^I *)
+   m.newLine      := CHR (13)     (* ^M *)
+END Default ;
+
+PROCEDURE Load (path : ARRAY OF CHAR ; VAR m : Map ; VAR ok : BOOLEAN) ;
+VAR fd : INTEGER ;
+    k : LONGINT ;
+    b : BYTE ;
+    buf : ARRAY [0..MaxFile] OF CHAR ;
+    n, i, ls, le, eq, j : CARDINAL ;
+    name : ARRAY [0..31] OF CHAR ;
+    val : ARRAY [0..63] OF CHAR ;
+    bad : BOOLEAN ;
+BEGIN
+   Default (m) ;
+   ok := FALSE ;
+   fd := open (ADR (path), 0, 0) ;
+   IF fd < 0 THEN
+      RETURN
+   END ;
+   n := 0 ;
+   LOOP
+      k := read (fd, ADR (b), 1) ;
+      IF k # 1 THEN EXIT END ;
+      IF b = 26 THEN EXIT END ;
+      IF n <= HIGH (buf) THEN
+         buf [n] := CHR (ORD (b)) ;
+         INC (n)
+      END
+   END ;
+   k := close (fd) ;
+   bad := FALSE ;
+   i := 0 ;
+   WHILE i < n DO
+      ls := i ;
+      WHILE (i < n) AND (buf [i] # CHR (CR)) AND (buf [i] # CHR (LF)) DO
+         INC (i)
+      END ;
+      le := i ;
+      WHILE (i < n) AND ((buf [i] = CHR (CR)) OR (buf [i] = CHR (LF))) DO
+         INC (i)
+      END ;
+      WHILE (ls < le) AND ((buf [ls] = ' ') OR (buf [ls] = CHR (9))) DO
+         INC (ls)
+      END ;
+      WHILE (le > ls) AND ((buf [le - 1] = ' ') OR (buf [le - 1] = CHR (9))) DO
+         DEC (le)
+      END ;
+      IF (ls < le) AND (buf [ls] # ';') THEN
+         eq := ls ;
+         WHILE (eq < le) AND (buf [eq] # '=') DO INC (eq) END ;
+         IF eq = le THEN
+            bad := TRUE
+         ELSE
+            j := 0 ;
+            WHILE (ls < eq) AND (j <= HIGH (name)) DO
+               name [j] := buf [ls] ; INC (j) ; INC (ls)
+            END ;
+            name [j] := 0C ;
+            INC (eq) ;
+            j := 0 ;
+            WHILE (eq < le) AND (j <= HIGH (val)) DO
+               val [j] := buf [eq] ; INC (j) ; INC (eq)
+            END ;
+            val [j] := 0C ;
+            Apply (m, name, val, bad)
+         END
+      END
+   END ;
+   ok := NOT bad
+END Load ;
+
+END Keys.

+ 6 - 3
shell/Makefile

@@ -6,8 +6,8 @@ FLAGS = -fiso
 # pass-3 rollup on a compiler-size program ("too many errors in pass 3"), so
 # pass-3 rollup on a compiler-size program ("too many errors in pass 3"), so
 # the import closure is generated first and the link then consumes it.  Phase 1
 # the import closure is generated first and the link then consumes it.  Phase 1
 # exiting 1 is the expected rollup, hence the `|| true`.
 # exiting 1 is the expected rollup, hence the `|| true`.
-MODS  = Shell Term TermCfg Posix TextBuf Editor Compiler Runtime Linker Exec86
-LINK  = Shell.mod Term.o TermCfg.o Posix.o TextBuf.o Editor.o Compiler.o Runtime.o Linker.o Exec86.o
+MODS  = Shell Term TermCfg Keys Posix TextBuf Editor Compiler Runtime Linker Exec86
+LINK  = Shell.mod Term.o TermCfg.o Keys.o Posix.o TextBuf.o Editor.o Compiler.o Runtime.o Linker.o Exec86.o
 
 
 all: tpshell tp3cfg
 all: tpshell tp3cfg
 
 
@@ -33,10 +33,13 @@ Term.o: Term.mod Term.def TermCfg.def Posix.def
 TermCfg.o: TermCfg.mod TermCfg.def Posix.def
 TermCfg.o: TermCfg.mod TermCfg.def Posix.def
 	$(GM2) $(FLAGS) -c TermCfg.mod
 	$(GM2) $(FLAGS) -c TermCfg.mod
 
 
+Keys.o: Keys.mod Keys.def TermCfg.def Posix.def
+	$(GM2) $(FLAGS) -c Keys.mod
+
 TextBuf.o: TextBuf.mod TextBuf.def
 TextBuf.o: TextBuf.mod TextBuf.def
 	$(GM2) $(FLAGS) -c TextBuf.mod
 	$(GM2) $(FLAGS) -c TextBuf.mod
 
 
-Editor.o: Editor.mod Editor.def Term.def TextBuf.def Posix.def
+Editor.o: Editor.mod Editor.def Term.def TextBuf.def Posix.def Keys.def
 	$(GM2) $(FLAGS) -c Editor.mod
 	$(GM2) $(FLAGS) -c Editor.mod
 
 
 Posix.o: Posix.c Posix.def
 Posix.o: Posix.c Posix.def

+ 30 - 0
shell/TP3.KEY

@@ -0,0 +1,30 @@
+; TP3 key map -- read by tpshell's editor at startup (Keys.Load).
+;
+; The editor's single-key commands.  Values are decoded like TP3.TRM's:
+; \e, \r, \n, \t, \\, ^X.  Each must be exactly one byte.
+;
+; Not here (yet): the Ctrl-K sub-commands and the Esc/ANSI cursor sequences.
+; Those are the next slice; see TERMINAL.md.
+QUIT=^Q
+CANCEL=^U
+PREFIX=^K
+LITERAL=^P
+WORDLEFT=^A
+CHARLEFT=^S
+CHARRIGHT=^D
+WORDRIGHT=^F
+LINEUP=^E
+LINEDOWN=^X
+SCROLLUP=^W
+SCROLLDOWN=^Z
+PAGEUP=^R
+PAGEDOWN=^C
+INSERTTOGGLE=^V
+DELETECHAR=^G
+DELETELEFT=^H
+DELETEWORD=^T
+INSERTBREAK=^N
+DELETELINE=^Y
+REPEAT=^L
+AUTOTAB=^I
+NEWLINE=^M

+ 78 - 0
shell/tests/KeysTest.mod

@@ -0,0 +1,78 @@
+MODULE KeysTest ;
+
+(* Read a key map and print it as hex, so the loader/decoder is asserted byte
+   for byte by tests/term_config.sh (which copies the fixture to the fixed
+   path). *)
+
+FROM Keys IMPORT Map, Load ;
+FROM Posix IMPORT write ;
+FROM SYSTEM IMPORT ADR ;
+
+VAR
+   m : Map ;
+   ok : BOOLEAN ;
+   n : LONGINT ;
+   ch : CHAR ;
+
+PROCEDURE Out (s : ARRAY OF CHAR) ;
+VAR i : CARDINAL ;
+BEGIN
+   i := 0 ;
+   WHILE (i <= HIGH (s)) AND (s [i] # 0C) DO
+      ch := s [i] ;
+      n := write (1, ADR (ch), 1) ;
+      INC (i)
+   END
+END Out ;
+
+PROCEDURE NL ;
+BEGIN
+   ch := CHR (10) ;
+   n := write (1, ADR (ch), 1)
+END NL ;
+
+PROCEDURE HexDig (d : CARDINAL) ;
+BEGIN
+   IF d < 10 THEN
+      ch := CHR (ORD ('0') + d)
+   ELSE
+      ch := CHR (ORD ('A') + d - 10)
+   END ;
+   n := write (1, ADR (ch), 1)
+END HexDig ;
+
+PROCEDURE K (label : ARRAY OF CHAR ; c : CHAR) ;
+BEGIN
+   Out (label) ; Out ("=") ;
+   HexDig (ORD (c) DIV 16) ; HexDig (ORD (c) MOD 16) ;
+   NL
+END K ;
+
+BEGIN
+   Load ("../tmp/key_test.key", m, ok) ;
+   IF ok THEN Out ("ok=1") ELSE Out ("ok=0") END ;
+   NL ;
+   K ("QUIT", m.quit) ;
+   K ("CANCEL", m.cancel) ;
+   K ("PREFIX", m.prefix) ;
+   K ("LITERAL", m.literal) ;
+   K ("WORDLEFT", m.wordLeft) ;
+   K ("CHARLEFT", m.charLeft) ;
+   K ("CHARRIGHT", m.charRight) ;
+   K ("WORDRIGHT", m.wordRight) ;
+   K ("LINEUP", m.lineUp) ;
+   K ("LINEDOWN", m.lineDown) ;
+   K ("SCROLLUP", m.scrollUp) ;
+   K ("SCROLLDOWN", m.scrollDown) ;
+   K ("PAGEUP", m.pageUp) ;
+   K ("PAGEDOWN", m.pageDown) ;
+   K ("INSERTTOGGLE", m.insertToggle) ;
+   K ("DELETECHAR", m.deleteChar) ;
+   K ("DELETELEFT", m.deleteLeft) ;
+   K ("DELETEWORD", m.deleteWord) ;
+   K ("INSERTBREAK", m.insertBreak) ;
+   K ("DELETELINE", m.deleteLine) ;
+   K ("REPEAT", m.repeatLast) ;
+   K ("AUTOTAB", m.autoTab) ;
+   K ("NEWLINE", m.newLine)
+END KeysTest.

+ 24 - 0
shell/tests/fixtures/key_custom.hex

@@ -0,0 +1,24 @@
+ok=1
+QUIT=18
+CANCEL=14
+PREFIX=0B
+LITERAL=10
+WORDLEFT=02
+CHARLEFT=13
+CHARRIGHT=04
+WORDRIGHT=06
+LINEUP=05
+LINEDOWN=18
+SCROLLUP=17
+SCROLLDOWN=1A
+PAGEUP=12
+PAGEDOWN=03
+INSERTTOGGLE=16
+DELETECHAR=07
+DELETELEFT=08
+DELETEWORD=14
+INSERTBREAK=0E
+DELETELINE=19
+REPEAT=0C
+AUTOTAB=09
+NEWLINE=0D

+ 5 - 0
shell/tests/fixtures/key_custom.key

@@ -0,0 +1,5 @@
+; a remap: QUIT and WORDLEFT and CANCEL change; NOTAKEY is ignored
+QUIT=^X
+WORDLEFT=^B
+CANCEL=^T
+NOTAKEY=^Z

+ 24 - 0
shell/tests/fixtures/key_default.hex

@@ -0,0 +1,24 @@
+ok=1
+QUIT=11
+CANCEL=15
+PREFIX=0B
+LITERAL=10
+WORDLEFT=01
+CHARLEFT=13
+CHARRIGHT=04
+WORDRIGHT=06
+LINEUP=05
+LINEDOWN=18
+SCROLLUP=17
+SCROLLDOWN=1A
+PAGEUP=12
+PAGEDOWN=03
+INSERTTOGGLE=16
+DELETECHAR=07
+DELETELEFT=08
+DELETEWORD=14
+INSERTBREAK=0E
+DELETELINE=19
+REPEAT=0C
+AUTOTAB=09
+NEWLINE=0D

+ 30 - 0
shell/tests/fixtures/key_default.key

@@ -0,0 +1,30 @@
+; TP3 key map -- read by tpshell's editor at startup (Keys.Load).
+;
+; The editor's single-key commands.  Values are decoded like TP3.TRM's:
+; \e, \r, \n, \t, \\, ^X.  Each must be exactly one byte.
+;
+; Not here (yet): the Ctrl-K sub-commands and the Esc/ANSI cursor sequences.
+; Those are the next slice; see TERMINAL.md.
+QUIT=^Q
+CANCEL=^U
+PREFIX=^K
+LITERAL=^P
+WORDLEFT=^A
+CHARLEFT=^S
+CHARRIGHT=^D
+WORDRIGHT=^F
+LINEUP=^E
+LINEDOWN=^X
+SCROLLUP=^W
+SCROLLDOWN=^Z
+PAGEUP=^R
+PAGEDOWN=^C
+INSERTTOGGLE=^V
+DELETECHAR=^G
+DELETELEFT=^H
+DELETEWORD=^T
+INSERTBREAK=^N
+DELETELINE=^Y
+REPEAT=^L
+AUTOTAB=^I
+NEWLINE=^M

+ 25 - 0
shell/tests/term_config.sh

@@ -52,6 +52,31 @@ for name in term_ansi term_vt52; do
     fi
     fi
 done
 done
 
 
+# ---- the editor's key map ------------------------------------------------
+if [ ! -f Keys.o ] || [ Keys.mod -nt Keys.o ]; then
+    $GM2 -fiso -c Keys.mod >../tmp/termcfg_c_keys 2>&1 || {
+        echo "COMPILE_FAIL Keys"; grep -m5 "error:" ../tmp/termcfg_c_keys; exit 1; }
+fi
+if [ ! -x tests/keycfgtest ] || [ tests/KeysTest.mod -nt tests/keycfgtest ] \
+   || [ Keys.o -nt tests/keycfgtest ] || [ TermCfg.o -nt tests/keycfgtest ]; then
+    rm -f tests/keycfgtest
+    $GM2 -fiso -o tests/keycfgtest tests/KeysTest.mod Keys.o TermCfg.o Posix.o \
+        >../tmp/keycfg_link 2>&1 || {
+        echo "LINK_FAIL KeysTest"; grep -m5 -E "error:|undefined" ../tmp/keycfg_link; exit 1; }
+fi
+for name in key_default key_custom; do
+    cp "tests/fixtures/$name.key" ../tmp/key_test.key || exit 1
+    ./tests/keycfgtest > "../tmp/$name.got" 2>&1
+    if diff -u "tests/fixtures/$name.hex" "../tmp/$name.got" > "../tmp/$name.diff"; then
+        printf '  %-20s PASS\n' "$name"
+        pass=$((pass + 1))
+    else
+        printf '  %-20s FAIL\n' "$name"
+        head -20 "../tmp/$name.diff"
+        fail=$((fail + 1))
+    fi
+done
+
 echo "----------------------------------------------------------------"
 echo "----------------------------------------------------------------"
 echo "terminal config: $pass passed, $fail failed (of $((pass + fail)))"
 echo "terminal config: $pass passed, $fail failed (of $((pass + fail)))"
 [ "$fail" -eq 0 ] || exit 1
 [ "$fail" -eq 0 ] || exit 1