Просмотр исходного кода

Add syntax highlighting and keyword up-casing to the tvision-m2 editor

- EditorSetLanguage(edp, lang) selects Modula-2, Pascal or Oberon
  (langNone/langModula2/langPascal/langOberon).
- Per-line highlighting with comment state carried across lines:
  keywords (white), strings '...'/"..." (red), numbers (red) and comments
  (grey) - (* ... *) nestable, { ... } for Pascal, // line comments.
- Modula-2 and Oberon keywords are automatically upper-cased when a word is
  completed by a delimiter (space, Tab, Enter or punctuation); Pascal is
  left as typed.
- EditorAddLine now replaces the initial empty line instead of appending,
  so building text no longer leaves a leading blank line.
- examples/edit gained a Syntax menu to switch language live; docs updated.
Eric Streit 3 недель назад
Родитель
Сommit
2972fbd010

+ 8 - 0
tvision-m2/API.md

@@ -126,6 +126,14 @@ tv.FocusView(ev);
 | `EditorLoad(data, path) : BOOLEAN` | load a text file into the buffer |
 | `EditorSave(data, path) : BOOLEAN` | save the buffer to a text file |
 | `EditorDirty(data) : BOOLEAN` | TRUE if modified since load/save |
+| `EditorSetLanguage(data, lang)` | enable highlighting for a language |
+
+Language constants: `langNone`, `langModula2`, `langPascal`, `langOberon`.
+With a language set the editor highlights keywords (white), strings (red),
+comments (grey; `(* ... *)` nestable, plus `{ ... }` for Pascal and `//` for
+Pascal/Modula-2) and numbers (red).  For Modula-2 and Oberon, finishing a
+word with a delimiter (space, Tab, Enter or punctuation) automatically
+upper-cases it if it is a keyword (`begin` -> `BEGIN`).
 
 Keys: printable text, Enter, Backspace, Delete, arrows, Home/End, PgUp/PgDn,
 Tab (indent to a 4-column stop), Ins (insert/overwrite).  The mouse places

+ 3 - 0
tvision-m2/README.md

@@ -52,6 +52,9 @@ sibling project:
   PgUp/PgDn, Enter/Backspace/Delete, Tab indent, mouse positioning, wheel
   scroll, its own Ln/Col/Insert/Modified status row, plus load/save to a
   file (`EditorLoad`/`EditorSave`).
+- Syntax highlighting for Modula-2, Pascal and Oberon (keywords, strings,
+  numbers, `(* *)`/`{ }`/`//` comments) and automatic keyword up-casing for
+  Modula-2 and Oberon (`begin` -> `BEGIN`).
 - Widgets: static text, single/double frame, input line (insert, backspace,
   delete, Home/End/arrows), push button, check box, radio buttons, list box
   with selection and wheel scrolling, vertical scroll bar.

+ 3 - 1
tvision-m2/SUMMARY.md

@@ -46,7 +46,9 @@ from the captured terminal stream).
 - SIGWINCH-driven resize detection via a libc `signal` handler.
 - Multi-line editor widget (`VEditor`) with insert/overwrite, navigation,
   Tab indent, mouse positioning, a Ln/Col/Insert/Modified status row and
-  file load/save; demo `examples/edit`.
+  file load/save; syntax highlighting for Modula-2/Pascal/Oberon and
+  automatic keyword up-casing for Modula-2/Oberon; demo `examples/edit`
+  (with a Syntax menu to switch language).
 - Example programs (`empty`, `window`, `dialog`, `menus`, `edit`, `gfx`); API
   reference (`API.md`) and tutorial (`TUTORIAL.md`).
 - tigr graphics backend (`tvg`): the same view tree rendered in a window.

+ 1 - 0
tvision-m2/TUTORIAL.md

@@ -151,6 +151,7 @@ edp := CAST(tv.EditorDataPtr, ADR(ed));
 IF NOT tv.EditorLoad(edp, "notes.txt") THEN
     tv.EditorInit(edp); tv.EditorAddLine(edp, "Type here...")
 END;
+tv.EditorSetLanguage(edp, tv.langModula2);   (* or langPascal / langOberon *)
 ev := tv.NewView(tv.VEditor, 2, 2, 60, 20);
 tv.EditorAttach(ev, edp);
 tv.AddView(win, ev);

BIN
tvision-m2/demo


