duskos

dusk os fork
git clone git://git.alexwennerberg.com/duskos
Log | Files | Refs | README | LICENSE

commit 635922780acf4506816bd56593a17ad73b6ab39d
parent 12eb9c4837b9395ae89a93625775574e9e77dabe
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Sat, 14 Jan 2023 21:59:29 -0500

text/ed: new unit, first steps (wip)

see doc/text/ed

Diffstat:
MMakefile | 6+++---
Mfs/doc/dict.txt | 1+
Afs/doc/text/ed.txt | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfs/sys/rdln.fs | 2+-
Mfs/tests/all.fs | 1+
Afs/tests/text/all.fs | 2++
Afs/tests/text/ed.fs | 10++++++++++
Afs/text/ed.fs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 253 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile @@ -65,9 +65,9 @@ testcc: dusk testemul: dusk echo "' byefail ' abort realias f<< tests/emul/all.fs bye" | ./dusk || (echo; exit 1) -.PHONY: testgr -testgr: dusk - echo "' byefail ' abort realias f<< tests/gr/all.fs bye" | ./dusk || (echo; exit 1) +.PHONY: testtext +testtext: dusk + echo "' byefail ' abort realias f<< tests/text/all.fs bye" | ./dusk || (echo; exit 1) .PHONY: clean clean: diff --git a/fs/doc/dict.txt b/fs/doc/dict.txt @@ -255,6 +255,7 @@ llnext ll -- ll Yield next LL element. llend ll -- ll Iterate LL until we reach the last element. llprev tgt ll -- ll From "ll", iterate LL until we reach the element when the LL pointer points to "tgt". +llappend elem ll -- Append "elem" at the end of "ll". lladd ll -- ll Write a new LL element to here, yield it, then write its address to the last element of "ll". llinsert 'll -- ll Given a *pointer* to a LL, write a new LL to here and diff --git a/fs/doc/text/ed.txt b/fs/doc/text/ed.txt @@ -0,0 +1,155 @@ +# Text Editor + +TODO: this editor is a work in progress. What is described below isn't yet +entirely implemented. + +This line based text editor lives at text/ed.fs and allows the examination and +modification of text contents. + +The central structure of this unit is Edbuf (editor buffer) and its API is +described below. You will typically only want one copy of this structure and +this unit automatically instantiate one and binds it to the "edbuf" structbind. + +This structure has all the methods to need to load, save and modify text +contents from within it. The buffer is a linked list of Line structures which +themselves reference buffers in an Arena allocator (see doc/lib/alloc). Lines +have a maximum length of ARENASZ bytes. + +Interacting directly with the buffer structure gives you a maximum of +flexibility, but is a bit verbose. To make text operations more efficient, +shortcut words are provided. They are described below. + +Because these shortcut can clash with other words in the system, it is +recommended to load the text editor in its own context (see doc/lib/context). + +## Line API + +The Line structure describes a line in the Ed buffer. It is a linked list. The +line structure does not include the LF character. This means that an empty line +has a cnt of 0. + +Fields: + +next Next line in LL +cnt Number of characters in the line +ptr Pointer to the first character of the line + +## Edbuf API + +Edbuf extends the IO struct (see doc/sys/io), allowing some pretty nice +interactions with other IO-aware words. How it works is that its "IO position" +is related to its line positioning. Whenever we change the active line, its +"IO position" becomes the beginning of that line. + +An IO read of Edbuf reads the requested number of bytes from the current line +up until the end of the line. If the read results in partially reading a line, +"cpos" is updated with a "sub-line" position. That number is the index we're +currently at within the current line. + +An IO write writes to the currently selected line. If the line already had +contents, the written part is concatenated to the pre-existing part. If a LF is +encountered, a new line is created and :writebuf stops. + +Therefore, "loading" a file contents to the buffer is a :write to it. Saving the +buffer somewhere else is a :read from it. + +No special care is given to ASCII control characters except for LF. Try to avoid +them in your text files. + +Line numbers are 0-indexed. + +Fields: + +buf An arena buffer with the contents of lines (see doc/lib/alloc). +lines The first Line of the LL. +sel A pointer to the select Line. +cpos Current position (character index) on the current line. + +Words: + +:new ( -- edbuf ) + Create a new Edbuf structure with its own "buf" arena. + +:empty ( self -- ) + Empty the buffer, resetting positions and arena allocator. + +:line ( n self -- line ) + Return Line reference for line at index "n". + +:print ( n self -- ) + Print "n" lines, starting from current line, to stdio. + +:cprint ( self -- ) + Print current line with a second line under it containing a "^" character + indicating the current "cpos". + +:insert ( n self -- ) + Insert "n" empty lines at "lpos", displacing subsequent lines by "n". + +:delete ( n self -- ) + Delete "n" lines starting from "lpos". + +:cinsert ( a u self -- ) + Insert character range "a u" at current line/char position. This means copying + the current line buffer in a new area (otherwise we overwrite another line + with the new contents) with the new content inserted in the middle. + +:cdelete ( n self -- ) + Delete n characters from current line/char position. + +:cfind ( a u self -- ) + Search current line for substring contained in range "a u". If found, move + "cpos" to it. + +## Convenience layer + +The convenience layer is a collection of words with short names that makes +interacting with the API above easier. The "input" words ("a" and "i") work in +a peculiar way because they piggy-back on sys/rdln. When one of thoe words is +executed, it fetches the rest of the sys/rdln line directly and copies that +string as the inserted/added string. For example, let's say that you type this: + + foo bar i Hello there + +"foo", "bar" and "i" will be interpreted normally, but "Hello there" will be +copied without going through the interpreter. The, rdln's input buffer is +cleared and we go back to regular interpretation. + +Values: + +print? Boolean flags indicating whether all actions are followed by :cprint. +pagesz Number of lines in a "page" + +Words: + +p ( -- ) + Print one "page". + +s ( -- ) + Print "stats", that is, the current line with :cprint followed by another + line with "current lineno" / "line count". + +i ( "...\n" -- ) + Insert typed line before the current one and select it. + +a ( "...\n" -- ) + Append types line after the current one and select it. + +d ( n -- ) + Delete "n" lines starting from the current one. + +g ( lineno -- ) + Select line number "lineno" + +{ ( -- ) + Go one "page" up. + +} ( -- ) + Go one "page" down. + +eof ( -- ) + Go to the last line of the buffer. + +edload ( -- ) + Empty the buffer and load the entire contents of "file" (see doc/sys/file) + into it. diff --git a/fs/sys/rdln.fs b/fs/sys/rdln.fs @@ -1,7 +1,7 @@ \ Readline interface require /sys/kbd.fs -64 const LNSZ +$80 const LNSZ create in( LNSZ allot here value in) in) value in> diff --git a/fs/tests/all.fs b/fs/tests/all.fs @@ -8,4 +8,5 @@ f<< /tests/comp/c/all.fs f<< /tests/ar/all.fs f<< /tests/emul/all.fs f<< /tests/gr/all.fs +f<< /tests/text/all.fs ." All tests passed\n" diff --git a/fs/tests/text/all.fs b/fs/tests/text/all.fs @@ -0,0 +1,2 @@ +f<< /tests/text/ed.fs + diff --git a/fs/tests/text/ed.fs b/fs/tests/text/ed.fs @@ -0,0 +1,10 @@ +?f<< /tests/harness.fs +?f<< /text/ed.fs +testbegin +\ Ed tests +f" /init.fs" edload +\ TODO: if I run this test in isolation, "s" works fine. If I run the test from +\ the whole suite, it spits garbage and unbalances PS. +\ s +p +testend diff --git a/fs/text/ed.fs b/fs/text/ed.fs @@ -0,0 +1,80 @@ +?f<< /lib/arena.fs +?f<< /lib/fmt.fs +?f<< /lib/str.fs + +: llidx ( tgt ll -- idx ) + 0 >r begin 2dup = if 2drop r> neg exit then llnext next + abort" llprev failed" ; + +struct[ Line + sfield next + sfield cnt + sfield ptr +]struct + +extends IO struct[ Edbuf + sfield buf + sfield lines + sfield sel + sfield cpos + + : _readbuf ( a self -- a? read-n ) abort" TODO" ; + + : _newline ( self -- line ) + Line SZ swap buf Arena :allot dup Line SZ 0 fill ; + + : _addline ( self -- ) + dup _newline swap 2dup to sel lines llappend ; + + : _writeline ( a u self -- ) dup >r sel >r \ V1=self V2=sel + V2 Line ptr if \ we need to concatenate "a u" to line + V2 Line cnt over + V1 buf Arena :ensure ( a u ) + V2 Line ptr V2 Line cnt V1 buf Arena :move ( a u new-a ) + V2 to Line ptr ( a u ) + tuck V1 buf Arena :move drop ( u ) + else ( a u ) + tuck V1 buf Arena :move V2 to Line ptr then ( u ) + r> to+ Line cnt rdrop ; + + : _writebuf ( a n self -- written-n ) >r \ V1=self + 2dup LF rot> [c]? ( a u idx ) dup 0< if + drop tuck r> _writeline ( written-n ) + else ( a u idx ) + nip tuck r@ _writeline 1+ ( written-n ) + r> _addline then ; + + : :empty ( self -- ) + dup buf Arena :reset + 0 over to cpos + dup _newline swap 2dup to lines to sel ; + + : :new ( -- edbuf ) + Arena :new here ( arena edbuf ) + 0 ( putback ) , ['] _readbuf , ['] _writebuf , ['] drop , ['] :empty , + swap ( arena ) , 0 ( lines ) , 0 ( sel ) , 0 ( cpos ) , + dup :empty ; + + : :linecnt ( self -- cnt ) lines llcnt ; + + : :selpos ( self -- idx ) dup sel swap lines llidx ; + + : :print ( n self -- ) >r \ V1=self + >r V1 sel begin ( line ) + dup Line ptr over Line cnt stdio :write LF stdout + llnext dup not if leave then next drop rdrop ; +]struct + +Edbuf :new structbind Edbuf edbuf + +\ Convenience layer + +20 value pagesz + +: edload ( -- ) + 0 file :seek edbuf :empty + edbuf :self file :spit + edbuf lines to edbuf sel ; + +: s ( -- ) edbuf :linecnt edbuf :selpos .f" %d / %d\n" ; + +: p ( -- ) pagesz edbuf :print ;