This implements a block editor for RETRO. It provides a visual interface, inspired by VIBE and REM and allows for extending the environment by simply writing new words that handle specific keys. It also requires a network connection and the Tuporo Gopher server which provides the actual block store.
Some architectural notes:
• the blocks are stored on a server, not locally
• editing is modal (ala vi)
• editor commands are just normal words in the dictionary
• this uses ANSI escape sequences and so requires a traditional terminal
or terminal emulator
• dvorak based key bindings
So getting started, some configuration settings for the server side:
SERVER returns the server url and port.
Next, create a buffer to store the currently loaded block. With the server-side storage I don't need to keep more than the current block in memory.
A block is 1024 bytes; this includes one additional to use a terminator. Doing this allows the block to be passed to s:evaluate as a string.
I also define a variable, Current-Block, which holds the number of the currently loaded block.
With that done, it's now time for a word to load a block from the server.
Roo requires an associated gopher server (tuporo). This is a special server that provides access to Forth blocks across a network. The selectors we are interested in are:
/r/<block#>
Which returns a raw block (1024 bytes), and:
/s/<block#>/text
Which copies the text into the specified block.
So first, define words to construct the selectors:
And then words to actually talk to the server:
All done :)
The Mode variable will be used to track the current mode. I have chosen to implement two modes: command ($C) and insert ($I).
Command mode will be used for all non-entry related options, including (but not limited to) cursor movement, block navigation, and code evaluation.
So with two modes I only need one variable to track which mode is active, and a single word to switch back and forth between them.
I need a way to keep track of where in the block the user currently is. So two variables: one for the row and one for the column:
To ensure that the cursor stays within the block, I am implementing a constrain word to limit the range of the cursor. Thanks to v:limit this is really easy.
And then the words to adjust the cursor positioning:
The other bit related to the cursor is a word to decide the offset into the block. This will be used to aid in entering text.
The last bit here is insert-character which inserts a character to cursor-position in the Block and moves the cursor to the right.
Handling of keys is essential to using Roo. I chose to use a method that I borrowed from Sam Falvo II's VIBE editor and leverage the dictionary for key handlers.
In Roo a key handler is a word in the roo: namespace. A word like:
roo:c:a
Will implement a handler called when 'a' is typed in command mode. And
roo:i:`
Would implement a handler for the '' key in insert mode.
In command mode keys not matching a handler are ignored. For words that do match up to a control word, the word will be called. In insert mode, any keys not mapped to a word will be inserted into the block at the current position.
My default keymap will be (subject to change!):
` Switch modes h Cursor left t Cursor down n Cursor up s Cursor right H Previous block S Next block e Evaluate block q Quit
Getting started, I define a word to take a character and pack it into a string. It then tries to find this in the dictionary.
With that, I can implement another helper: call-dt, which will take the dictionary token returned by handler-for and call the xt for the word.
The final piece is the top level key handler. This has the following jobs:
• try to find a handler for the key
• if mode is $C and the handler is valid, call the handler
• if mode is $I and the handler is invalid, insert the key into the
block
• if mode is $I and the handler is valid, call the handler
Having finished this, it's trivial to define the majority of the basic commands:
I only define one command in input mode, to switch back to command mode:
Note that this calls save-block to update the remote block storage. This is the only place I call save-block.
One last word is a handler to allow the editor to be closed cleanly. This also has a variable, Completed, which will be used to decide if editing is finished.
The block display is kept minimalistic. Each line is bounded by a single vertical bar (|) on the right edge, and there is a separatator line at the bottom to indicate the base of the block. To the left of this is a single number, indicating the current block number. This is followed by the mode indicator.
I also display the current stack contents below the block.
The display looks like:
(blank) | :roo:c:+ (nn-m) + ; | :roo:c:1 (-n) #1 ; :roo:c:2 (-n) #2 ; | :roo:c:4 (-n) #4 ; :roo:c:3 (-n) #3 ; | | | | | | | | | | | | | ----------------------------------------------------------------+ 29C 1 2 <3>
The cursor display will be platform specific.
All that's left is a single top level loop to tie it all together.
EOF retro /tmp/_roo.forth rm -f /tmp/_roo.forth stty -cbreak