history: document ANSI escape sequences used
[pforth] / fth / history.fth
CommitLineData
8e9db35f
PB
1\ Command Line History
2\
3\ Author: Phil Burk
4\ Copyright 1988 Phil Burk
5\ Revised 2001 for pForth
6
70 [IF]
8
9Requires an ANSI compatible terminal.
10
11To get Windows computers to use ANSI mode in their DOS windows,
12Add this line to "C:\CONFIG.SYS" then reboot.
13
14 device=c:\windows\command\ansi.sys
15
16When command line history is on, you can use the UP and DOWN arrow to scroll
17through previous commands. Use the LEFT and RIGHT arrows to edit within a line.
18 CONTROL-A moves to beginning of line.
19 CONTROL-E moves to end of line.
20 CONTROL-X erases entire line.
21
22
23HISTORY# ( -- , dump history buffer with numbers)
24HISTORY ( -- , dump history buffer )
25XX ( line# -- , execute line x of history )
26HISTORY.RESET ( -- , clear history tables )
27HISTORY.ON ( -- , install history vectors )
28HISTORY.OFF ( -- , uninstall history vectors )
29
30[THEN]
31
32include? ESC[ termio.fth
33
34ANEW TASK-HISTORY.FTH
35decimal
36
37private{
38
39\ You can expand the history buffer by increasing this constant!!!!!!!!!!
402048 constant KH_HISTORY_SIZE
41
42create KH-HISTORY kh_history_size allot
43KH-HISTORY kh_history_size erase
44
45\ An entry in the history buffer consists of
46\ byte - Count byte = N,
47\ chars - N chars,
48\ short - line number in Big Endian format,
49\ byte - another Count byte = N, for reverse scan
50\
51\ The most recent entry is put at the beginning,
52\ older entries are shifted up.
53
db35c5f4 544 constant KH_LINE_EXTRA_SIZE ( 2 count bytes plus 2 line_number bytes )
8e9db35f
PB
55
56: KH-END ( -- addr , end of history buffer )
57 kh-history kh_history_size +
58;
59
60: LINENUM@ ( addr -- w , stores in BigEndian format )
61 dup c@ 8 shift
62 swap 1+ c@ or
63;
64
65: LINENUM! ( w addr -- )
66 over -8 shift over c!
67 1+ c!
68;
69
70variable KH-LOOK ( cursor offset into history, point to 1st count byte of line )
71variable KH-MAX
72variable KH-COUNTER ( 16 bit counter for line # )
73variable KH-SPAN ( total number of characters in line )
74variable KH-MATCH-SPAN ( span for matching on shift-up )
75variable KH-CURSOR ( points to next insertion point )
76variable KH-ADDRESS ( address to store chars )
77variable KH-INSIDE ( true if we are scrolling inside the history buffer )
78
79: KH.MAKE.ROOM ( N -- , make room for N more bytes at beginning)
80 >r ( save N )
81 kh-history dup r@ + ( source dest )
82 kh_history_size r> - 0 max move
83;
84
85: KH.NEWEST.LINE ( -- addr count , most recent line )
86 kh-history count
87;
88
89: KH.REWIND ( -- , move cursor to most recent line )
90 0 kh-look !
91;
92
93: KH.CURRENT.ADDR ( -- $addr , count byte of current line )
94 kh-look @ kh-history +
95;
96
97: KH.CURRENT.LINE ( -- addr count )
98 kh.current.addr count
99;
100
101: KH.COMPARE ( addr count -- flag , true if redundant )
102 kh.newest.line compare 0= \ note: ANSI COMPARE is different than JForth days
103;
104
105: KH.NUM.ADDR ( -- addr , address of current line's line count )
106 kh.current.line +
107;
108
109: KH.CURRENT.NUM ( -- # , number of current line )
110 kh.num.addr LINENUM@
111;
112
113: KH.ADDR++ ( $addr -- $addr' , convert one kh to previous )
114 count + 3 +
115;
116: KH.ADDR-- ( $addr -- $addr' , convert one kh to next )
117 dup 1- c@ \ get next lines endcount
118 4 + \ account for lineNum and two count bytes
119 - \ calc previous address
120;
121
122: KH.ENDCOUNT.ADDR ( -- addr , address of current end count )
123 kh.num.addr 2+
124;
125
126: KH.ADD.LINE ( addr count -- )
127 dup 256 >
128 IF ." KH.ADD.LINE - Too big for history!" 2drop
129 ELSE ( add to end )
130\ Compare with most recent line.
131 2dup kh.compare
132 IF 2drop
133 ELSE
134 >r ( save count )
135\ Set look pointer to point to first count byte of last string.
136 0 kh-look !
137\ Make room for this line of text and line header.
138\ PLB20100823 Was cell+ which broke on 64-bit code.
139 r@ KH_LINE_EXTRA_SIZE + kh.make.room
140\ Set count bytes at beginning and end.
141 r@ kh-history c! ( start count )
142 r@ kh.endcount.addr c!
143 kh-counter @ kh.num.addr LINENUM! ( line )
144\ Number lines modulo 1024
145 kh-counter @ 1+ $ 3FF and kh-counter !
146 kh-history 1+ ( calc destination )
147 r> cmove ( copy chars into space )
148 THEN
149 THEN
150;
151
152: KH.BACKUP.LINE { | cantmove addr' -- cantmove , advance KH-LOOK if in bounds }
153 true -> cantmove ( default flag, at end of history )
154\ KH-LOOK points to count at start of current line
155 kh.current.addr c@ \ do we have any lines?
156 IF
157 kh.current.addr kh.addr++ -> addr'
158 addr' kh-end U< \ within bounds?
159 IF
160 addr' c@ \ older line has chars?
161 IF
162 addr' kh-history - kh-look !
163 false -> cantmove
164 THEN
165 THEN
166 THEN
167 cantmove
168;
169
170: KH.FORWARD.LINE ( -- cantmove? )
171 kh-look @ 0= dup not
172 IF kh.current.addr kh.addr--
173 kh-history - kh-look !
174 THEN
175;
176
177: KH.OLDEST.LINE ( -- addr count | 0, oldest in buffer )
178 BEGIN kh.backup.line
179 UNTIL
180 kh.current.line dup 0=
181 IF
182 nip
183 THEN
184;
185
186: KH.FIND.LINE ( line# -- $addr )
187 kh.rewind
188 BEGIN kh.current.num over -
189 WHILE kh.backup.line
190 IF ." Line not in History Buffer!" cr drop 0 exit
191 THEN
192 REPEAT
193 drop kh.current.addr
194;
195
196
197: KH-BUFFER ( -- buffer )
198 kh-address @
199;
200
201: KH.RETURN ( -- , move to beginning of line )
202 0 out !
203 13 emit
204;
205
206: KH.REPLACE.LINE ( addr count -- , make this the current line of input )
207 kh.return
208 tio.erase.eol
209 dup kh-span !
210 dup kh-cursor !
211 2dup kh-buffer swap cmove
212 type
213;
214
215: KH.GET.MATCH ( -- , search for line with same start )
216 kh-match-span @ 0= ( keep length for multiple matches )
217 IF kh-span @ kh-match-span !
218 THEN
219 BEGIN
220 kh.backup.line not
221 WHILE
222 kh.current.line drop
223 kh-buffer kh-match-span @ text=
224 IF kh.current.line kh.replace.line
225 exit
226 THEN
227 REPEAT
228;
229
230: KH.FAR.RIGHT
231 kh-span @ kh-cursor @ - dup 0>
232 IF
233 tio.forwards
234 kh-span @ kh-cursor !
235 ELSE drop
236 THEN
237;
238
239: KH.FAR.LEFT ( -- )
240 kh.return
241 kh-cursor off
242;
243
244: KH.GET.OLDER ( -- , goto previous line )
245 kh-inside @
246 IF kh.backup.line drop
247 THEN
248 kh.current.line kh.replace.line
249 kh-inside on
250;
251
252: KH.GET.NEWER ( -- , next line )
253 kh.forward.line
254 IF
255 kh-inside off
256 tib 0
257 ELSE kh.current.line
258 THEN
259 kh.replace.line
260;
261
262: KH.CLEAR.LINE ( -- , rewind history scrolling and clear line )
263 kh.rewind
264 tib 0 kh.replace.line
265 kh-inside off
266;
267
268: KH.GO.RIGHT ( -- )
269 kh-cursor @ kh-span @ <
270 IF 1 kh-cursor +!
271 1 tio.forwards
272 THEN
273;
274
275: KH.GO.LEFT ( -- )
276 kh-cursor @ ?dup
277 IF 1- kh-cursor !
278 1 tio.backwards
279 THEN
280;
281
282: KH.REFRESH ( -- , redraw current line as is )
283 kh.return
284 kh-buffer kh-span @ type
285 tio.erase.eol
286
287 kh.return
288 kh-cursor @ ?dup
289 IF tio.forwards
290 THEN
291
292 kh-span @ out !
293;
294
295: KH.BACKSPACE ( -- , backspace character from buffer and screen )
296 kh-cursor @ ?dup ( past 0? )
297 IF kh-span @ <
298 IF ( inside line )
299 kh-buffer kh-cursor @ + ( -- source )
300 dup 1- ( -- source dest )
301 kh-span @ kh-cursor @ - cmove
302\ ." Deleted!" cr
303 ELSE
304 backspace
305 THEN
306 -1 kh-span +!
307 -1 kh-cursor +!
308 ELSE bell
309 THEN
310 kh.refresh
311;
312
313: KH.DELETE ( -- , forward delete )
314 kh-cursor @ kh-span @ < ( before end )
315 IF ( inside line )
316 kh-buffer kh-cursor @ + 1+ ( -- source )
317 dup 1- ( -- source dest )
318 kh-span @ kh-cursor @ - 0 max cmove
319 -1 kh-span +!
320 kh.refresh
321 THEN
322;
323
324: KH.HANDLE.WINDOWS.KEY ( char -- , handle fkeys or arrows used by Windows ANSI.SYS )
325 CASE
326 $ 8D OF kh.get.match ENDOF
327 0 kh-match-span ! ( reset if any other key )
328 $ 48 OF kh.get.older ENDOF
329 $ 50 OF kh.get.newer ENDOF
330 $ 4D OF kh.go.right ENDOF
331 $ 4B OF kh.go.left ENDOF
332 $ 91 OF kh.clear.line ENDOF
333 $ 74 OF kh.far.right ENDOF
334 $ 73 OF kh.far.left ENDOF
335 $ 53 OF kh.delete ENDOF
336 ENDCASE
337;
338
339: KH.HANDLE.ANSI.KEY ( char -- , handle fkeys or arrows used by ANSI terminal )
340 CASE
341 $ 41 OF kh.get.older ENDOF
342 $ 42 OF kh.get.newer ENDOF
343 $ 43 OF kh.go.right ENDOF
344 $ 44 OF kh.go.left ENDOF
345 ENDCASE
346;
347
8e9db35f
PB
348: KH.SPECIAL.KEY ( char -- true | false , handle fkeys or arrows, true if handled )
349 true >r
350 CASE
351
352 $ E0 OF key kh.handle.windows.key
353 ENDOF
354
355 ASCII_ESCAPE OF
356 key dup $ 4F = \ for TELNET
357 $ 5B = OR \ for regular ANSI terminals
358 IF
359 key kh.handle.ansi.key
360 ELSE
361 rdrop false >r
362 THEN
363 ENDOF
364
365 ASCII_BACKSPACE OF kh.backspace ENDOF
366 ASCII_DELETE OF kh.backspace ENDOF
367 ASCII_CTRL_X OF kh.clear.line ENDOF
368 ASCII_CTRL_A OF kh.far.left ENDOF
369 ASCII_CTRL_E OF kh.far.right ENDOF
370
371 rdrop false >r
372
373 ENDCASE
374 r>
375;
376
377: KH.SMART.KEY ( -- char )
378 BEGIN
379 key dup kh.special.key
380 WHILE
381 drop
382 REPEAT
383;
384
385: KH.INSCHAR { charc | repaint -- }
386 false -> repaint
387 kh-cursor @ kh-span @ <
388 IF
389\ Move characters up
390 kh-buffer kh-cursor @ + ( -- source )
391 dup 1+ ( -- source dest )
392 kh-span @ kh-cursor @ - cmove>
393 true -> repaint
394 THEN
395\ write character to buffer
396 charc kh-buffer kh-cursor @ + c!
397 1 kh-cursor +!
398 1 kh-span +!
399 repaint
400 IF kh.refresh
401 ELSE charc emit
402 THEN
403;
404
405: EOL? ( char -- flag , true if an end of line character )
406 dup 13 =
407 swap 10 = OR
408;
409
410: KH.GETLINE ( max -- )
411 kh-max !
412 kh-span off
413 kh-cursor off
414 kh-inside off
415 kh.rewind
416 0 kh-match-span !
417 BEGIN
418 kh-max @ kh-span @ >
419 IF kh.smart.key
420 dup EOL? not ( <cr?> )
421 ELSE 0 false
422 THEN ( -- char flag )
423 WHILE ( -- char )
424 kh.inschar
425 REPEAT drop
426 kh-span @ kh-cursor @ - ?dup
427 IF tio.forwards ( move to end of line )
428 THEN
429 space
430 flushemit
431;
432
433: KH.ACCEPT ( addr max -- numChars )
434 swap kh-address !
435 kh.getline
436 kh-span @ 0>
437 IF kh-buffer kh-span @ kh.add.line
438 THEN
439 kh-span @
440;
441
442: TEST.HISTORY
443 4 0 DO
444 pad 128 kh.accept
445 cr pad swap type cr
446 LOOP
447;
448
449}private
450
451
452: HISTORY# ( -- , dump history buffer with numbers)
453 cr kh.oldest.line ?dup
454 IF
455 BEGIN kh.current.num 3 .r ." ) " type ?pause cr
456 kh.forward.line 0=
457 WHILE kh.current.line
458 REPEAT
459 THEN
460;
461
462: HISTORY ( -- , dump history buffer )
463 cr kh.oldest.line ?dup
464 IF
465 BEGIN type ?pause cr
466 kh.forward.line 0=
467 WHILE kh.current.line
468 REPEAT
469 THEN
470;
471
472: XX ( line# -- , execute line x of history )
473 kh.find.line ?dup
474 IF count evaluate
475 THEN
476;
477
478
479: HISTORY.RESET ( -- , clear history tables )
480 kh-history kh_history_size erase
481 kh-counter off
482;
483
484: HISTORY.ON ( -- , install history vectors )
485 history.reset
486 what's accept ['] (accept) =
487 IF ['] kh.accept is accept
488 THEN
489;
490
491: HISTORY.OFF ( -- , uninstall history vectors )
492 what's accept ['] kh.accept =
493 IF ['] (accept) is accept
494 THEN
495;
496
497
498: AUTO.INIT
499 auto.init
500 history.on
501;
502: AUTO.TERM
503 history.off
504 auto.term
505;
506
507if.forgotten history.off
508
5090 [IF]
510history.reset
511history.on
512[THEN]