-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreadline.tcl
981 lines (864 loc) · 28.2 KB
/
readline.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
#
# tclline: An attempt at a pure tcl readline.
#
# This base code taken from http://wiki.tcl.tk/20215 and
# http://wiki.tcl.tk/16139
#
# Author: HCG
# Licence: "as freely available as possible" http://wiki.tcl.tk/4381
#
# Modified by rjmcmahon: fixes history and multiple key sequences per input char (may not assume atomic)
# Also added ability to extend the completion handlers
#
# Note: The wiki.tcl.tk is broken with respect to creating a new page and reverting back to an old page
# Some comments from the previous wiki page no longer shown as the file upload seem to overwrite the entire wiki page.
#
# [LV] Actually, it isn't broken - but there might be a misunderstanding in terms of the functionality of file upload.
# The comments on each page are literally part of the page itself. The file upload replaces the complete contents of the page with new contents
# Thus, if the comments would be useful to keep, you should download the current wiki text page, replace the portion of the page containing
# code or whatever with the new contents, then upload the entire page. By just uploading tclline, you are effectively saying "the only thing
# I want on this page is the contents of this file".
#
# example usage in .tclshrc:
#
# package require TclReadLine
# ;# set ::TclReadLine::PROMPT {tclsh[info patchlevel] \[[pwd]\]% } ;# no colour alternative
# set ::TclReadLine::PROMPT {\033\[36mtclsh-[info patchlevel]\033\[0m \[\033\[34m[file tail [pwd]\033\[0m]\]\033\[31m % \033\[0m}
# tailcall ::TclReadLine::interact
package provide TclReadLine 1.1
if {[catch {
# Use Tclx if available:
package require Tclx
interp alias {} stty exec stty
# Prevent sigint from killing our shell:
signal ignore SIGINT
}]} {
# else, fall back on Expect:
package require Expect
interp alias {} stty {} exp_stty
# Prevent sigint from killing our shell:
exp_trap SIG_IGN SIGINT
# Handle terminal resize events:
exp_trap ::TclReadLine::getColumns SIGWINCH
}
namespace eval TclReadLine {
namespace export interact
# Initialise our own env variables:
variable PROMPT ">"
variable PROMPT {\033\[36mtclsh-[info patchlevel]\033\[0m \[\033\[34m[file tail [pwd]\033\[0m]\]\033\[31m % \033\[0m}
variable COMPLETION_MATCH ""
# Support extensions to the completion handling
# which will be called in list order.
# Initialize with the "open sourced" TCL base handler
# taken from the wiki page
variable COMPLETION_HANDLERS [list TclReadLine::handleCompletionBase]
#
# This value was determined by measuring
# a cygwin over ssh.
#
variable READLINE_LATENCY 10 ;# in ms
variable CMDLINE ""
variable CMDLINE_CURSOR 0
variable CMDLINE_LINES 0
variable CMDLINE_PARTIAL
variable ALIASES
array set ALIASES {}
variable forever 0
# Resource and history files:
variable HISTORY_SIZE 100
variable HISTORY_LEVEL 0
variable HISTFILE $::env(HOME)/.tclline_history
variable RCFILE $::env(HOME)/.tcllinerc
}
proc TclReadLine::ESC {} {
return "\033"
}
proc TclReadLine::shift {ls} {
upvar 1 $ls LIST
set ret [lindex $LIST 0]
set LIST [lrange $LIST 1 end]
return $ret
}
proc TclReadLine::readbuf {txt} {
upvar 1 $txt STRING
set ret [string index $STRING 0]
set STRING [string range $STRING 1 end]
return $ret
}
proc TclReadLine::goto {row {col 1}} {
switch -- $row {
"home" {set row 1}
}
print "[ESC]\[${row};${col}H" nowait
}
proc TclReadLine::gotocol {col} {
print "\r" nowait
if {$col > 0} {
print "[ESC]\[${col}C" nowait
}
}
proc TclReadLine::clear {} {
print "[ESC]\[2J" nowait
goto home
}
proc TclReadLine::clearline {} {
print "[ESC]\[2K\r" nowait
}
proc TclReadLine::getColumns {} {
variable COLUMNS
set cols 80
if {![catch {stty size} size]} {
lassign $size rows cols
}
if { ![string is integer $cols] || ![string is integer $rows] } {
set cols 80
if {![catch {stty -a} err]} {
# check for Linux stlye stty output
if {[regexp {rows (= )?(\d+); columns (= )?(\d+)} $err - i1 rows i2 cols]} {
return [set COLUMNS $cols]
}
# check for BSD style stty output
if {[regexp { (\d+) rows; (\d+) columns;} $err - rows cols]} {
return [set COLUMNS $cols]
}
}
}
set COLUMNS $cols
}
proc TclReadLine::localInfo {args} {
set v [uplevel _info $args]
if { [string equal "script" [lindex $args 0]] } {
if { [string equal $v $TclReadLine::ThisScript] } {
return ""
}
}
return $v
}
proc TclReadLine::localPuts {args} {
set l [llength $args]
if { 3 < $l } {
return -code error "Error: wrong \# args"
}
if { 1 < $l } {
if { [string equal "-nonewline" [lindex $args 0]] } {
if { 2 < $l } {
# we don't send to channel...
eval _origPuts $args
} else {
set str [lindex $args 1]
append TclReadLine::putsString $str ;# no newline...
}
} else {
# must be a channel
eval _origPuts $args
}
} else {
append TclReadLine::putsString [lindex $args 0] "\n"
}
}
proc TclReadLine::prompt {{txt ""}} {
if { "" != [info var ::tcl_prompt1] } {
rename ::puts ::_origPuts
rename TclReadLine::localPuts ::puts
variable putsString
set putsString ""
eval [set ::tcl_prompt1]
set prompt $putsString
rename ::puts TclReadLine::localPuts
rename ::_origPuts ::puts
} else {
variable PROMPT
set prompt [subst $PROMPT]
}
set txt "$prompt$txt"
variable CMDLINE_LINES
variable CMDLINE_CURSOR
variable COLUMNS
foreach {end mid} $CMDLINE_LINES break
# Calculate how many extra lines we need to display.
# Also calculate cursor position:
set n -1
set totalLen 0
set visprompt [regsub -all {\x1b\[[0-9;]*[a-zA-Z]} $prompt {}] ;# strip colour codes
set cursorLen [expr {$CMDLINE_CURSOR+[string length $visprompt]}]
set row 0
set col 0
# Render output line-by-line to $out then copy back to $txt:
set found 0
set out [list]
foreach line [split $txt "\n"] {
set len [expr {[string length $line]+1}]
incr totalLen $len
if {$found == 0 && $totalLen >= $cursorLen} {
set cursorLen [expr {$cursorLen - ($totalLen - $len)}]
set col [expr {$cursorLen % $COLUMNS}]
set row [expr {$n + ($cursorLen / $COLUMNS) + 1}]
if {$cursorLen >= $len} {
set col 0
incr row
}
set found 1
}
incr n [expr {int(ceil(double($len)/$COLUMNS))}]
while {$len > 0} {
lappend out [string range $line 0 [expr {$COLUMNS-1}]]
set line [string range $line $COLUMNS end]
set len [expr {$len-$COLUMNS}]
}
}
set txt [join $out "\n"]
set row [expr {$n-$row}]
# Reserve spaces for display:
if {$end} {
if {$mid} {
print "[ESC]\[${mid}B" nowait
}
for {set x 0} {$x < $end} {incr x} {
clearline
print "[ESC]\[1A" nowait
}
}
clearline
set CMDLINE_LINES $n
# Output line(s):
print "\r$txt"
if {$row} {
print "[ESC]\[${row}A" nowait
}
gotocol $col
lappend CMDLINE_LINES $row
}
proc TclReadLine::print {txt {wait wait}} {
# Sends output to stdout chunks at a time.
# This is to prevent the terminal from
# hanging if we output too much:
while {[string length $txt]} {
puts -nonewline [string range $txt 0 2047]
set txt [string range $txt 2048 end]
if {$wait == "wait"} {
after 1
}
}
}
proc TclReadLine::unknown {args} {
set name [lindex $args 0]
set cmdline $TclReadLine::CMDLINE
set cmd [string trim [regexp -inline {^\s*[^\s]+} $cmdline]]
if {[info exists TclReadLine::ALIASES($cmd)]} {
set cmd [regexp -inline {^\s*[^\s]+} $TclReadLine::ALIASES($cmd)]
}
set new [auto_execok $name]
if {$new != ""} {
set redir ""
if {$name == $cmd && [info command $cmd] == ""} {
set redir ">&@ stdout <@ stdin"
}
if {[catch {
uplevel 1 exec $redir $new [lrange $args 1 end]} ret]
} {
return
}
return $ret
}
uplevel _unknown $args
}
proc TclReadLine::alias {word command} {
variable ALIASES
set ALIASES($word) $command
}
proc TclReadLine::unalias {word} {
variable ALIASES
array unset ALIASES $word
}
################################
# Key bindings
################################
proc TclReadLine::handleEscapes {} {
variable CMDLINE
variable CMDLINE_CURSOR
upvar 1 keybuffer keybuffer
set seq ""
set found 0
while {[set ch [readbuf keybuffer]] != ""} {
append seq $ch
switch -exact -- $seq {
"\[A" { ;# Cursor Up (cuu1,up)
handleHistory 1
set found 1; break
}
"\[B" { ;# Cursor Down
handleHistory -1
set found 1; break
}
"\[C" { ;# Cursor Right (cuf1,nd)
if {$CMDLINE_CURSOR < [string length $CMDLINE]} {
incr CMDLINE_CURSOR
}
set found 1; break
}
"\[D" { ;# Cursor Left
if {$CMDLINE_CURSOR > 0} {
incr CMDLINE_CURSOR -1
}
set found 1; break
}
"\[H" -
"\[7~" -
"\[1~" { ;# home
set CMDLINE_CURSOR 0
set found 1; break
}
"\[3~" { ;# delete
if {$CMDLINE_CURSOR < [string length $CMDLINE]} {
set CMDLINE [string replace $CMDLINE \
$CMDLINE_CURSOR $CMDLINE_CURSOR]
}
set found 1; break
}
"\[F" -
"\[K" -
"\[8~" -
"\[4~" { ;# end
set CMDLINE_CURSOR [string length $CMDLINE]
set found 1; break
}
"\[5~" { ;# Page Up
}
"\[6~" { ;# Page Down
}
}
}
return $found
}
proc TclReadLine::handleControls {} {
variable CMDLINE
variable CMDLINE_CURSOR
upvar 1 char char
upvar 1 keybuffer keybuffer
# Control chars start at a == \u0001 and count up.
switch -exact -- $char {
\u0001 { ;# ^a
set CMDLINE_CURSOR 0
}
\u0002 { ;# ^b
if { $CMDLINE_CURSOR > 0 } {
incr CMDLINE_CURSOR -1
}
}
\u0004 { ;# ^d
# should exit - if this is the EOF char, and the
# cursor is at the end-of-input
if { 0 == [string length $CMDLINE] } {
doExit
}
set CMDLINE [string replace $CMDLINE \
$CMDLINE_CURSOR $CMDLINE_CURSOR]
}
\u0005 { ;# ^e
set CMDLINE_CURSOR [string length $CMDLINE]
}
\u0006 { ;# ^f
if {$CMDLINE_CURSOR < [string length $CMDLINE]} {
incr CMDLINE_CURSOR
}
}
\u0007 { ;# ^g
set CMDLINE ""
set CMDLINE_CURSOR 0
}
\u000b { ;# ^k
variable YANK
set YANK [string range $CMDLINE [expr {$CMDLINE_CURSOR } ] end ]
set CMDLINE [string range $CMDLINE 0 [expr {$CMDLINE_CURSOR - 1 } ]]
}
\u0019 { ;# ^y
variable YANK
if { [ info exists YANK ] } {
set CMDLINE \
"[string range $CMDLINE 0 [expr {$CMDLINE_CURSOR - 1 }]]$YANK[string range $CMDLINE $CMDLINE_CURSOR end]"
}
}
\u000e { ;# ^n
handleHistory -1
}
\u0010 { ;# ^p
handleHistory 1
}
\u0003 { ;# ^c
# clear line
set CMDLINE ""
set CMDLINE_CURSOR 0
}
\u0008 -
\u007f { ;# ^h && backspace ?
if {$CMDLINE_CURSOR > 0} {
incr CMDLINE_CURSOR -1
set CMDLINE [string replace $CMDLINE \
$CMDLINE_CURSOR $CMDLINE_CURSOR]
}
}
\u001b { ;# ESC - handle escape sequences
handleEscapes
}
}
# Rate limiter:
set keybuffer ""
}
proc TclReadLine::shortMatch {maybe} {
# Find the shortest matching substring:
set maybe [lsort $maybe]
set shortest [lindex $maybe 0]
foreach x $maybe {
while {![string match $shortest* $x]} {
set shortest [string range $shortest 0 end-1]
}
}
return $shortest
}
proc TclReadLine::addCompletionHandler {completion_extension} {
variable COMPLETION_HANDLERS
set COMPLETION_HANDLERS [concat $completion_extension $COMPLETION_HANDLERS]
}
proc TclReadLine::delCompletionHandler {completion_extension} {
variable COMPLETION_HANDLERS
set COMPLETION_HANDLERS [lsearch -all -not -inline $COMPLETION_HANDLERS $completion_extension]
}
proc TclReadLine::getCompletionHandler {} {
variable COMPLETION_HANDLERS
return "$COMPLETION_HANDLERS"
}
proc TclReadLine::handleCompletion {} {
variable COMPLETION_HANDLERS
foreach handler $COMPLETION_HANDLERS {
if {[eval $handler] == 1} {
break
}
}
return
}
proc TclReadLine::handleCompletionBase {} {
variable CMDLINE
variable CMDLINE_CURSOR
set vars ""
set cmds ""
set execs ""
set files ""
# First find out what kind of word we need to complete:
set wordstart [string last " " $CMDLINE [expr {$CMDLINE_CURSOR-1}]]
incr wordstart
set wordend [string first " " $CMDLINE $wordstart]
if {$wordend == -1} {
set wordend end
} else {
incr wordend -1
}
set word [string range $CMDLINE $wordstart $wordend]
if {[string trim $word] == ""} return
set firstchar [string index $word 0]
# Check if word is a variable:
if {$firstchar == "\$"} {
set word [string range $word 1 end]
incr wordstart
# Check if it is an array key:proc
set x [string first "(" $word]
if {$x != -1} {
set v [string range $word 0 [expr {$x-1}]]
incr x
set word [string range $word $x end]
incr wordstart $x
if {[uplevel \#0 "array exists $v"]} {
set vars [uplevel \#0 "array names $v $word*"]
}
} else {
foreach x [uplevel \#0 {info vars}] {
if {[string match $word* $x]} {
lappend vars $x
}
}
}
} else {
# Check if word is possibly a path:
if {$firstchar == "/" || $firstchar == "." || $wordstart != 0} {
set files [glob -nocomplain -- $word*]
}
if {$files == ""} {
# Not a path then get all possibilities:
if {$firstchar == "\[" || $wordstart == 0} {
if {$firstchar == "\["} {
set word [string range $word 1 end]
incr wordstart
}
# Check executables:
foreach dir [split $::env(PATH) :] {
foreach f [glob -nocomplain -directory $dir -- $word*] {
set exe [string trimleft [string range $f \
[string length $dir] end] "/"]
if {[lsearch -exact $execs $exe] == -1} {
lappend execs $exe
}
}
}
# Check commands:
foreach x [info commands] {
if {[string match $word* $x]} {
lappend cmds $x
}
}
} else {
# Check commands anyway:
foreach x [info commands] {
if {[string match $word* $x]} {
lappend cmds $x
}
}
}
}
if {$wordstart != 0} {
# Check variables anyway:
set x [string first "(" $word]
if {$x != -1} {
set v [string range $word 0 [expr {$x-1}]]
incr x
set word [string range $word $x end]
incr wordstart $x
if {[uplevel \#0 "array exists $v"]} {
set vars [uplevel \#0 "array names $v $word*"]
}
} else {
foreach x [uplevel \#0 {info vars}] {
if {[string match $word* $x]} {
lappend vars $x
}
}
}
}
}
variable COMPLETION_MATCH
set maybe [concat $vars $cmds $execs $files]
set shortest [shortMatch $maybe]
if {"$word" == "$shortest"} {
if {[llength $maybe] > 1 && $COMPLETION_MATCH != $maybe} {
set COMPLETION_MATCH $maybe
clearline
set temp ""
foreach {match format} {
vars "35"
cmds "1;32"
execs "32"
files "0"
} {
if {[llength [set $match]]} {
append temp "[ESC]\[${format}m"
foreach x [set $match] {
append temp "[file tail $x] "
}
append temp "[ESC]\[0m"
}
}
print "\n$temp\n"
}
} else {
if {[file isdirectory $shortest] &&
[string index $shortest end] != "/"} {
append shortest "/"
}
if {$shortest != ""} {
set CMDLINE \
[string replace $CMDLINE $wordstart $wordend $shortest]
set CMDLINE_CURSOR \
[expr {$wordstart+[string length $shortest]}]
} elseif { $COMPLETION_MATCH != " not found "} {
set COMPLETION_MATCH " not found "
print "\nNo match found.\n"
}
}
}
proc TclReadLine::handleHistory {x} {
variable HISTORY_LEVEL
variable HISTORY_SIZE
variable CMDLINE
variable CMDLINE_CURSOR
variable CMDLINE_PARTIAL
set maxid [expr {[history nextid] - 1}]
if {$maxid > 0} {
#
# Check for a top level command line and history event
# Store this command line locally (i.e. don't use the history stack)
#
if {$HISTORY_LEVEL == 0} {
set CMDLINE_PARTIAL $CMDLINE
}
incr HISTORY_LEVEL $x
#
# Note: HISTORY_LEVEL is used to offset into
# the history events. It will be reset to zero
# when a command is executed by tclline.
#
# Check the three bounds of
# 1) HISTORY_LEVEL <= 0 - Restore the top level cmd line (not in history stack)
# 2) HISTORY_LEVEL > HISTORY_SIZE
# 3) HISTORY_LEVEL > maxid
#
if {$HISTORY_LEVEL <= 0} {
set HISTORY_LEVEL 0
if {[info exists CMDLINE_PARTIAL]} {
set CMDLINE $CMDLINE_PARTIAL
set CMDLINE_CURSOR [string length $CMDLINE]
}
return
} elseif {$HISTORY_LEVEL > $maxid} {
set HISTORY_LEVEL $maxid
} elseif {$HISTORY_LEVEL > $HISTORY_SIZE} {
set HISTORY_LEVEL $HISTORY_SIZE
}
set id [expr {($maxid + 1) - $HISTORY_LEVEL}]
set cmd [expr {$id > $maxid ? "" : [history event $id]}]
set CMDLINE $cmd
set CMDLINE_CURSOR [string length $cmd]
}
}
################################
# History handling functions
################################
proc TclReadLine::getHistory {} {
variable HISTORY_SIZE
set l [list]
set e [history nextid]
set i [expr {$e - $HISTORY_SIZE}]
if {$i <= 0} {
set i 1
}
for { set i } {$i < $e} {incr i} {
lappend l [history event $i]
}
return $l
}
proc TclReadLine::setHistory {hlist} {
foreach event $hlist {
history add $event
}
}
################################
# main()
################################
proc TclReadLine::rawInput {} {
fconfigure stdin -buffering none -blocking 0
fconfigure stdout -buffering none -translation crlf
stty raw -echo
}
proc TclReadLine::lineInput {} {
fconfigure stdin -buffering line -blocking 1
fconfigure stdout -buffering line
stty -raw echo
}
proc TclReadLine::doExit {{code 0}} {
variable HISTFILE
variable HISTORY_SIZE
# Reset terminal:
#print "[ESC]c[ESC]\[2J" nowait
restore ;# restore "info' command -
lineInput
set hlist [getHistory]
#
# Get rid of the TclReadLine::doExit, shouldn't be more than one
#
set hlist [lsearch -all -not -inline $hlist "TclReadLine::doExit"]
set hlistlen [llength $hlist]
if {$hlistlen > 0} {
set f [open $HISTFILE w]
if {$hlistlen > $HISTORY_SIZE} {
set hlist [lrange $hlist [expr {($hlistlen - $HISTORY_SIZE - 1)}] end]
}
foreach x $hlist {
# Escape newlines:
puts $f [string map {
\n "\\n"
"\\" "\\b"
} $x]
}
close $f
}
exit $code
}
proc TclReadLine::restore {} {
lineInput
rename ::unknown TclReadLine::unknown
rename ::_unknown ::unknown
}
proc TclReadLine::interact {} {
rename ::unknown ::_unknown
rename TclReadLine::unknown ::unknown
variable RCFILE
if {[file exists $RCFILE]} {
source $RCFILE
}
# Load history if available:
# variable HISTORY
variable HISTFILE
variable HISTORY_SIZE
history keep $HISTORY_SIZE
if {[file exists $HISTFILE]} {
set f [open $HISTFILE r]
set hlist [list]
foreach x [split [read $f] "\n"] {
if {$x != ""} {
# Undo newline escapes:
lappend hlist [string map {
"\\n" \n
"\\\\" "\\"
"\\b" "\\"
} $x]
}
}
setHistory $hlist
unset hlist
close $f
}
rawInput
# This is to restore the environment on exit:
# Do not unalias this!
alias exit TclReadLine::doExit
variable ThisScript [info script]
tclline ;# emit the first prompt
fileevent stdin readable TclReadLine::tclline
variable forever
vwait TclReadLine::forever
restore
}
proc TclReadLine::check_partial_keyseq {buffer} {
variable READLINE_LATENCY
upvar $buffer keybuffer
#
# check for a partial esc sequence as tclline expects the whole sequence
#
if {[string index $keybuffer 0] == [ESC]} {
#
# Give extra time to read partial key sequences
#
set timer [expr {[clock clicks -milliseconds] + $READLINE_LATENCY}]
while {[clock clicks -milliseconds] < $timer } {
append keybuffer [read stdin]
}
}
}
proc TclReadLine::tclline {} {
variable COLUMNS
variable CMDLINE_CURSOR
variable CMDLINE
set char ""
set keybuffer [read stdin]
set COLUMNS [getColumns]
check_partial_keyseq keybuffer
while {$keybuffer != ""} {
if {[eof stdin]} return
set char [readbuf keybuffer]
if {$char == ""} {
# Sleep for a bit to reduce CPU overhead:
after 40
continue
}
if {[string is print $char]} {
set x $CMDLINE_CURSOR
if {$x < 1 && [string trim $char] == ""} continue
set trailing [string range $CMDLINE $x end]
set CMDLINE [string replace $CMDLINE $x end]
append CMDLINE $char
append CMDLINE $trailing
incr CMDLINE_CURSOR
} elseif {$char == "\t"} {
handleCompletion
} elseif {$char == "\n" || $char == "\r"} {
if {[info complete $CMDLINE] &&
[string index $CMDLINE end] != "\\"} {
lineInput
print "\n" nowait
uplevel \#0 {
# Handle aliases:
set cmdline $TclReadLine::CMDLINE
#
# Add the cmd line to history before doing any substitutions
#
history add $cmdline
set cmd [string trim [regexp -inline {^\s*[^\s]+} $cmdline]]
if {[info exists TclReadLine::ALIASES($cmd)]} {
regsub -- "(?q)$cmd" $cmdline $TclReadLine::ALIASES($cmd) cmdline
}
# Perform glob substitutions:
set cmdline [string map {
"\\*" \0
"\\~" \1
} $cmdline]
#
# Prevent glob substitution of *,~ for tcl commands
#
if {[info commands $cmd] != ""} {
set cmdline [string map {
"\*" \0
"\~" \1
} $cmdline]
}
while {[regexp -indices \
{([\w/\.]*(?:~|\*)[\w/\.]*)+} $cmdline x]
} {
foreach {i n} $x break
set s [string range $cmdline $i $n]
set x [glob -nocomplain -- $s]
# If glob can't find anything then don't do
# glob substitution, pass * or ~ as literals:
if {$x == ""} {
set x [string map {
"*" \0
"~" \1
} $s]
}
set cmdline [string replace $cmdline $i $n $x]
}
set cmdline [string map {
\0 "*"
\1 "~"
} $cmdline]
rename ::info ::_info
rename TclReadLine::localInfo ::info
# Reset HISTORY_LEVEL before next command
set TclReadLine::HISTORY_LEVEL 0
if {[info exists TclReadLine::CMDLINE_PARTIAL]} {
unset TclReadLine::CMDLINE_PARTIAL
}
# Run the command:
set code [catch $cmdline res]
rename ::info TclReadLine::localInfo
rename ::_info ::info
if {$code == 1} {
TclReadLine::print "$::errorInfo\n"
} else {
TclReadLine::print "$res\n"
}
set TclReadLine::CMDLINE ""
set TclReadLine::CMDLINE_CURSOR 0
set TclReadLine::CMDLINE_LINES {0 0}
} ;# end uplevel
rawInput
} else {
set x $CMDLINE_CURSOR
if {$x < 1 && [string trim $char] == ""} continue
set trailing [string range $CMDLINE $x end]
set CMDLINE [string replace $CMDLINE $x end]
append CMDLINE $char
append CMDLINE $trailing
incr CMDLINE_CURSOR
}
} else {
handleControls
}
}
prompt $CMDLINE
}
# start immediately if invoked as a script:
if {!$::tcl_interactive && [info script] eq $::argv0} {
TclReadLine::interact
}
# Put the following in your .tclshrc
# if {$::tcl_interactive} {
# package require TclReadLine
# TclReadLine::interact
# }