#!/usr/bin/env rre This will (hopefully) be a functional text editor written in RETRO. It draws influence from my earlier block editors, but is intended to operate on actual text files instead of blocks. First up, several variables and constants that are used through the rest of the code. ~~~ 'SourceFile var 'CurrentLine var 'FID var ~~~ ~~~ #12 'MAX-LINES const '/tmp/rre.edit 'TEMP-FILE s:const ~~~ Get the name of the file to edit. If no file is provided, exit. ~~~ sys:argc n:zero? [ #0 unix:exit ] if #0 sys:argv s:keep !SourceFile ~~~ This is just a shortcut to make writing strings to the current file easier. ~~~ :file:puts (s-) [ @FID file:write ] s:for-each ASCII:LF @FID file:write ; ~~~ I now turn my attention to displaying the file. I am aiming for an interface like: : --------------------------------------------------------------- * 99: 100: :n:square dup * ; 101: 102: This is the current line 103: --------------------------------------------------------------- The * denotes the currently selected line. I start with words to count the number of lines in the file and advance to the currently selected line. ~~~ :count-lines (-) #0 @SourceFile [ drop n:inc ] file:for-each-line ; :skip-to @CurrentLine #0 n:max [ @FID file:read-line drop ] times ; ~~~ Now for words to format the output. This should all be pretty clear in intent. ~~~ :clear-display (-) ASCII:ESC '%c[2J s:with-format puts nl ; :---- (-) #80 [ $- putc ] times nl ; :header (-) count-lines @SourceFile '%s_:_%n_lines\n s:with-format puts ; :pad (n-n) dup #0 #9 n:between? [ '____ puts ] if dup #10 #99 n:between? [ '___ puts ] if dup #100 #999 n:between? [ '__ puts ] if dup #1000 #9999 n:between? [ '_ puts ] if ; :mark-if-current (n-n) dup @CurrentLine eq? [ $* putc ] [ sp ] choose ; :line# (n-) putn ':_ puts ; :display-line (n-n) dup mark-if-current pad line# n:inc @FID file:read-line puts nl ; :display (-) @SourceFile file:R file:open !FID clear-display header ---- skip-to @CurrentLine count-lines MAX-LINES n:min [ display-line ] times drop ---- dump-stack @FID file:close ; ~~~ With the code to display the file done, I can proceed on to words for handling editing. First, is a word to delete contents of the current line. The process here is to just write all but the current line to a dummy file, replacing the current line text with a newline. Then replace the original file with the dummy one. ~~~ :current? (n-nf) over @CurrentLine eq? ; :delete-line (-) TEMP-FILE file:W file:open !FID #0 @SourceFile [ current? [ drop s:empty ] if file:puts n:inc ] file:for-each-line drop @FID file:close @SourceFile TEMP-FILE 'mv_%s_%s s:with-format unix:system ; ~~~ ~~~ :kill-line (-) TEMP-FILE file:W file:open !FID #0 @SourceFile [ current? [ drop ] [ file:puts ] choose n:inc ] file:for-each-line drop @FID file:close @SourceFile TEMP-FILE 'mv_%s_%s s:with-format unix:system ; ~~~ ~~~ :add-line (-) TEMP-FILE file:W file:open !FID #0 @SourceFile [ current? [ ASCII:LF @FID file:write ] if file:puts n:inc ] file:for-each-line drop @FID file:close @SourceFile TEMP-FILE 'mv_%s_%s s:with-format unix:system ; ~~~ Replacing a line is next. Much like the `delete-line`, this writes all but the current line to a dummy file. It uses a `gets` word to read in the text to write instead of the original current line. When done, it replaces the original file with the dummy one. ~~~ :gets (-s) s:empty [ buffer:set [ repeat getc dup ASCII:LF -eq? 0; drop buffer:add again ] call drop ] sip ; :replace-line (-) TEMP-FILE file:W file:open !FID #0 @SourceFile [ current? [ drop gets ] if file:puts n:inc ] file:for-each-line drop @FID file:close @SourceFile TEMP-FILE 'mv_%s_%s s:with-format unix:system ; ~~~ ~~~ :goto (-) gets s:to-number !CurrentLine ; ~~~ And now tie everything together. There's a key handler and a top level loop. ~~~ :| '_|_ puts ; :describe (cs-) swap putc $: putc puts ; :help $1 'insert_line describe | $2 'replace_text describe | $3 '____________ describe | $4 'erase_text_ describe | $5 'delete_line_ describe nl $j 'down_______ describe | $k 'up__________ describe | $g 'goto_line___ describe | #32 '___________ describe | $q 'quit________ describe nl ; :handler getc $1 [ add-line ] case $2 [ replace-line ] case $4 [ delete-line ] case $5 [ kill-line ] case $j [ &CurrentLine v:inc &CurrentLine #0 #10000 v:limit ] case $k [ &CurrentLine v:dec &CurrentLine #0 #10000 v:limit ] case $g [ goto &CurrentLine #0 #10000 v:limit ] case $q [ 'stty_-cbreak unix:system #0 unix:exit ] case drop ; :edit 'stty_cbreak unix:system repeat display help handler again ; ~~~ Run the editor. ~~~ edit ~~~