commit e1549c08a2cd75d6837bb3f1cd8a32aef7f11cf5
parent eb8f7b80f4634073e0edc15b53c082c0ad7c2f25
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Mon, 6 Feb 2023 22:18:59 -0500
text/ged: add visual mode
Also, rename text/ed's "convenience layer" to avoid clashes with "i" and "j".
Diffstat:
5 files changed, 91 insertions(+), 46 deletions(-)
diff --git a/fs/doc/text/ed.txt b/fs/doc/text/ed.txt
@@ -22,6 +22,16 @@ Layer.
Because these shortcut can clash with other words in the system, it is
recommended to load the text editor in its own context.
+## Mark API
+
+A Mark's purpose is to remember a particular position. For now, it's not used
+much. There is a global "mark" structbind to a single Mark.
+
+Fields:
+
+lpos Line position (index)
+cpos Character position
+
## Line API
The Line structure describes a line in the Ed buffer. It is a linked list. The
@@ -142,22 +152,27 @@ Words:
:insertline ( self -- )
Insert a new empty line before selection.
+:mark! ( self -- )
+ Set "mark" structbind to buffer's current position.
+
## Convenience layer
The convenience layer is a collection of words with short names that makes
interacting with the API above easier. Its choice of name is broadly inspired by
UNIX's "vi" program.
-The "input" words ("a" and "i") work in a peculiar way because they consume the
-rest of the input line. For example, let's say that you type this:
+The "input" words ("I", "o", "O") work in a peculiar way because they consume
+the rest of the input line. For example, let's say that you type this:
- foo bar i Hello there
+ foo bar I Hello there
-"foo", "bar" and "i" will be interpreted normally, but "Hello there" will be
+"foo", "bar" and "I" will be interpreted normally, but "Hello there" will be
copied without going through the interpreter. The content is then sent to the
buffer with :puts. The LF character is not sent (otherwise, every insert
operation would result in a new line being inserted).
+"I" is used instead of "i" to avoid shadowing the iterator loop variable.
+
### API
Values:
@@ -174,7 +189,7 @@ s ( -- )
Print "stats", that is, the current line with :cprint followed by another
line with "current lineno" / "line count".
-i ( "...\n" -- )
+I ( "...\n" -- )
Insert typed line before the current one and select it.
o ( "...\n" -- )
@@ -189,36 +204,33 @@ f ( "...\n" -- )
n ( -- )
Repeat the last find
-d ( n -- )
- Delete "n" lines starting from the current one.
+dl ( n -- )
+ Delete "n" lines starting from current pos.
+
+dc ( n -- )
+ Delete "n" characters starting from current pos.
g ( lineno -- )
Select line number "lineno"
-h ( n -- )
+c- ( n -- )
Go "n" characters to the left.
-H ( -- )
+bol ( -- )
Go to the beginning of the line.
-l ( n -- )
+c+ ( n -- )
Go "n" characters to the right.
-L ( -- )
+eol ( -- )
Go to the end of the line.
-j ( n -- )
+l- ( n -- )
Go "n" lines up.
-k ( n -- )
+l+ ( n -- )
Go "n" lines down.
-{ ( -- )
- Go one "page" up.
-
-} ( -- )
- Go one "page" down.
-
eof ( -- )
Go to the last line of the buffer.
diff --git a/fs/doc/text/ged.txt b/fs/doc/text/ged.txt
@@ -18,17 +18,28 @@ enters the grid keybindings loop.
Keybindings are rudimentary, but you have the ":" binding allowing you to
quickly enter fancier commands (example "i hello").
-## Keybindings
+## Visual mode
-Some key bindings map directly to their the text/ed convenience word of the same
-name, with the same behavior. Those are:
+Pressing "v" toggles the visual mode. When toggled on, current position is
+marked and when moving selection afterwards, the range between the mark and the
+selection is shown visually.
- h j k l H L i f n o O
+For now, it doesn't do anything else, but soon enough it will be very powerful.
-Others have this behavior:
+## Keybindings
q Quit
+h Go left
+j Go down
+k Go up
+l Go right
+i Insert
+o Insert line after
+O Insert line before
+f Find
+n Find next
z zoom. Reframe the editor so that selection is in the middle of the screen.
+v Toggle visual mode
] move 1 page down
[ move 1 page up
: enter a single line of command that will be interpreted as forth
diff --git a/fs/tests/text/ed.fs b/fs/tests/text/ed.fs
@@ -10,21 +10,21 @@ edbuf :linecnt 1 #eq
edbuf :linecnt 1 #eq
f" /tests/txtfile" edload
1 g
-5 capture l
+5 capture c+
S" with some text\n ^\n1 / 17" #s=
-7 capture j
+7 capture l+
S" be\n ^\n8 / 17" #s=
-5 capture k
+5 capture l-
S" (oh well maybe grow)\n ^\n3 / 17" #s=
-capture L
+capture eol
S" (oh well maybe grow)\n ^\n3 / 17" #s=
-capture H
+capture bol
S" (oh well maybe grow)\n^\n3 / 17" #s=
-4 l 5 capture dc
+4 c+ 5 capture dc
S" (oh maybe grow)\n ^\n3 / 17" #s=
2 capture dl
S" tests that process text\n ^\n3 / 15" #s=
-L capture i abc
+eol capture I abc
S" tests that process textabc\n ^\n3 / 15" #s=
capture o appended line
S" appended line\n ^\n4 / 16" #s=
@@ -34,8 +34,8 @@ S" inserted line\n ^\n4 / 17" #s=
S" with some text\n ^\n0 / 16" #s=
0 g capture O at the beginning of the buf
S" at the beginning of the buf\n ^\n0 / 17" #s=
-17 j edbuf lpos 16 #eq
-1 j edbuf lpos 16 #eq
+17 l+ edbuf lpos 16 #eq
+1 l+ edbuf lpos 16 #eq
\ Now let's save this to a BlobFile
2 const TOTSEC
diff --git a/fs/text/ed.fs b/fs/text/ed.fs
@@ -5,6 +5,12 @@
: nspcs ( n -- ) for spc> next ;
+struct[ Mark
+ sfield lpos
+ sfield cpos
+]struct
+here Mark SZ allot0 structbind Mark mark
+
struct[ Line
sfield _next
sfield cnt
@@ -129,6 +135,8 @@ extends IO struct[ Edbuf
V1 sel llitern ( delpoint tgt )
tuck swap ( tgt tgt prev ) ! ( tgt )
?dup not if V1 lines llend then r> _sel! ;
+
+ : :mark! ( self -- ) dup lpos to mark lpos cpos to mark cpos ;
]struct
Edbuf :new structbind Edbuf edbuf
@@ -142,20 +150,20 @@ Edbuf :new structbind Edbuf edbuf
: ?s print? if s then ;
: g ( n -- ) edbuf :go ?s ;
-: h ( n -- ) edbuf :goleft ?s ;
-: H ( -- ) $10000 h ;
-: l ( n -- ) edbuf :goright ?s ;
-: L ( -- ) $10000 l ;
-: j ( n -- ) edbuf :godown ?s ;
-: k ( n -- ) edbuf :goup ?s ;
+: c- ( n -- ) edbuf :goleft ?s ;
+: bol ( -- ) $10000 c- ;
+: c+ ( n -- ) edbuf :goright ?s ;
+: eol ( -- ) $10000 c+ ;
+: l+ ( n -- ) edbuf :godown ?s ;
+: l- ( n -- ) edbuf :goup ?s ;
: p ( -- ) pagesz edbuf :print ;
: dc ( n -- ) edbuf :delchars ?s ;
: dl ( n -- ) edbuf :dellines ?s ;
-: i console :readline edbuf :puts ?s ;
+: I console :readline edbuf :puts ?s ;
: f console :readline edbuf :find ?s ;
: n edbuf :findnext ?s ;
-: o edbuf :appendline i ;
-: O edbuf :insertline i ;
+: o edbuf :appendline I ;
+: O edbuf :insertline I ;
: edload ( -- )
0 file :seek edbuf :empty
edbuf :self file :spit edbuf _sel$$ ;
diff --git a/fs/text/ged.fs b/fs/text/ged.fs
@@ -4,6 +4,9 @@ require /sys/grid.fs
?f<< /lib/wordtbl.fs
grid LINES 1- const _height
+grid COLS grid LINES * 1- const _maxpos
+0 value visualmode
+0 value _top
: _spitline ( pos line -- )
swap grid :spiton
@@ -11,12 +14,21 @@ grid LINES 1- const _height
dup if swap Line ptr over grid :write else nip then ( n )
grid COLS -^ ?dup if nspcs then grid :spitoff ;
+: _scrpos ( pos -- pos ) _top grid COLS * - max0 _maxpos min ;
+
+: _clrhighlight ( -- ) 0 _maxpos 1+ for2 0 i grid :highlight next ;
+
+: _highlight ( -- )
+ _clrhighlight
+ mark lpos grid COLS * mark cpos + _scrpos
+ edbuf lpos grid COLS * edbuf cpos + _scrpos
+ ?swap for2 1 i grid :highlight next ;
+
: _spitpage ( fromline -- )
0 swap _height for ( lineno line )
dup if over grid COLS * over _spitline else over grid :clrline then
dup if llnext then swap 1+ swap next 2drop ;
-0 value _top
: _top! ( lineno -- )
dup to _top edbuf lines Line :itern nip _spitpage ;
@@ -25,7 +37,8 @@ grid LINES 1- const _height
_height 1- - max0 _top over <= if _top! else drop then then ;
: _refresh ( -- )
- _reframe edbuf lpos _top - grid COLS * edbuf cpos + grid :pos! ;
+ _reframe edbuf lpos _top - grid COLS * edbuf cpos + grid :pos!
+ visualmode if _highlight then ;
: _statusclr _height grid :clrline ;
: _statusline _statusclr _height grid COLS * grid :pos! ;
@@ -34,7 +47,7 @@ grid LINES 1- const _height
: _rdln[] rdln :range 1- ;
: _pagerefresh _top _top! ;
-S" qhjklHLifnoOz[]:" const KEYS
+S" qhjklHLifnoOzv[]:" const KEYS
KEYS c@ wordtbl handlers
:w _statusline quit ;
:w 1 edbuf :goleft ;
@@ -49,10 +62,11 @@ KEYS c@ wordtbl handlers
:w _typeline if edbuf :appendline _rdln[] edbuf :write then _pagerefresh ;
:w _typeline if edbuf :insertline _rdln[] edbuf :write then _pagerefresh ;
:w edbuf lpos _height >> - max0 _top! ;
+:w visualmode not to visualmode edbuf :mark! _pagerefresh _clrhighlight ;
:w pagesz edbuf :goup ;
:w pagesz edbuf :godown ;
:w _typeline _statusline if rdln :interpret then _pagerefresh ;
-: ged 0 to _top begin
+: ged 0 to _top 0 to visualmode begin
_refresh key KEYS c@+ [c]? ( idx )
dup 0>= if handlers swap wexec else drop then again ;