1 \ Command Line History
\r
4 \ Copyright 1988 Phil Burk
\r
5 \ Revised 2001 for pForth
\r
9 Requires an ANSI compatible terminal.
\r
11 To get Windows computers to use ANSI mode in their DOS windows,
\r
12 Add this line to "C:\CONFIG.SYS" then reboot.
\r
14 device=c:\windows\command\ansi.sys
\r
16 When command line history is on, you can use the UP and DOWN arrow to scroll
\r
17 through previous commands. Use the LEFT and RIGHT arrows to edit within a line.
\r
18 CONTROL-A moves to beginning of line.
\r
19 CONTROL-E moves to end of line.
\r
20 CONTROL-X erases entire line.
\r
23 HISTORY# ( -- , dump history buffer with numbers)
\r
24 HISTORY ( -- , dump history buffer )
\r
25 XX ( line# -- , execute line x of history )
\r
26 HISTORY.RESET ( -- , clear history tables )
\r
27 HISTORY.ON ( -- , install history vectors )
\r
28 HISTORY.OFF ( -- , uninstall history vectors )
\r
32 include? ESC[ termio.fth
\r
34 ANEW TASK-HISTORY.FTH
\r
39 \ You can expand the history buffer by increasing this constant!!!!!!!!!!
\r
40 2048 constant KH_HISTORY_SIZE
\r
42 create KH-HISTORY kh_history_size allot
\r
43 KH-HISTORY kh_history_size erase
\r
45 \ An entry in the history buffer consists of
\r
46 \ byte - Count byte = N,
\r
48 \ short - line number in Big Endian format,
\r
49 \ byte - another Count byte = N, for reverse scan
\r
51 \ The most recent entry is put at the beginning,
\r
52 \ older entries are shifted up.
\r
54 4 constant KH_LINE_EXTRA_SIZE ( 2 count bytes plus 2 size bytes )
\r
56 : KH-END ( -- addr , end of history buffer )
\r
57 kh-history kh_history_size +
\r
60 : LINENUM@ ( addr -- w , stores in BigEndian format )
\r
65 : LINENUM! ( w addr -- )
\r
66 over -8 shift over c!
\r
70 variable KH-LOOK ( cursor offset into history, point to 1st count byte of line )
\r
72 variable KH-COUNTER ( 16 bit counter for line # )
\r
73 variable KH-SPAN ( total number of characters in line )
\r
74 variable KH-MATCH-SPAN ( span for matching on shift-up )
\r
75 variable KH-CURSOR ( points to next insertion point )
\r
76 variable KH-ADDRESS ( address to store chars )
\r
77 variable KH-INSIDE ( true if we are scrolling inside the history buffer )
\r
79 : KH.MAKE.ROOM ( N -- , make room for N more bytes at beginning)
\r
81 kh-history dup r@ + ( source dest )
\r
82 kh_history_size r> - 0 max move
\r
85 : KH.NEWEST.LINE ( -- addr count , most recent line )
\r
89 : KH.REWIND ( -- , move cursor to most recent line )
\r
93 : KH.CURRENT.ADDR ( -- $addr , count byte of current line )
\r
94 kh-look @ kh-history +
\r
97 : KH.CURRENT.LINE ( -- addr count )
\r
98 kh.current.addr count
\r
101 : KH.COMPARE ( addr count -- flag , true if redundant )
\r
102 kh.newest.line compare 0= \ note: ANSI COMPARE is different than JForth days
\r
105 : KH.NUM.ADDR ( -- addr , address of current line's line count )
\r
109 : KH.CURRENT.NUM ( -- # , number of current line )
\r
110 kh.num.addr LINENUM@
\r
113 : KH.ADDR++ ( $addr -- $addr' , convert one kh to previous )
\r
116 : KH.ADDR-- ( $addr -- $addr' , convert one kh to next )
\r
117 dup 1- c@ \ get next lines endcount
\r
118 4 + \ account for lineNum and two count bytes
\r
119 - \ calc previous address
\r
122 : KH.ENDCOUNT.ADDR ( -- addr , address of current end count )
\r
126 : KH.ADD.LINE ( addr count -- )
\r
128 IF ." KH.ADD.LINE - Too big for history!" 2drop
\r
129 ELSE ( add to end )
\r
130 \ Compare with most recent line.
\r
135 \ Set look pointer to point to first count byte of last string.
\r
137 \ Make room for this line of text and line header.
\r
138 \ PLB20100823 Was cell+ which broke on 64-bit code.
\r
139 r@ KH_LINE_EXTRA_SIZE + kh.make.room
\r
140 \ Set count bytes at beginning and end.
\r
141 r@ kh-history c! ( start count )
\r
142 r@ kh.endcount.addr c!
\r
143 kh-counter @ kh.num.addr LINENUM! ( line )
\r
144 \ Number lines modulo 1024
\r
145 kh-counter @ 1+ $ 3FF and kh-counter !
\r
146 kh-history 1+ ( calc destination )
\r
147 r> cmove ( copy chars into space )
\r
152 : KH.BACKUP.LINE { | cantmove addr' -- cantmove , advance KH-LOOK if in bounds }
\r
153 true -> cantmove ( default flag, at end of history )
\r
154 \ KH-LOOK points to count at start of current line
\r
155 kh.current.addr c@ \ do we have any lines?
\r
157 kh.current.addr kh.addr++ -> addr'
\r
158 addr' kh-end U< \ within bounds?
\r
160 addr' c@ \ older line has chars?
\r
162 addr' kh-history - kh-look !
\r
170 : KH.FORWARD.LINE ( -- cantmove? )
\r
171 kh-look @ 0= dup not
\r
172 IF kh.current.addr kh.addr--
\r
173 kh-history - kh-look !
\r
177 : KH.OLDEST.LINE ( -- addr count | 0, oldest in buffer )
\r
178 BEGIN kh.backup.line
\r
180 kh.current.line dup 0=
\r
186 : KH.FIND.LINE ( line# -- $addr )
\r
188 BEGIN kh.current.num over -
\r
189 WHILE kh.backup.line
\r
190 IF ." Line not in History Buffer!" cr drop 0 exit
\r
193 drop kh.current.addr
\r
197 : KH-BUFFER ( -- buffer )
\r
201 : KH.RETURN ( -- , move to beginning of line )
\r
206 : KH.REPLACE.LINE ( addr count -- , make this the current line of input )
\r
211 2dup kh-buffer swap cmove
\r
215 : KH.GET.MATCH ( -- , search for line with same start )
\r
216 kh-match-span @ 0= ( keep length for multiple matches )
\r
217 IF kh-span @ kh-match-span !
\r
222 kh.current.line drop
\r
223 kh-buffer kh-match-span @ text=
\r
224 IF kh.current.line kh.replace.line
\r
231 kh-span @ kh-cursor @ - dup 0>
\r
234 kh-span @ kh-cursor !
\r
239 : KH.FAR.LEFT ( -- )
\r
244 : KH.GET.OLDER ( -- , goto previous line )
\r
246 IF kh.backup.line drop
\r
248 kh.current.line kh.replace.line
\r
252 : KH.GET.NEWER ( -- , next line )
\r
257 ELSE kh.current.line
\r
262 : KH.CLEAR.LINE ( -- , rewind history scrolling and clear line )
\r
264 tib 0 kh.replace.line
\r
268 : KH.GO.RIGHT ( -- )
\r
269 kh-cursor @ kh-span @ <
\r
275 : KH.GO.LEFT ( -- )
\r
282 : KH.REFRESH ( -- , redraw current line as is )
\r
284 kh-buffer kh-span @ type
\r
295 : KH.BACKSPACE ( -- , backspace character from buffer and screen )
\r
296 kh-cursor @ ?dup ( past 0? )
\r
299 kh-buffer kh-cursor @ + ( -- source )
\r
300 dup 1- ( -- source dest )
\r
301 kh-span @ kh-cursor @ - cmove
\r
313 : KH.DELETE ( -- , forward delete )
\r
314 kh-cursor @ kh-span @ < ( before end )
\r
316 kh-buffer kh-cursor @ + 1+ ( -- source )
\r
317 dup 1- ( -- source dest )
\r
318 kh-span @ kh-cursor @ - 0 max cmove
\r
324 : KH.HANDLE.WINDOWS.KEY ( char -- , handle fkeys or arrows used by Windows ANSI.SYS )
\r
326 $ 8D OF kh.get.match ENDOF
\r
327 0 kh-match-span ! ( reset if any other key )
\r
328 $ 48 OF kh.get.older ENDOF
\r
329 $ 50 OF kh.get.newer ENDOF
\r
330 $ 4D OF kh.go.right ENDOF
\r
331 $ 4B OF kh.go.left ENDOF
\r
332 $ 91 OF kh.clear.line ENDOF
\r
333 $ 74 OF kh.far.right ENDOF
\r
334 $ 73 OF kh.far.left ENDOF
\r
335 $ 53 OF kh.delete ENDOF
\r
339 : KH.HANDLE.ANSI.KEY ( char -- , handle fkeys or arrows used by ANSI terminal )
\r
341 $ 41 OF kh.get.older ENDOF
\r
342 $ 42 OF kh.get.newer ENDOF
\r
343 $ 43 OF kh.go.right ENDOF
\r
344 $ 44 OF kh.go.left ENDOF
\r
349 : KH.SPECIAL.KEY ( char -- true | false , handle fkeys or arrows, true if handled )
\r
353 $ E0 OF key kh.handle.windows.key
\r
357 key dup $ 4F = \ for TELNET
\r
358 $ 5B = OR \ for regular ANSI terminals
\r
360 key kh.handle.ansi.key
\r
366 ASCII_BACKSPACE OF kh.backspace ENDOF
\r
367 ASCII_DELETE OF kh.backspace ENDOF
\r
368 ASCII_CTRL_X OF kh.clear.line ENDOF
\r
369 ASCII_CTRL_A OF kh.far.left ENDOF
\r
370 ASCII_CTRL_E OF kh.far.right ENDOF
\r
378 : KH.SMART.KEY ( -- char )
\r
380 key dup kh.special.key
\r
386 : KH.INSCHAR { charc | repaint -- }
\r
388 kh-cursor @ kh-span @ <
\r
390 \ Move characters up
\r
391 kh-buffer kh-cursor @ + ( -- source )
\r
392 dup 1+ ( -- source dest )
\r
393 kh-span @ kh-cursor @ - cmove>
\r
396 \ write character to buffer
\r
397 charc kh-buffer kh-cursor @ + c!
\r
406 : EOL? ( char -- flag , true if an end of line character )
\r
411 : KH.GETLINE ( max -- )
\r
419 kh-max @ kh-span @ >
\r
421 dup EOL? not ( <cr?> )
\r
423 THEN ( -- char flag )
\r
427 kh-span @ kh-cursor @ - ?dup
\r
428 IF tio.forwards ( move to end of line )
\r
434 : KH.ACCEPT ( addr max -- numChars )
\r
438 IF kh-buffer kh-span @ kh.add.line
\r
446 cr pad swap type cr
\r
453 : HISTORY# ( -- , dump history buffer with numbers)
\r
454 cr kh.oldest.line ?dup
\r
456 BEGIN kh.current.num 3 .r ." ) " type ?pause cr
\r
458 WHILE kh.current.line
\r
463 : HISTORY ( -- , dump history buffer )
\r
464 cr kh.oldest.line ?dup
\r
466 BEGIN type ?pause cr
\r
468 WHILE kh.current.line
\r
473 : XX ( line# -- , execute line x of history )
\r
480 : HISTORY.RESET ( -- , clear history tables )
\r
481 kh-history kh_history_size erase
\r
485 : HISTORY.ON ( -- , install history vectors )
\r
487 what's accept ['] (accept) =
\r
488 IF ['] kh.accept is accept
\r
492 : HISTORY.OFF ( -- , uninstall history vectors )
\r
493 what's accept ['] kh.accept =
\r
494 IF ['] (accept) is accept
\r
508 if.forgotten history.off
\r