retroforth/vm/nga-c-native-x86/x86/Block-Editor.retro
crc c676b83793 fix many uses of deprecated names (thanks john_cephalopoda) [first of a few patched related to this]
FossilOrigin-Name: faa0b6ed0fa09b4307285cfc8a6172762636285bb7864e64cff2995e4d9ba324
2020-09-30 23:02:23 +00:00

361 lines
10 KiB
Forth
Executable file

#!/usr/bin/env retro
# A Block Editor
I've written numerous block editors over the years. This is a
new one that I'm planning to eventually use with Retro/Native.
This presents a visual, (briefly) modal interface.
0-----#---1---------2---------3---------4---------5---------6---
| This is the new block editor! |
# |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
0---------1---------2---------3---------4---------5---------6---
| Output is shown here |
| |
| |
| |
0---------1---------2---------3---------4---------5---------6---
#2 1:0 ::
The horizontal rulers have column indicators, there are cursor
position indicators (and the actual cursor location shows at the
intersection). The bottom line has the current block number, line
number, and column number.
Key Bindings
| Key | Action |
| --- | ------------------------------ |
| H | Save and select previous block |
| S | Save and select next block |
| h | Move cursor left |
| t | Move cursor down |
| n | Move cursor up |
| s | Move cursor right |
| i | Save the current block |
| y | Reload the current block |
| a | Edit text |
| c | Move cursor to prior word |
| r | Move cursor to next word |
| 1 | Evaluate the current block |
| x | Erase the current block |
| q | Save the Blocks and Quit |
| ` | Clear output buffer |
| 7 | Share block |
| b | Copy line |
| m | Paste line |
| B | Copy block |
| M | Paste block |
| z | Delete line |
The key bindings are oriented around the Dvorak keyboard layout
which I use. The key map leverages an approach I stole from
Samuel Falvo II's VIBE editor: the key handlers are words in
the dictionary with a format like:
editor:key<*>
With the `*` being the key.
# Sample Blocks
This will create an empty `retro.blocks` file if one is not
found. I have a sample included, but it is compressed. To use
the sample run `gunzip retro.blocks` and keep a copy or a
symlink to it in your CWD.
# Configuration
I define the number of blocks and the file to use. On the non
hosted Retro systems the block file will be replaced by a
block storage device.
~~~
#16 'BLOCKS const
~~~
# Variables
I am keeping some data in variables. This will include the
current block number, the current line, and the current row.
~~~
'CurrentBlock var
'CurrentLine var
'CurrentCol var
~~~
# Buffers
I have one memory region for the current block and a second
one for the entire set of blocks. I keep the blocks in RAM
for performance.
There is also one half block set aside for the output display.
This is the `TOB` (Text Output Buffer).
~~~
'Block d:create #1025 allot
'Blocks d:create
BLOCKS #1024 * allot
'TOB d:create #513 allot
~~~
# Block File I/O
This should be replaced with the block storage device once
that is added to Nga.
~~~
:blocks:initialize (-)
&Blocks #1024 BLOCKS * [ #32 over store n:inc ] times drop ;
:block:write (-)
;
:block:read (-)
;
~~~
These are used to load a block into the active buffer and
copy it back to the full set.
~~~
{{
:current-block @CurrentBlock #1024 * &Blocks + ;
:next [ n:inc ] bi@ ;
:copy dup-pair fetch swap store ;
---reveal---
:block:select (n-)
!CurrentBlock
[ &Block buffer:set
current-block #1024
[ fetch-next buffer:add ] times drop ] buffer:preserve ;
:block:update (-)
current-block &Block #1024 [ copy next ] times drop-pair ;
}}
~~~
# Block Display
This is fairly long, but mostly due to my desire to have a
more complex display with column and cursor indicators.
It should be pretty straightforward though.
~~~
:tty:clear (-) clear ;
{{
:cursor (cl-)
swap vga:move-cursor ;
:indicators @CurrentCol #3 + #0 cursor $# c:put
#1 @CurrentLine #1 + cursor $# c:put
#1 #24 cursor ;
:ruler
'___0---------1---------2---------3 s:put
'---------4---------5---------6--- s:put nl ;
:block# $# c:put @CurrentBlock n:put ;
:pos @CurrentLine n:put $: c:put @CurrentCol n:put ;
:status block# sp pos '_::_ s:put dump-stack ;
:format '_|_ s:put call '_| s:put nl ;
:line [ #64 [ fetch-next c:put ] times ] format ;
:code &Block #16 [ line ] indexed-times drop ;
:format '_|_ s:put call '_| s:put nl ;
:tob &TOB #4 [ line ] times drop ;
---reveal---
:block:display (-)
tty:clear ruler code ruler tob ruler indicators status ;
}}
~~~
# Text Output Buffer
~~~
{{
'TNext var
:scroll? (-f)
@TNext #256 gt? ;
:scroll-up (-)
&TOB #64 + &TOB #193 copy #193 !TNext
&TOB #193 + #64 [ #32 over store n:inc ] times drop ;
---reveal---
:c:put<TOB> (c-)
dup #10 eq? [ drop @TNext #64 + dup #64 mod - !TNext ]
[ @TNext &TOB + store &TNext v:inc ] choose
scroll? [ scroll-up ] if ;
:with-tob (q-)
&c:put<TOB> &c:put set-hook call &c:put unhook ;
:tob:initialize (-)
&TOB #512 [ #32 over store n:inc ] times drop #0 !TNext ;
}}
~~~
# The Editor Core
~~~
{{
:cursor ;
:handler
c:get
'editor:key<_> [ #11 + store ] sip d:lookup
dup n:-zero? [ d:xt fetch call ] [ drop ] choose ;
---reveal---
'DoneEditing var
:edit
FALSE !DoneEditing
tob:initialize blocks:initialize block:read #0 block:select
[ block:display cursor handler @DoneEditing ] until ;
}}
~~~
# The Rest
# Key Handlers
All of the actual work is done by the words that make up
the key handlers.
## Evaluate block
The `1` key is used to evaluate a block. To avoid wasting
space, a block of memory is allocated to use as a temporary
`Heap`. The actual `Heap` is saved and restored during the
tokenization.
~~~
{{
'Tokens d:create #4097 allot
:prepare &Tokens #4096 [ #0 over store n:inc ] times drop ;
:generate &Tokens !Heap &Block #32 s:tokenize ;
:item dup s:length n:zero? [ drop ] [ interpret ] choose ;
:process [ item ] a:for-each ;
---reveal---
:editor:key<1>
[ prepare &Heap [ generate ] v:preserve process ] with-tob ;
}}
~~~
## Cursor and Block Movement
~~~
{{
:boundaries
&CurrentLine #0 #15 v:limit
&CurrentCol #0 #63 v:limit ;
:keep-in-range #0 BLOCKS n:dec n:limit ;
---reveal---
:editor:key<H>
block:update
@CurrentBlock n:dec keep-in-range block:select tty:clear ;
:editor:key<S>
block:update
@CurrentBlock n:inc keep-in-range block:select tty:clear ;
:editor:key<n> &CurrentLine v:dec boundaries ;
:editor:key<h> &CurrentCol v:dec boundaries ;
:editor:key<s> &CurrentCol v:inc boundaries ;
:editor:key<t> &CurrentLine v:inc boundaries ;
}}
~~~
## Navigate to prior, next word
These are helpful to quickly navigate through a block.
~~~
{{
:limit (n-n) &Block dup #1024 + n:limit ;
:fetch-prior (a-Ac) [ n:dec ] [ fetch ] bi ;
:find-next-word
@CurrentLine #64 * @CurrentCol + &Block + n:inc
repeat fetch-next #32 -eq? 0; drop again ;
:find-prior-word
@CurrentLine #64 * @CurrentCol + &Block + n:dec
repeat fetch-prior #32 -eq? 0; drop again ;
:select-next (-)
find-next-word n:dec limit &Block -
#64 /mod !CurrentLine !CurrentCol ;
:select-prior (-)
find-prior-word n:inc limit &Block -
#64 /mod !CurrentLine !CurrentCol ;
---reveal---
:editor:key<c> select-prior ;
:editor:key<r> select-next ;
}}
~~~
## Edit (Replace)
~~~
{{
:dest @CurrentLine #64 * @CurrentCol + &Block + ;
:chars over s:length ;
:copy [ dup-pair &fetch dip store &n:inc bi@ ] times ;
---reveal---
:editor:key<a>
s:get dest chars copy drop-pair tty:clear ;
}}
~~~
## Erase
~~~
:editor:key<x> &Block #1024 [ #32 over store n:inc ] times drop ;
:editor:key<z> &Block @CurrentLine #64 * + #64
[ #32 over store n:inc ] times drop ;
~~~
## Copy/Paste
~~~
{{
'Copy d:create #65 allot
'CopyBlock d:create #1025 allot
---reveal---
:editor:key<b> &Block @CurrentLine #64 * + &Copy #64 copy ;
:editor:key<m> &Copy &Block @CurrentLine #64 * + #64 copy ;
:editor:key<B> &Block &CopyBlock #1024 copy ;
:editor:key<M> &CopyBlock &Block #1024 copy ;
}}
~~~
## Misc.
~~~
:editor:key<i> block:update block:write ;
:editor:key<y> @CurrentBlock block:select ;
:editor:key<q> TRUE !DoneEditing ;
:editor:key<`> tob:initialize ;
~~~
~~~
:editor:key<!>
&Block @CurrentBlock #2 * ata:write
&Block #512 + @CurrentBlock #2 * n:inc ata:write ;
:editor:key<@>
&Block @CurrentBlock #2 * ata:read
&Block #512 + @CurrentBlock #2 * n:inc ata:read ;
~~~