BIN
tvision-m2/examples/dialog


BIN
tvision-m2/examples/edit


+ 18 - 4
tvision-m2/examples/edit.mod

@@ -13,9 +13,12 @@ FROM SYSTEM IMPORT ADR, CAST;
 IMPORT tv;
 
 CONST
-    mNew   = 100;
-    mSave  = 101;
-    mQuit  = 102;
+    mNew    = 100;
+    mSave   = 101;
+    mQuit   = 102;
+    mModula2 = 110;
+    mPascal  = 111;
+    mOberon  = 112;
 
 VAR
     ed : tv.EditorData;
@@ -30,7 +33,7 @@ VAR
 PROCEDURE Sample;
 BEGIN
     tv.EditorInit(edp);
-    tv.EditorAddLine(edp, "MODULE hello;");
+    tv.EditorAddLine(edp, "MODULE hello;   (* sample for tvision-m2 *)");
     tv.EditorAddLine(edp, "");
     tv.EditorAddLine(edp, "FROM InOut IMPORT WriteString, WriteLn;");
     tv.EditorAddLine(edp, "");
@@ -63,6 +66,10 @@ BEGIN
     tv.MenuItemAdd("Save", mSave);
     tv.MenuSep;
     tv.MenuItemAdd("Quit", mQuit);
+    tv.MenuAdd("Syntax");
+    tv.MenuItemAdd("Modula-2", mModula2);
+    tv.MenuItemAdd("Pascal", mPascal);
+    tv.MenuItemAdd("Oberon", mOberon);
 
     bar := tv.NewView(tv.VText, 0, tv.Rows() - 1, tv.Cols(), 1);
     tv.AddView(tv.Desk(), bar);
@@ -72,6 +79,7 @@ BEGIN
     tv.AddView(tv.Desk(), w);
 
     IF NOT tv.EditorLoad(edp, "tvision-edit.txt") THEN Sample END;
+    tv.EditorSetLanguage(edp, tv.langModula2);
 
     edView := tv.NewView(tv.VEditor, 2, 2, tv.Cols() - 4, tv.Rows() - 5);
     tv.EditorAttach(edView, CAST(tv.EditorDataPtr, ADR(ed)));
@@ -99,6 +107,12 @@ BEGIN
                         tv.Message("Save", "Could not save the file.")
                     END;
                     Refresh
+                ELSIF cmd = mModula2 THEN
+                    tv.EditorSetLanguage(edp, tv.langModula2); Refresh
+                ELSIF cmd = mPascal THEN
+                    tv.EditorSetLanguage(edp, tv.langPascal); Refresh
+                ELSIF cmd = mOberon THEN
+                    tv.EditorSetLanguage(edp, tv.langOberon); Refresh
                 ELSIF cmd = tv.cmClose THEN
                     quit := TRUE
                 END

BIN
tvision-m2/examples/empty


BIN
tvision-m2/examples/gfx


BIN
tvision-m2/examples/menus


BIN
tvision-m2/examples/window


+ 8 - 0
tvision-m2/tv.def

@@ -59,6 +59,12 @@ CONST
     EDMAXLINES = 512;
     EDMAXCOL   = 160;
 
+    (* editor languages (for highlighting / keyword upcasing) *)
+    langNone    = 0;
+    langModula2 = 1;
+    langPascal  = 2;
+    langOberon  = 3;
+
 TYPE
     Attr  = CARDINAL;
     Point = RECORD x, y : INTEGER; END;
@@ -132,6 +138,7 @@ TYPE
                      top, left : INTEGER;  (* scroll *)
                      ins   : BOOLEAN;      (* insert mode *)
                      dirty : BOOLEAN;
+                     lang  : INTEGER;      (* one of the lang* constants *)
                  END;
     EditorDataPtr = POINTER TO EditorData;
 
