forked from andreas-kupries/tcl-linenoise
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlinenoise.tcl
423 lines (344 loc) · 11.5 KB
/
linenoise.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
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## A Tcl Binding to antirez's linenoise (Minimal line-editing)
## as modified and extended by Steve Bennett of Workware.
##
## Copyright (c) 2013-2014 Andreas Kupries http://wiki.tcl.tk/andreas%20kupries
# # ## ### ##### ######## ############# #####################
##
# Based on git@github.com:andreas-kupries/linenoise.git
# Forked from git@github.com:msteveb/linenoise.git
# Forked from git@github.com:antirez/linenoise.git
#
# Based on http://github.com/andreas-kupries/linenoise
# Forked from http://github.com/msteveb/linenoise
# Forked from http://github.com/antirez/linenoise
# # ## ### ##### ######## ############# #####################
##
# Notes and ideas regarding the underlying linenoise C library
#
# - Note: Do I need the utf support, or is that term specific?
# # ## ### ##### ######## ############# #####################
## Requisites
package require critcl 3.1
critcl::buildrequirement {
package require critcl::util 1.1 ; # locate
}
# # ## ### ##### ######## ############# #####################
if {![critcl::compiling]} {
error "Unable to build linenoise, no proper compiler found."
}
# # ## ### ##### ######## ############# #####################
## Administrivia
critcl::license \
{Andreas Kupries} \
{Under a BSD license.}
critcl::summary \
{A line-editor package build on top of Steve Bennet's extensions to Salvator's (antirez) linenoise C library}
critcl::description {
This package provides access to antirez's linenoise library for
creating a line editor, as modified and extended by Steve Bennett
(msteveb) of Workware, and myself. An important difference to
readline/editline is the minimal approach of linenoise.
}
critcl::subject \
{line editor} linenoise readline editline \
{edit line} tty console terminal {read line} \
{line reader}
critcl::meta location \
http://github.com/andreas-kupries/tcl-linenoise
critcl::meta location/c-library \
http://github.com/andreas-kupries/linenoise
critcl::meta location/c-library/msteveb \
http://github.com/msteveb/linenoise
critcl::meta location/c-library/origin \
http://github.com/antirez/linenoise
# # ## ### ##### ######## ############# #####################
## Implementation.
critcl::tcl 8.5
# # ## ### ##### ######## ############# #####################
## Find the linenoise sources (via its headers), and figure out their
## configuration.
#
# We specify both paths for where we expect to find the sources of
# linenoise itself. Both are given relative to the directory of this
# file.
#
# (1) A sub directory in our sources.
# (2) A sibling directory to our sources.
critcl::msg "\n"
# With Tcl 8.5+ CK could be replaced by a lambda.
proc PB {label x} { return "${label}: [expr {$x ? "yes" : "no"}]" }
proc CK {p} {
# Check for "linenoiseAddCompletion", make sure that the found
# header is the correct one.
set lines [split [critcl::Cat $p] \n]
set n [llength [critcl::Grep *linenoiseAddCompletion* $lines]]
if {!$n} { return 0 }
# Additional checks to figure out the libraries' configuration.
set ::hashidden [llength [critcl::Grep *linenoiseGetHidden* $lines]]
set ::exthidden [llength [critcl::Grep *LN_HIDDEN_STAR* $lines]]
set ::haslines [llength [critcl::Grep *linenoiseLines* $lines]]
return 1
}
critcl::cheaders [critcl::util::locate "Location of Linenoise " {
linenoise/linenoise.h
../linenoise/linenoise.h
} ::CK]
# Location of Linenoise
critcl::msg [PB {Support for hidden input } $hashidden]
critcl::msg [PB {Extended hidden input } $exthidden]
critcl::msg [PB {Access to terminal height} $haslines]
rename CK {}
rename PB {}
critcl::msg ""
# # ## ### ##### ######## ############# #####################
## Declare the Tcl layer aggregating the C primitives into a Tclish
## API.
critcl::tsources policy.tcl
# # ## ### ##### ######## ############# #####################
## Main C section.
## ATTENTION! linenoise operates directly on the process's stdin file
## descriptor. This makes its operation inherently thread-
## unsafe.
#
## To rescue what we can we combine the actual prompting with the
## completion callback setup in a single command which mutex locks the
## whole user interaction. This allows it to not only use
## process-global variables, but also prevents multiple threads from
## fighting for user interaction, forcing serialization.
critcl::ccode {
#include <linenoise.h>
#include <linenoise.c>
#include <tcl.h>
/* The mutex serializing the threads requesting user interaction. */
TCL_DECLARE_MUTEX (edit)
/* The Tcl interpreter currently interacting with the user */
Tcl_Interp* einterp;
/* The Tcl-level completion callback for the current interaction,
* if any.
*/
Tcl_Obj* ecomplete;
/* The C-level completion callback. Assumes that the e-variables
* above are properly set, and that everything is locked to the
* current thread.
*/
static void
linenoise_tcl_callback (const char* buffer, linenoiseCompletions* lc)
{
Tcl_SavedResult sr;
Tcl_Obj* completions;
Tcl_Obj* cmd;
Tcl_Obj** listv;
int i, listc, res = TCL_OK;
/* Generate callback, extend prefix with argument */
cmd = Tcl_DuplicateObj (ecomplete);
Tcl_ListObjAppendElement (einterp, cmd, Tcl_NewStringObj (buffer, -1));
/* Run the callback, result is (expected to be a)
* list of completions.
*/
Tcl_SaveResult (einterp, &sr);
res = Tcl_EvalObj (einterp, cmd);
completions = Tcl_GetObjResult (einterp);
/* Ignore failures, and results which are not lists */
if ((res != TCL_OK) ||
(Tcl_ListObjGetElements (einterp, completions, &listc, &listv) != TCL_OK)) {
Tcl_RestoreResult (einterp, &sr);
return;
}
/* Copy the result over to linenoise structures */
for (i=0; i< listc; i++) {
linenoiseAddCompletion (lc, Tcl_GetString (listv[i]));
}
Tcl_RestoreResult (einterp, &sr);
}
}
# # ## ### ##### ######## ############# #####################
## Inner API: History primitives
# - Direct extension of history with a string.
# - Clearing the history
# - Loading the history from file (OS native path).
# - Saving the history to file (OS native path).
# - Setting and retrieving the maximal history size.
# - Retrieving current size of the history.
# - Retrieving the current contents of the history as a Tcl list.
# (Saving the history to memory)
# - Setting the history from a Tcl list.
# (Loading the history from memory)
critcl::cproc linenoise::history_add {char* line} boolean {
return linenoiseHistoryAdd (line);
}
critcl::cproc linenoise::history_clear {} void {
/* msteveb/linenoise extension */
linenoiseHistoryFree ();
/* bugfix! */
history_len = 0;
}
# The caller is responsible for Tcl VFS integration (temp files, etc.).
# See policy.tcl for the wrapper code.
critcl::cproc linenoise::history_load {char* path} int {
return linenoiseHistoryLoad (path);
}
# The caller is responsible for Tcl VFS integration (temp files, etc.)
# See policy.tcl for the wrapper code.
critcl::cproc linenoise::history_save {char* path} int {
return linenoiseHistorySave (path);
}
# maxlen < 1 ==> 0, no change
# else ==> 1, trim or expand
critcl::cproc linenoise::history_setmax {int maxlen} boolean {
return linenoiseHistorySetMaxLen (maxlen);
}
# ATTENTION! We are poking into the internals of linenoise again.
critcl::cproc linenoise::history_getmax {} int {
return linenoiseHistoryGetMaxLen ();
}
critcl::cproc linenoise::history_size {} int {
/* msteveb/linenoise extension */
int len;
(void) linenoiseHistory (&len);
return len;
}
critcl::cproc linenoise::history_set {
Tcl_Interp* ip
Tcl_Obj* list
} ok {
/* Replace current history with the entries in the specified list */
int i;
int lc;
Tcl_Obj** lv;
int r = Tcl_ListObjGetElements (ip, list, &lc, &lv);
if (r != TCL_OK) {
return r;
}
/* Inlined history_clear */
/* msteveb/linenoise extension */
linenoiseHistoryFree ();
/* bugfix! */
history_len = 0;
for (i=0; i < lc; i++) {
linenoiseHistoryAdd (Tcl_GetString (lv [i]));
}
return r;
}
critcl::cproc linenoise::history_list {} Tcl_Obj* {
/* msteveb/linenoise extension */
int i, len;
char** h;
Tcl_Obj* res;
Tcl_Obj** lv;
h = linenoiseHistory (&len);
lv = (Tcl_Obj**) ckalloc (len * sizeof (Tcl_Obj*));
for (i=0; i < len; i++) {
lv [i] = Tcl_NewStringObj (h [i],-1);
}
res = Tcl_NewListObj (len, lv);
ckfree ((char*) lv);
Tcl_IncrRefCount (res);
return res;
}
# # ## ### ##### ######## ############# #####################
## Inner API: Main primitives
## - modify/query the "hidden" flag
## - prompt for input, possibly with completion
if {$hashidden} {
if {$exthidden} {
critcl::msg -nonewline { (Use: Extended hidden input)}
# Extended => modes = {no, (echo)stars, all|full|noecho}
critcl::buildrequirement {
package require critcl::emap
}
# visible == no == 0,
# all == yes == 1, -- default is full supression.
# stars == 2
critcl::emap::def hiddenmode {
no 0 n 0 off 0 false 0 0 0
all 1 yes 1 y 1 on 1 true 1 1 1
stars 2
} -nocase
# result-type: hiddenmode
# arg-type: hiddenmode
critcl::cproc linenoise::hidden_set {hiddenmode enable} void {
linenoiseSetHidden (enable);
}
critcl::cproc linenoise::hidden_get {} hiddenmode {
return linenoiseGetHidden ();
}
critcl::cproc linenoise::hidden_extended {} boolean {
return 1;
}
} else {
critcl::msg -nonewline { (Use: Basic hidden input)}
# Basic hidden => enable is boolean <=> on/off.
critcl::cproc linenoise::hidden_set {boolean enable} void {
linenoiseSetHidden (enable);
}
critcl::cproc linenoise::hidden_get {} boolean {
return linenoiseGetHidden ();
}
critcl::cproc linenoise::hidden_extended {} boolean {
return 0;
}
}
} else {
critcl::msg -nonewline { (Use: NO hidden input)}
}
if 0 {# may we have this ?
critcl::cproc linenoise::clear {} void {
linenoiseClearScreen ();
}}
critcl::cproc linenoise::columns {} int {
return linenoiseColumns ();
}
if {$haslines} {
critcl::msg -nonewline { (Use: Query terminal height)}
critcl::cproc linenoise::lines {} int {
return linenoiseLines ();
}
} else {
critcl::msg -nonewline { (Use: NO querying terminal height)}
}
critcl::cproc linenoise::Prompt {
Tcl_Interp* interp
char* prompt
Tcl_Obj* complete
} ok {
Tcl_Obj** lv;
int lc;
char* line;
if (Tcl_ListObjGetElements (einterp, complete,
&lc, &lv) != TCL_OK) {
return TCL_ERROR;
}
Tcl_MutexLock (&edit);
if (!lc) {
/* No completion callback. Simple prompting. */
einterp = 0;
ecomplete = 0;
linenoiseSetCompletionCallback (0);
} else {
einterp = interp;
ecomplete = complete;
linenoiseSetCompletionCallback (linenoise_tcl_callback);
}
line = linenoise (prompt);
linenoiseSetCompletionCallback (0);
einterp = 0;
ecomplete = 0;
Tcl_MutexUnlock (&edit);
if (line == NULL) {
Tcl_SetResult (interp, "aborted", TCL_STATIC);
return TCL_ERROR;
}
Tcl_SetResult (interp, line, TCL_VOLATILE);
return TCL_OK;
}
# # ## ### ##### ######## ############# #####################
## Make the C pieces ready. Immediate build of the binaries, no deferal.
if {![critcl::load]} {
error "Building and loading linenoise failed."
}
# # ## ### ##### ######## ############# #####################
package provide linenoise 1.3
return
# vim: set sts=4 sw=4 tw=80 et ft=tcl: