\ Command Line History \ \ Author: Phil Burk \ Copyright 1988 Phil Burk \ Revised 2001 for pForth 0 [IF] Requires an ANSI compatible terminal. To get Windows computers to use ANSI mode in their DOS windows, Add this line to "C:\CONFIG.SYS" then reboot. device=c:\windows\command\ansi.sys When command line history is on, you can use the UP and DOWN arrow to scroll through previous commands. Use the LEFT and RIGHT arrows to edit within a line. CONTROL-A moves to beginning of line. CONTROL-E moves to end of line. CONTROL-X erases entire line. HISTORY# ( -- , dump history buffer with numbers) HISTORY ( -- , dump history buffer ) XX ( line# -- , execute line x of history ) HISTORY.RESET ( -- , clear history tables ) HISTORY.ON ( -- , install history vectors ) HISTORY.OFF ( -- , uninstall history vectors ) [THEN] include? ESC[ termio.fth ANEW TASK-HISTORY.FTH decimal private{ \ You can expand the history buffer by increasing this constant!!!!!!!!!! 2048 constant KH_HISTORY_SIZE create KH-HISTORY kh_history_size allot KH-HISTORY kh_history_size erase \ An entry in the history buffer consists of \ byte - Count byte = N, \ chars - N chars, \ short - line number in Big Endian format, \ byte - another Count byte = N, for reverse scan \ \ The most recent entry is put at the beginning, \ older entries are shifted up. 4 constant KH_LINE_EXTRA_SIZE ( 2 count bytes plus 2 line_number bytes ) : KH-END ( -- addr , end of history buffer ) kh-history kh_history_size + ; : LINENUM@ ( addr -- w , stores in BigEndian format ) dup c@ 8 shift swap 1+ c@ or ; : LINENUM! ( w addr -- ) over -8 shift over c! 1+ c! ; variable KH-LOOK ( cursor offset into history, point to 1st count byte of line ) variable KH-MAX variable KH-COUNTER ( 16 bit counter for line # ) variable KH-SPAN ( total number of characters in line ) variable KH-MATCH-SPAN ( span for matching on shift-up ) variable KH-CURSOR ( points to next insertion point ) variable KH-ADDRESS ( address to store chars ) variable KH-INSIDE ( true if we are scrolling inside the history buffer ) : KH.MAKE.ROOM ( N -- , make room for N more bytes at beginning) >r ( save N ) kh-history dup r@ + ( source dest ) kh_history_size r> - 0 max move ; : KH.NEWEST.LINE ( -- addr count , most recent line ) kh-history count ; : KH.REWIND ( -- , move cursor to most recent line ) 0 kh-look ! ; : KH.CURRENT.ADDR ( -- $addr , count byte of current line ) kh-look @ kh-history + ; : KH.CURRENT.LINE ( -- addr count ) kh.current.addr count ; : KH.COMPARE ( addr count -- flag , true if redundant ) kh.newest.line compare 0= \ note: ANSI COMPARE is different than JForth days ; : KH.NUM.ADDR ( -- addr , address of current line's line count ) kh.current.line + ; : KH.CURRENT.NUM ( -- # , number of current line ) kh.num.addr LINENUM@ ; : KH.ADDR++ ( $addr -- $addr' , convert one kh to previous ) count + 3 + ; : KH.ADDR-- ( $addr -- $addr' , convert one kh to next ) dup 1- c@ \ get next lines endcount 4 + \ account for lineNum and two count bytes - \ calc previous address ; : KH.ENDCOUNT.ADDR ( -- addr , address of current end count ) kh.num.addr 2+ ; : KH.ADD.LINE ( addr count -- ) dup 256 > IF ." KH.ADD.LINE - Too big for history!" 2drop ELSE ( add to end ) \ Compare with most recent line. 2dup kh.compare IF 2drop ELSE >r ( save count ) \ Set look pointer to point to first count byte of last string. 0 kh-look ! \ Make room for this line of text and line header. \ PLB20100823 Was cell+ which broke on 64-bit code. r@ KH_LINE_EXTRA_SIZE + kh.make.room \ Set count bytes at beginning and end. r@ kh-history c! ( start count ) r@ kh.endcount.addr c! kh-counter @ kh.num.addr LINENUM! ( line ) \ Number lines modulo 1024 kh-counter @ 1+ $ 3FF and kh-counter ! kh-history 1+ ( calc destination ) r> cmove ( copy chars into space ) THEN THEN ; : KH.BACKUP.LINE { | cantmove addr' -- cantmove , advance KH-LOOK if in bounds } true -> cantmove ( default flag, at end of history ) \ KH-LOOK points to count at start of current line kh.current.addr c@ \ do we have any lines? IF kh.current.addr kh.addr++ -> addr' addr' kh-end U< \ within bounds? IF addr' c@ \ older line has chars? IF addr' kh-history - kh-look ! false -> cantmove THEN THEN THEN cantmove ; : KH.FORWARD.LINE ( -- cantmove? ) kh-look @ 0= dup not IF kh.current.addr kh.addr-- kh-history - kh-look ! THEN ; : KH.OLDEST.LINE ( -- addr count | 0, oldest in buffer ) BEGIN kh.backup.line UNTIL kh.current.line dup 0= IF nip THEN ; : KH.FIND.LINE ( line# -- $addr ) kh.rewind BEGIN kh.current.num over - WHILE kh.backup.line IF ." Line not in History Buffer!" cr drop 0 exit THEN REPEAT drop kh.current.addr ; : KH-BUFFER ( -- buffer ) kh-address @ ; : KH.RETURN ( -- , move to beginning of line ) 0 out ! 13 emit ; : KH.REPLACE.LINE ( addr count -- , make this the current line of input ) kh.return tio.erase.eol dup kh-span ! dup kh-cursor ! 2dup kh-buffer swap cmove type ; : KH.GET.MATCH ( -- , search for line with same start ) kh-match-span @ 0= ( keep length for multiple matches ) IF kh-span @ kh-match-span ! THEN BEGIN kh.backup.line not WHILE kh.current.line drop kh-buffer kh-match-span @ text= IF kh.current.line kh.replace.line exit THEN REPEAT ; : KH.FAR.RIGHT kh-span @ kh-cursor @ - dup 0> IF tio.forwards kh-span @ kh-cursor ! ELSE drop THEN ; : KH.FAR.LEFT ( -- ) kh.return kh-cursor off ; : KH.GET.OLDER ( -- , goto previous line ) kh-inside @ IF kh.backup.line drop THEN kh.current.line kh.replace.line kh-inside on ; : KH.GET.NEWER ( -- , next line ) kh.forward.line IF kh-inside off tib 0 ELSE kh.current.line THEN kh.replace.line ; : KH.CLEAR.LINE ( -- , rewind history scrolling and clear line ) kh.rewind tib 0 kh.replace.line kh-inside off ; : KH.GO.RIGHT ( -- ) kh-cursor @ kh-span @ < IF 1 kh-cursor +! 1 tio.forwards THEN ; : KH.GO.LEFT ( -- ) kh-cursor @ ?dup IF 1- kh-cursor ! 1 tio.backwards THEN ; : KH.REFRESH ( -- , redraw current line as is ) kh.return kh-buffer kh-span @ type tio.erase.eol kh.return kh-cursor @ ?dup IF tio.forwards THEN kh-span @ out ! ; : KH.BACKSPACE ( -- , backspace character from buffer and screen ) kh-cursor @ ?dup ( past 0? ) IF kh-span @ < IF ( inside line ) kh-buffer kh-cursor @ + ( -- source ) dup 1- ( -- source dest ) kh-span @ kh-cursor @ - cmove \ ." Deleted!" cr ELSE backspace THEN -1 kh-span +! -1 kh-cursor +! ELSE bell THEN kh.refresh ; : KH.DELETE ( -- , forward delete ) kh-cursor @ kh-span @ < ( before end ) IF ( inside line ) kh-buffer kh-cursor @ + 1+ ( -- source ) dup 1- ( -- source dest ) kh-span @ kh-cursor @ - 0 max cmove -1 kh-span +! kh.refresh THEN ; : KH.HANDLE.WINDOWS.KEY ( char -- , handle fkeys or arrows used by Windows ANSI.SYS ) CASE $ 8D OF kh.get.match ENDOF 0 kh-match-span ! ( reset if any other key ) $ 48 OF kh.get.older ENDOF $ 50 OF kh.get.newer ENDOF $ 4D OF kh.go.right ENDOF $ 4B OF kh.go.left ENDOF $ 91 OF kh.clear.line ENDOF $ 74 OF kh.far.right ENDOF $ 73 OF kh.far.left ENDOF $ 53 OF kh.delete ENDOF ENDCASE ; : KH.HANDLE.ANSI.KEY ( char -- , handle fkeys or arrows used by ANSI terminal ) CASE $ 41 OF kh.get.older ENDOF $ 42 OF kh.get.newer ENDOF $ 43 OF kh.go.right ENDOF $ 44 OF kh.go.left ENDOF ENDCASE ; : KH.SPECIAL.KEY ( char -- true | false , handle fkeys or arrows, true if handled ) true >r CASE $ E0 OF key kh.handle.windows.key ENDOF ASCII_ESCAPE OF key dup $ 4F = \ for TELNET $ 5B = OR \ for regular ANSI terminals IF key kh.handle.ansi.key ELSE rdrop false >r THEN ENDOF ASCII_BACKSPACE OF kh.backspace ENDOF ASCII_DELETE OF kh.backspace ENDOF ASCII_CTRL_X OF kh.clear.line ENDOF ASCII_CTRL_A OF kh.far.left ENDOF ASCII_CTRL_E OF kh.far.right ENDOF rdrop false >r ENDCASE r> ; : KH.SMART.KEY ( -- char ) BEGIN key dup kh.special.key WHILE drop REPEAT ; : KH.INSCHAR { charc | repaint -- } false -> repaint kh-cursor @ kh-span @ < IF \ Move characters up kh-buffer kh-cursor @ + ( -- source ) dup 1+ ( -- source dest ) kh-span @ kh-cursor @ - cmove> true -> repaint THEN \ write character to buffer charc kh-buffer kh-cursor @ + c! 1 kh-cursor +! 1 kh-span +! repaint IF kh.refresh ELSE charc emit THEN ; : EOL? ( char -- flag , true if an end of line character ) dup 13 = swap 10 = OR ; : KH.GETLINE ( max -- ) kh-max ! kh-span off kh-cursor off kh-inside off kh.rewind 0 kh-match-span ! BEGIN kh-max @ kh-span @ > IF kh.smart.key dup EOL? not ( ) ELSE 0 false THEN ( -- char flag ) WHILE ( -- char ) kh.inschar REPEAT drop kh-span @ kh-cursor @ - ?dup IF tio.forwards ( move to end of line ) THEN space flushemit ; : KH.ACCEPT ( addr max -- numChars ) swap kh-address ! kh.getline kh-span @ 0> IF kh-buffer kh-span @ kh.add.line THEN kh-span @ ; : TEST.HISTORY 4 0 DO pad 128 kh.accept cr pad swap type cr LOOP ; }private : HISTORY# ( -- , dump history buffer with numbers) cr kh.oldest.line ?dup IF BEGIN kh.current.num 3 .r ." ) " type ?pause cr kh.forward.line 0= WHILE kh.current.line REPEAT THEN ; : HISTORY ( -- , dump history buffer ) cr kh.oldest.line ?dup IF BEGIN type ?pause cr kh.forward.line 0= WHILE kh.current.line REPEAT THEN ; : XX ( line# -- , execute line x of history ) kh.find.line ?dup IF count evaluate THEN ; : HISTORY.RESET ( -- , clear history tables ) kh-history kh_history_size erase kh-counter off ; : HISTORY.ON ( -- , install history vectors ) history.reset what's accept ['] (accept) = IF ['] kh.accept is accept THEN ; : HISTORY.OFF ( -- , uninstall history vectors ) what's accept ['] kh.accept = IF ['] (accept) is accept THEN ; : AUTO.INIT auto.init history.on ; : AUTO.TERM history.off auto.term ; if.forgotten history.off 0 [IF] history.reset history.on [THEN]