@@ -209,5 +216,6 @@ PROCEDURE EditorGetLine (data : EditorDataPtr; i : INTEGER; VAR s : ARRAY OF CHA
 PROCEDURE EditorLoad (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
 PROCEDURE EditorSave (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
 PROCEDURE EditorDirty (data : EditorDataPtr) : BOOLEAN;
+PROCEDURE EditorSetLanguage (data : EditorDataPtr; lang : INTEGER);
 
 END tv.

+ 236 - 11
tvision-m2/tv.mod

@@ -1239,8 +1239,207 @@ BEGIN
     END
 END EdMove;
 
+(* --- syntax highlighting + keyword upcasing ---------------------------- *)
+
+PROCEDURE Eq (a, b : ARRAY OF CHAR) : BOOLEAN;
+    VAR i : INTEGER;
+BEGIN
+    i := 0;
+    WHILE (i <= VAL(INTEGER, HIGH(a))) AND (i <= VAL(INTEGER, HIGH(b))) AND
+          (a[i] # 0C) AND (b[i] # 0C) AND (a[i] = b[i]) DO
+        i := i + 1
+    END;
+    RETURN a[i] = b[i]
+END Eq;
+
+PROCEDURE IsIdentChar (c : CHAR) : BOOLEAN;
+BEGIN
+    RETURN ((c >= 'A') AND (c <= 'Z')) OR ((c >= 'a') AND (c <= 'z')) OR
+           ((c >= '0') AND (c <= '9')) OR (c = '_')
+END IsIdentChar;
+
+PROCEDURE UpcaseCopy (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
+    VAR i : INTEGER; c : CHAR;
+BEGIN
+    i := 0;
+    WHILE (i <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[i] # 0C) DO
+        c := s[i];
+        IF (c >= 'a') AND (c <= 'z') THEN c := CHR(ORD(c) - 32) END;
+        dest[i] := c;
+        i := i + 1
+    END;
+    dest[i] := 0C
+END UpcaseCopy;
+
+PROCEDURE IsKeywordM2 (w : ARRAY OF CHAR) : BOOLEAN;
+BEGIN
+    RETURN Eq(w,"AND") OR Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"BY") OR
+           Eq(w,"CASE") OR Eq(w,"CONST") OR Eq(w,"DEFINITION") OR Eq(w,"DIV") OR
+           Eq(w,"DO") OR Eq(w,"ELSE") OR Eq(w,"ELSIF") OR Eq(w,"END") OR
+           Eq(w,"EXIT") OR Eq(w,"EXPORT") OR Eq(w,"FOR") OR Eq(w,"FROM") OR
+           Eq(w,"IF") OR Eq(w,"IMPLEMENTATION") OR Eq(w,"IMPORT") OR Eq(w,"IN") OR
+           Eq(w,"LOOP") OR Eq(w,"MOD") OR Eq(w,"MODULE") OR Eq(w,"NOT") OR
+           Eq(w,"OF") OR Eq(w,"OR") OR Eq(w,"POINTER") OR Eq(w,"PROCEDURE") OR
+           Eq(w,"QUALIFIED") OR Eq(w,"RECORD") OR Eq(w,"REPEAT") OR Eq(w,"RETURN") OR
+           Eq(w,"SET") OR Eq(w,"THEN") OR Eq(w,"TO") OR Eq(w,"TYPE") OR
+           Eq(w,"UNTIL") OR Eq(w,"VAR") OR Eq(w,"WHILE") OR Eq(w,"WITH")
+END IsKeywordM2;
+
+PROCEDURE IsKeywordOberon (w : ARRAY OF CHAR) : BOOLEAN;
+BEGIN
+    RETURN Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"BY") OR Eq(w,"CASE") OR
+           Eq(w,"CONST") OR Eq(w,"DIV") OR Eq(w,"DO") OR Eq(w,"ELSE") OR
+           Eq(w,"ELSIF") OR Eq(w,"END") OR Eq(w,"FALSE") OR Eq(w,"FOR") OR
+           Eq(w,"IF") OR Eq(w,"IMPORT") OR Eq(w,"IN") OR Eq(w,"IS") OR
+           Eq(w,"MOD") OR Eq(w,"MODULE") OR Eq(w,"NIL") OR Eq(w,"OF") OR
+           Eq(w,"OR") OR Eq(w,"POINTER") OR Eq(w,"PROCEDURE") OR Eq(w,"RECORD") OR
+           Eq(w,"REPEAT") OR Eq(w,"RETURN") OR Eq(w,"THEN") OR Eq(w,"TO") OR
+           Eq(w,"TRUE") OR Eq(w,"TYPE") OR Eq(w,"UNTIL") OR Eq(w,"VAR") OR
+           Eq(w,"WHILE") OR Eq(w,"WITH")
+END IsKeywordOberon;
+
+PROCEDURE IsKeywordPascal (w : ARRAY OF CHAR) : BOOLEAN;
+BEGIN
+    RETURN Eq(w,"AND") OR Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"CASE") OR
+           Eq(w,"CONST") OR Eq(w,"DIV") OR Eq(w,"DO") OR Eq(w,"DOWNTO") OR
+           Eq(w,"ELSE") OR Eq(w,"END") OR Eq(w,"FILE") OR Eq(w,"FOR") OR
+           Eq(w,"FUNCTION") OR Eq(w,"GOTO") OR Eq(w,"IF") OR Eq(w,"IN") OR
+           Eq(w,"LABEL") OR Eq(w,"MOD") OR Eq(w,"NIL") OR Eq(w,"NOT") OR
+           Eq(w,"OF") OR Eq(w,"OR") OR Eq(w,"PACKED") OR Eq(w,"PROCEDURE") OR
+           Eq(w,"PROGRAM") OR Eq(w,"RECORD") OR Eq(w,"REPEAT") OR Eq(w,"SET") OR
+           Eq(w,"THEN") OR Eq(w,"TO") OR Eq(w,"TYPE") OR Eq(w,"UNTIL") OR
+           Eq(w,"VAR") OR Eq(w,"WHILE") OR Eq(w,"WITH")
+END IsKeywordPascal;
+
+PROCEDURE IsKeywordLang (lang : INTEGER; w : ARRAY OF CHAR) : BOOLEAN;
+BEGIN
+    IF lang = langModula2 THEN RETURN IsKeywordM2(w) END;
+    IF lang = langOberon THEN RETURN IsKeywordOberon(w) END;
+    IF lang = langPascal THEN RETURN IsKeywordPascal(w) END;
+    RETURN FALSE
+END IsKeywordLang;
+
+PROCEDURE HiPut (v : ViewPtr; c : INTEGER; ch : CHAR; attr : Attr;
+                 x0, y0, w : INTEGER; draw : BOOLEAN);
+BEGIN
+    IF draw AND (c >= v^.ed^.left) AND (c - v^.ed^.left < w) THEN
+        DrawCh(x0 + (c - v^.ed^.left), y0, VAL(CARDINAL, ORD(ch)), attr)
+    END
+END HiPut;
+
+PROCEDURE DrawLineHi (v : ViewPtr; ln : INTEGER; x0, y0, w : INTEGER;
+                      draw : BOOLEAN; VAR state : INTEGER);
+    VAR ed : EditorDataPtr; lang, i, j, n : INTEGER; ch, q : CHAR; done : BOOLEAN;
+        kw, str, com, num : Attr; word : ARRAY [0..63] OF CHAR;
+BEGIN
+    ed := v^.ed;
+    lang := ed^.lang;
+    kw  := A(White, Cyan, FALSE);
+    str := A(Red, Cyan, FALSE);
+    com := A(DarkGray, Cyan, FALSE);
+    num := A(Red, Cyan, FALSE);
+    i := 0;
+    WHILE i < ed^.len[ln] DO
+        ch := ed^.line[ln][i];
+        IF state > 0 THEN
+            (* inside a (* ... *) comment (nestable for M2/Oberon) *)
+            IF (ch = '(') AND (i + 1 < ed^.len[ln]) AND
+               (ed^.line[ln][i + 1] = '*') THEN
+                HiPut(v, i, ch, com, x0, y0, w, draw);
+                HiPut(v, i + 1, '*', com, x0, y0, w, draw);
+                state := state + 1; i := i + 2
+            ELSIF (ch = '*') AND (i + 1 < ed^.len[ln]) AND
+                  (ed^.line[ln][i + 1] = ')') THEN
+                HiPut(v, i, ch, com, x0, y0, w, draw);
+                HiPut(v, i + 1, ')', com, x0, y0, w, draw);
+                state := state - 1; i := i + 2
+            ELSE
+                HiPut(v, i, ch, com, x0, y0, w, draw); i := i + 1
+            END
+        ELSIF state < 0 THEN
+            (* inside a Pascal { ... } comment *)
+            HiPut(v, i, ch, com, x0, y0, w, draw);
+            IF ch = '}' THEN state := 0 END;
+            i := i + 1
+        ELSIF (ch = '(') AND (i + 1 < ed^.len[ln]) AND
+              (ed^.line[ln][i + 1] = '*') THEN
+            HiPut(v, i, '(', com, x0, y0, w, draw);
+            HiPut(v, i + 1, '*', com, x0, y0, w, draw);
+            state := 1; i := i + 2
+        ELSIF (lang = langPascal) AND (ch = '{') THEN
+            HiPut(v, i, ch, com, x0, y0, w, draw);
+            state := -1; i := i + 1
+        ELSIF (ch = '/') AND (i + 1 < ed^.len[ln]) AND
+              (ed^.line[ln][i + 1] = '/') AND (lang # langNone) THEN
+            WHILE i < ed^.len[ln] DO
+                HiPut(v, i, ed^.line[ln][i], com, x0, y0, w, draw); i := i + 1
+            END
+        ELSIF (ch = CHR(39)) OR (ch = CHR(34)) THEN
+            q := ch;
+            HiPut(v, i, ch, str, x0, y0, w, draw); i := i + 1;
+            done := FALSE;
+            WHILE (i < ed^.len[ln]) AND (NOT done) DO
+                HiPut(v, i, ed^.line[ln][i], str, x0, y0, w, draw);
+                IF ed^.line[ln][i] = q THEN
+                    IF (i + 1 < ed^.len[ln]) AND (ed^.line[ln][i + 1] = q) THEN
+                        i := i + 1;
+                        HiPut(v, i, q, str, x0, y0, w, draw)
+                    ELSE
+                        done := TRUE
+                    END
+                END;
+                i := i + 1
+            END
+        ELSIF (ch >= '0') AND (ch <= '9') THEN
+            WHILE (i < ed^.len[ln]) AND (ed^.line[ln][i] >= '0') AND
+                  (ed^.line[ln][i] <= '9') DO
+                HiPut(v, i, ed^.line[ln][i], num, x0, y0, w, draw); i := i + 1
+            END
+        ELSIF IsIdentChar(ch) THEN
+            n := 0;
+            WHILE (i < ed^.len[ln]) AND IsIdentChar(ed^.line[ln][i]) AND (n < 63) DO
+                word[n] := ed^.line[ln][i];
+                n := n + 1; i := i + 1
+            END;
+            word[n] := 0C;
+            UpcaseCopy(word, word);
+            FOR j := i - n TO i - 1 DO
+                IF IsKeywordLang(lang, word) THEN
+                    HiPut(v, j, ed^.line[ln][j], kw, x0, y0, w, draw)
+                ELSE
+                    HiPut(v, j, ed^.line[ln][j], A(Black, Cyan, FALSE), x0, y0, w, draw)
+                END
+            END
+        ELSE
+            HiPut(v, i, ch, A(Black, Cyan, FALSE), x0, y0, w, draw);
+            i := i + 1
+        END
+    END
+END DrawLineHi;
+
+PROCEDURE EdUpcaseWord (data : EditorDataPtr);
+    VAR ln, i, n, k : INTEGER; w, up : ARRAY [0..63] OF CHAR;
+BEGIN
+    IF (data = NIL) OR ((data^.lang # langModula2) AND (data^.lang # langOberon)) THEN
+        RETURN
+    END;
+    EdEnsure(data);
+    ln := data^.cy;
+    i := data^.cx;
+    WHILE (i > 0) AND IsIdentChar(data^.line[ln][i - 1]) DO i := i - 1 END;
+    n := data^.cx - i;
+    IF (n <= 0) OR (n > 63) THEN RETURN END;
+    FOR k := 0 TO n - 1 DO w[k] := data^.line[ln][i + k] END;
+    w[n] := 0C;
+    UpcaseCopy(up, w);
+    IF IsKeywordLang(data^.lang, up) AND (NOT Eq(w, up)) THEN
+        FOR k := 0 TO n - 1 DO data^.line[ln][i + k] := up[k] END;
+        data^.dirty := TRUE
+    END
+END EdUpcaseWord;
+
 PROCEDURE DrawEditor (v : ViewPtr);
-    VAR x0, y0, w, h, vis, r, ln, c : INTEGER;
+    VAR x0, y0, w, h, vis, r, ln, c, hstate, l : INTEGER;
         ed : EditorDataPtr; s, t : ARRAY [0..95] OF CHAR;
 BEGIN
     DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE));
@@ -1258,16 +1457,26 @@ BEGIN
     IF ed^.cx >= ed^.left + w THEN ed^.left := ed^.cx - w + 1 END;
     IF ed^.top < 0 THEN ed^.top := 0 END;
     IF ed^.left < 0 THEN ed^.left := 0 END;
+    hstate := 0;
+    IF ed^.lang # langNone THEN
+        FOR l := 0 TO ed^.top - 1 DO
+            IF l < ed^.count THEN DrawLineHi(v, l, x0, y0, w, FALSE, hstate) END
+        END
+    END;
     FOR r := 0 TO vis - 1 DO
         Fill(x0, y0 + r, w, 1, VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
         ln := ed^.top + r;
         IF ln < ed^.count THEN
-            c := 0;
-            WHILE (c < w) AND (ed^.left + c < ed^.len[ln]) DO
-                DrawCh(x0 + c, y0 + r,
-                       VAL(CARDINAL, ORD(ed^.line[ln][ed^.left + c])),
-                       A(Black, Cyan, FALSE));
-                c := c + 1
+            IF ed^.lang = langNone THEN
+                c := 0;
+                WHILE (c < w) AND (ed^.left + c < ed^.len[ln]) DO
+                    DrawCh(x0 + c, y0 + r,
+                           VAL(CARDINAL, ORD(ed^.line[ln][ed^.left + c])),
+                           A(Black, Cyan, FALSE));
+                    c := c + 1
+                END
+            ELSE
+                DrawLineHi(v, ln, x0, y0 + r, w, TRUE, hstate)
             END
         END
     END;
@@ -1296,7 +1505,7 @@ BEGIN
         EdClrLine(data^.line[0]);
         data^.len[0] := 0;
         data^.cx := 0; data^.cy := 0; data^.top := 0; data^.left := 0;
-        data^.ins := TRUE; data^.dirty := FALSE
+        data^.ins := TRUE; data^.dirty := FALSE; data^.lang := langNone
     END
 END EditorInit;
 
@@ -1305,7 +1514,11 @@ PROCEDURE EditorAddLine (data : EditorDataPtr; s : ARRAY OF CHAR);
 BEGIN
     IF data = NIL THEN RETURN END;
     IF data^.count >= EDMAXLINES THEN RETURN END;
-    i := data^.count;
+    IF (data^.count = 1) AND (data^.len[0] = 0) THEN
+        i := 0; data^.count := 0
+    ELSE
+        i := data^.count
+    END;
     n := TextLen(s);
     IF n > EDMAXCOL - 1 THEN n := EDMAXCOL - 1 END;
     CopyText(data^.line[i], s);
@@ -1370,6 +1583,11 @@ BEGIN
     RETURN data^.dirty
 END EditorDirty;
 
+PROCEDURE EditorSetLanguage (data : EditorDataPtr; lang : INTEGER);
+BEGIN
+    IF data # NIL THEN data^.lang := lang END
+END EditorSetLanguage;
+
 PROCEDURE DrawView(v : ViewPtr);
     VAR c : ViewPtr;
 BEGIN
@@ -1773,12 +1991,19 @@ BEGIN
         END
     ELSIF v^.kind = VEditor THEN
         IF v^.ed # NIL THEN
-            IF e.key = kChar THEN EdInsertChar(v^.ed, e.ch)
-            ELSIF e.key = kEnter THEN EdNewLine(v^.ed)
+            IF e.key = kChar THEN
+                IF NOT IsIdentChar(CHR(e.ch)) THEN EdUpcaseWord(v^.ed) END;
+                EdInsertChar(v^.ed, e.ch)
+            ELSIF e.key = kSpace THEN
+                EdUpcaseWord(v^.ed);
+                EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')))
+            ELSIF e.key = kEnter THEN
+                EdUpcaseWord(v^.ed); EdNewLine(v^.ed)
             ELSIF e.key = kBack THEN EdBackspace(v^.ed)
             ELSIF e.key = kDel THEN EdDelete(v^.ed)
             ELSIF e.key = kIns THEN v^.ed^.ins := NOT v^.ed^.ins
             ELSIF e.key = kTab THEN
+                EdUpcaseWord(v^.ed);
                 EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')));
                 WHILE (v^.ed^.cx MOD 4) # 0 DO
                     EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')))

BIN
tvision-m2/tv.o