Hello, here's a patch adding a command to enable/disable the very confusing readline history expansion:
diff --git a/sample.tclshrc b/sample.tclshrc
index e600455..9f18252 100644
--- a/sample.tclshrc
+++ b/sample.tclshrc
@@ -62,6 +62,9 @@ if {$tcl_interactive} {
# command (instead of when cleanly exiting)
set tclreadline::autosave 1
+ # disable history expansion (e.g. !, !!, !num or ^str1^str2^)
+ # ::tclreadline::readline historyexpansion 0
+
# go to tclrealdine's main loop.
#
tclreadline::Loop
diff --git a/tclreadline.c b/tclreadline.c
index a81d2fc..d253d64 100644
--- a/tclreadline.c
+++ b/tclreadline.c
@@ -99,6 +99,7 @@ static char* tclrl_eof_string = (char*) NULL;
static char* tclrl_custom_completer = (char*) NULL;
static char* tclrl_last_line = (char*) NULL;
static int tclrl_use_builtin_completer = 1;
+static int tclrl_use_history_expansion = 1;
static int tclrl_history_length = -1;
Tcl_Interp* tclrl_interp = (Tcl_Interp*) NULL;
@@ -206,12 +207,13 @@ TclReadlineCmd(ClientData clientData, Tcl_Interp *interp, int objc,
"read", "initialize", "write", "add", "complete",
"customcompleter", "builtincompleter", "eofchar",
"reset-terminal", "bell", "text", "update",
- (char *) NULL
+ "historyexpansion", (char *) NULL
};
enum SubCmdIdx {
TCLRL_READ, TCLRL_INITIALIZE, TCLRL_WRITE, TCLRL_ADD, TCLRL_COMPLETE,
TCLRL_CUSTOMCOMPLETER, TCLRL_BUILTINCOMPLETER, TCLRL_EOFCHAR,
- TCLRL_RESET_TERMINAL, TCLRL_BELL, TCLRL_TEXT, TCLRL_UPDATE
+ TCLRL_RESET_TERMINAL, TCLRL_BELL, TCLRL_TEXT, TCLRL_UPDATE,
+ TCLRL_HISTORYEXPANSION
};
Tcl_ResetResult(interp); /* clear the result space */
@@ -434,7 +436,6 @@ TclReadlineCmd(ClientData clientData, Tcl_Interp *interp, int objc,
break;
-
case TCLRL_TEXT:
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
@@ -446,6 +447,27 @@ TclReadlineCmd(ClientData clientData, Tcl_Interp *interp, int objc,
Tcl_NewStringObj(rl_line_buffer ? rl_line_buffer : "", -1));
break;
+ case TCLRL_HISTORYEXPANSION:
+ if (objc > 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?boolean?");
+ return TCL_ERROR;
+ } else if (3 == objc) {
+ int bool = tclrl_use_history_expansion;
+ if (TCL_OK != Tcl_GetBoolean(interp,
+ Tcl_GetStringFromObj(objv[2], 0),
+ &bool)) {
+ Tcl_AppendResult(interp,
+ "wrong # args: should be a boolean value.",
+ (char*) NULL);
+ return TCL_ERROR;
+ } else {
+ tclrl_use_history_expansion = bool;
+ }
+ }
+ Tcl_AppendResult(interp, tclrl_use_history_expansion ? "1" : "0",
+ (char*) NULL);
+ break;
+
default:
goto BAD_COMMAND;
/* NOTREACHED */
@@ -502,23 +524,28 @@ TclReadlineLineCompleteHandler(char* ptr)
*/
char* expansion = (char*) NULL;
- int status = history_expand(ptr, &expansion);
-
- if (status >= 2) {
- /* TODO: make this a valid tcl output */
- printf("%s\n", expansion);
- FREE(ptr);
- FREE(expansion);
- return;
- } else if (status <= -1) {
- Tcl_AppendResult
- (tclrl_interp, "error in history expansion: ", expansion, "\n", (char*) NULL);
+ if (tclrl_use_history_expansion) {
+ int status = history_expand(ptr, &expansion);
+
+ if (status >= 2) {
+ /* TODO: make this a valid tcl output */
+ printf("%s\n", expansion);
+ FREE(ptr);
+ FREE(expansion);
+ return;
+ } else if (status <= -1) {
+ Tcl_AppendResult
+ (tclrl_interp, "error in history expansion: ", expansion, "\n", (char*) NULL);
TclReadlineTerminate(TCL_ERROR);
- FREE(ptr);
- FREE(expansion);
- return;
+ FREE(ptr);
+ FREE(expansion);
+ return;
+ } else {
+ Tcl_AppendResult(tclrl_interp, expansion, (char*) NULL);
+ }
} else {
- Tcl_AppendResult(tclrl_interp, expansion, (char*) NULL);
+ Tcl_AppendResult(tclrl_interp, ptr, (char*) NULL);
+ expansion = ptr;
}
#ifdef EXECUTING_MACRO_NAME
@@ -549,7 +576,9 @@ TclReadlineLineCompleteHandler(char* ptr)
*/
TclReadlineTerminate(LINE_COMPLETE);
FREE(ptr);
- FREE(expansion);
+ if (tclrl_use_history_expansion) {
+ FREE(expansion);
+ }
}
}
@@ -670,7 +699,7 @@ TclReadlineCompletion(char* text, int start, int end)
int status;
rl_completion_append_character = ' '; /* reset, just in case ... */
- if (text && ('!' == text[0]
+ if (tclrl_use_history_expansion && text && ('!' == text[0]
|| (start && rl_line_buffer[start - 1] == '!' /* for '$' */))) {
char* expansion = (char*) NULL;
int oldlen = strlen(rl_line_buffer);
@@ -893,4 +922,3 @@ TclReadlineParse(char** args, int maxargs, char* buf)
*args = NULL;
return nr;
}
-
diff --git a/tclreadline.n.in b/tclreadline.n.in
index e128d22..b08b92f 100644
--- a/tclreadline.n.in
+++ b/tclreadline.n.in
@@ -170,6 +170,11 @@ idea of the terminal settings using terminalName as the terminal type.
The form w/o argument might not work if tclreadline was compiled
with an older version of libreadline.
+.TP 5
+\fB::tclreadline::readline historyexpansion\fP [\fIbool\fP]
+enable or disable history expansion.
+\fBtclreadline::readline historyexpansion\fP returns the current value.
+
.TP 5
\fB::tclreadline::readline bell\fP
Ring the terminal bell, obeying the setting of bell-style
diff --git a/tclreadlineCompleter.tcl b/tclreadlineCompleter.tcl
index 49f5cd5..74d3bf1 100644
--- a/tclreadlineCompleter.tcl
+++ b/tclreadlineCompleter.tcl
@@ -3392,7 +3392,8 @@ namespace eval tclreadline {
switch -- $pos {
1 { return [CompleteFromList $text \
{read initialize write add complete customcompleter
- builtincompleter eofchar reset-terminal bell}]
+ builtincompleter eofchar reset-terminal bell
+ historyexpansion}]
}
2 {
switch -- $cmd {
@@ -3402,6 +3403,7 @@ namespace eval tclreadline {
add { return [DisplayHints <completerLine>] }
completer { return [DisplayHints <line>] }
customcompleter { return [DisplayHints ?scriptCompleter?] }
+ historyexpansion -
builtincompleter { return [CompleteBoolean $text] }
eofchar { return [DisplayHints ?script?] }
reset-terminal {
diff --git a/tclreadlineSetup.tcl.in b/tclreadlineSetup.tcl.in
index 1ba2cc3..e235888 100644
--- a/tclreadlineSetup.tcl.in
+++ b/tclreadlineSetup.tcl.in
@@ -76,13 +76,15 @@ proc unknown args {
}
if {[info exists savedErrorCode]} { set errorCode $savedErrorCode }
if {[info exists savedErrorInfo]} { set errorInfo $savedErrorInfo }
- if {$name == "!!"} {
- set newcmd [history event]
- } elseif {[regexp {^!(.+)$} $name dummy event]} {
- set newcmd [history event $event]
- } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name dummy old new]} {
- set newcmd [history event -1]
- catch {regsub -all -- $old $newcmd $new newcmd}
+ if {[::tclreadline::readline historyexpansion]} {
+ if {$name == "!!"} {
+ set newcmd [history event]
+ } elseif {[regexp {^!(.+)$} $name dummy event]} {
+ set newcmd [history event $event]
+ } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name dummy old new]} {
+ set newcmd [history event -1]
+ catch {regsub -all -- $old $newcmd $new newcmd}
+ }
}
if {[info exists newcmd]} {
tclLog $newcmd
diff --git a/tclreadline.n.in b/tclreadline.n.in
index b08b92f..d78d183 100644
--- a/tclreadline.n.in
+++ b/tclreadline.n.in
@@ -84,18 +84,18 @@ Note that all commands reside in the namespace \fB::tclreadline::\fP.
.TP 5
\fB::tclreadline::readline add\fP \fIstring\fP
-adds a string to the completer. If the string contains white
+Adds a string to the completer. If the string contains white
spaces, each of the words will be completed consecutively when
hitting <Tab>. Example:
::tclreadline::readline add "button pathName ?options?"
-typing but<Tab> will complete to button. Hitting <Tab> again
+Typing but<Tab> will complete to button. Hitting <Tab> again
will complete to "button pathName". ...
.TP 5
\fB::tclreadline::readline complete\fP \fIstring\fP
-returns 1 if \fIstring\fP is a complete tcl command and 0 otherwise.
+Returns 1 if \fIstring\fP is a complete tcl command and 0 otherwise.
.TP 5
\fB::tclreadline::readline customcompleter\fP [\fIstring\fP]
@@ -126,7 +126,7 @@ the file tclreadlineSetup.tcl.
.TP 5
\fB::tclreadline::readline builtincompleter\fP [\fIbool\fP]
-enable or disable the builtin completer. If the builtin completer
+Enable or disable the builtin completer. If the builtin completer
is enabled, it will be invoked either if there is no custom completer,
or the custom completer returned an empty string. The builtin
completer is on by default.
@@ -135,7 +135,7 @@ custom completer (also, if called w/o the \fIbool\fP argument).
.TP 5
\fB::tclreadline::readline eofchar\fP [\fIscript\fP]
-set a script which will be called, if readline returns the eof character
+Set a script which will be called, if readline returns the eof character
(this is typically the case if CTRL-D is entered at the very beginning
of the line). The default for this script is "puts {}; exit". Setting
this to an empty value disables any action on eof.
@@ -143,37 +143,37 @@ this to an empty value disables any action on eof.
.TP 5
\fB::tclreadline::readline initialize\fP \fIhistoryfile\fP
-initialize the tclreadline interface and read the history from
+Initialize the tclreadline interface and read the history from
the \fIhistoryfile\fP. On success an empty string is returned.
This command has to be called before any other tclreadline commands.
.TP 5
\fB::tclreadline::readline read\fP \fIprompt\fP
-prints the \fIprompt\fP to stdout and enters the tclreadline event
+Prints the \fIprompt\fP to stdout and enters the tclreadline event
loop. Both readline and X events are processed. Returns the
(eventually history-expanded) input string.
-\fBtclreadline::readline read\fP rises an error, if an
+\fBtclreadline::readline read\fP raises an error if an
error occurs while evaluating a script completer.
.TP 5
\fB::tclreadline::readline write\fP \fIhistoryfile\fP
-writes the history to the \fIhistoryfile\fP. This command is called
+Writes the history to the \fIhistoryfile\fP. This command is called
automatically from the internal routine ::tclreadline::Exit.
If the variable \fBtclreadline::historyLength\fP is non-negative,
the historyfile will be truncated to hold only this number lines.
.TP 5
\fB::tclreadline::readline reset-terminal\fP [\fIterminalName\fP]
-w/o argument: reset the state of the terminal to what it was
+Without argument: reset the state of the terminal to what it was
before tclreadline was used. With argument: reinitialize readline's
idea of the terminal settings using terminalName as the terminal type.
-The form w/o argument might not work if tclreadline was compiled
+The form without argument might not work if tclreadline was compiled
with an older version of libreadline.
.TP 5
\fB::tclreadline::readline historyexpansion\fP [\fIbool\fP]
-enable or disable history expansion.
-\fBtclreadline::readline historyexpansion\fP returns the current value.
+Enable or disable history expansion. With no argument, returns the current
+value.
.TP 5
\fB::tclreadline::readline bell\fP
@@ -190,7 +190,7 @@ Redraw the current input line.
.TP 5
\fB::tclreadline::Print\fP [\fIyes / no\fP]
-turns on or off the default behavior of tclsh to print the result of
+Turns on or off the default behavior of tclsh to print the result of
every command. This is turned on by default, so it will just behave
as the tclsh w/o tclreadline. Turning off might be useful, when reading
binary data for example. If \fB::tclreadline::Print\fP is called w/o
@@ -198,7 +198,7 @@ arguments, it returns the current setting.
.TP 5
\fB::tclreadline::Loop\fP [\fIhistoryfile\fP]
-enter the tclreadline main loop. This command is typically called from
+Enter the tclreadline main loop. This command is typically called from
the startup resource file (something .tclshrc, depending on the interpreter
you use, see the file `sample.tclshrc'). The main loop sets up some
completion characteristics as variable -- try something like "puts $b<TAB>" --
@@ -213,7 +213,7 @@ it is probably a good idea to start with tclreadline::Loop
.TP 5
\fB::tclreadline::prompt1\fP
-a proc which is called by ::tclreadline::Loop and returns a string
+A proc which is called by ::tclreadline::Loop and returns a string
which will be displayed as the primary prompt. This prompt will be
something like "[info nameofexecutable] \\[[pwd]\\]" possibly fancy colored.
The default proc is defined on entering the ::tclreadline::Loop,
@@ -238,7 +238,7 @@ in the file tclreadlineSetup.tcl in your tclreadline installation directory.
.TP 5
\fB::tclreadline::prompt2\fP
-a proc which is called by ::tclreadline::Loop and returns a string
+A proc which is called by ::tclreadline::Loop and returns a string
which will be displayed as the secondary prompt when interactively
prompting for continuation of an incomplete command.
@@ -256,19 +256,19 @@ namespace \fI::tclreadline\fP:
.TP 5
\fBtclreadline::version\fP (read-only)
-holds the version string "@VERSION@".
+Holds the version string "@VERSION@".
.TP 5
\fBtclreadline::patchLevel\fP (read-only)
-holds the patch level string "@PATCHLEVEL_STR@".
+Holds the patch level string "@PATCHLEVEL_STR@".
.TP 5
\fBtclreadline::library\fP (read-only)
-holds the library string "@TCLRL_DIR@".
+Holds the library string "@TCLRL_DIR@".
.TP 5
\fBtclreadline::license\fP (read-only)
-holds a BSD license statement.
+Holds a BSD license statement.
.TP 5
\fBtclreadline::historyLength\fP
@@ -278,7 +278,7 @@ will not be truncated. See also \fBtclreadline::write\fP.
.SH "FILES"
-the \fB.tclshrc\fP file in the HOME directory, which
+The \fB.tclshrc\fP file in the HOME directory, which
is read on tclsh startup. Alternatively, the name of this initialization
file might be \fB.wishrc\fP ... depending on what interpreter you use.
These files should typically contain something like
@@ -292,14 +292,14 @@ These files should typically contain something like
which will enter the tclreadline main loop.
.PP
-the \fB.tclsh-history\fP file in the HOME directory. On startup
+The \fB.tclsh-history\fP file in the HOME directory. On startup
commands will be read from this file. On exit, the readline history
is written to this file. Note that if you abort tclsh with <cntrl-c>
no history is written. For the future it is planned to set up a signal
handler, which will write the history on <ctrl-c> before exiting.
.PP
-the \fB.inputrc\fP file in the users HOME directory. This file
+The \fB.inputrc\fP file in the users HOME directory. This file
is used normally for all programs which use the gnu readline (e.g. bash).
The `global' readline settings there will be valid also for
\fBtclreadline\fP. Additionally the .inputrc might hold conditional
@@ -314,7 +314,7 @@ some lines in your .inputrc:
For further documentation please refer to the gnu readline documentation.
.SH "BUGS"
-citing the readline manual: It's too big and too slow.
+Citing the readline manual: It's too big and too slow.
.SH "SEE ALSO"