aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2020-07-17 11:15:10 +1000
committerSteve Bennett <steveb@workware.net.au>2023-07-04 10:08:32 +1000
commit92aec20d365d699d001b767659b2b3aa4d90bd18 (patch)
treef01c5a53d4b3ac78a7649c48cbae952980703b78
parenta6f101248ee409e1b952fa9560f409127701e9c7 (diff)
downloadjimtcl-92aec20d365d699d001b767659b2b3aa4d90bd18.zip
jimtcl-92aec20d365d699d001b767659b2b3aa4d90bd18.tar.gz
jimtcl-92aec20d365d699d001b767659b2b3aa4d90bd18.tar.bz2
interactive: enable hint support
And add a default implementation of tcl::stdhint to add hinting for some built-in commands. Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r--examples/jtclsh.tcl4
-rw-r--r--initjimsh.tcl59
-rw-r--r--jim-history.c13
-rw-r--r--jim-interactive.c124
-rw-r--r--jim.h3
-rw-r--r--tests/history.test12
6 files changed, 189 insertions, 26 deletions
diff --git a/examples/jtclsh.tcl b/examples/jtclsh.tcl
index 772b327..83d0b0d 100644
--- a/examples/jtclsh.tcl
+++ b/examples/jtclsh.tcl
@@ -8,8 +8,8 @@ package require history
set histfile [env HOME]/.jtclsh
history load $histfile
-# Use the standard Tcl autocompletion
-history completion tcl::autocomplete
+# Use the standard Tcl autocompletion and hint support
+history completion tcl::autocomplete tcl::autohint
set prefix ""
while {1} {
# Read a complete line (script)
diff --git a/initjimsh.tcl b/initjimsh.tcl
index 1aab707..eab4ab4 100644
--- a/initjimsh.tcl
+++ b/initjimsh.tcl
@@ -44,7 +44,7 @@ if {$tcl_platform(platform) eq "windows"} {
}
# Set a global variable here so that custom commands can be added post hoc
-set tcl::autocomplete_commands {info tcl::prefix socket namespace array clock file package string dict signal history}
+set tcl::autocomplete_commands {info tcl::prefix socket namespace array clock file package string dict signal history zlib}
# Simple interactive command line completion callback
# Explicitly knows about some commands that support "-commands"
@@ -76,4 +76,61 @@ proc tcl::autocomplete {prefix} {
}]
}
+# Only commands that support "cmd -help subcommand" have autohint suport
+set tcl::stdhint_commands {array clock file package signal history zlib}
+
+set tcl::stdhint_cols {
+ none {0}
+ black {30}
+ red {31}
+ green {32}
+ yellow {33}
+ blue {34}
+ purple {35}
+ cyan {36}
+ normal {37}
+ grey {30 1}
+ gray {30 1}
+ lred {31 1}
+ lgreen {32 1}
+ lyellow {33 1}
+ lblue {34 1}
+ lpurple {35 1}
+ lcyan {36 1}
+ white {37 1}
+}
+
+# Make it easy to change the colour
+set tcl::stdhint_col $tcl::stdhint_cols(lcyan)
+
+# The default hint implementation
+proc tcl::stdhint {string} {
+ set result ""
+ if {[llength $string] >= 2} {
+ lassign $string cmd arg
+ if {$cmd in $::tcl::stdhint_commands || [info channel $cmd] ne ""} {
+ catch {
+ set help [$cmd -help $arg]
+ if {[string match "Usage: $cmd *" $help]} {
+ set n [llength $string]
+ set subcmd [lindex $help $n]
+ incr n
+ set hint [join [lrange $help $n end]]
+ set prefix ""
+ if {![string match "* " $string]} {
+ if {$n == 3 && $subcmd ne $arg} {
+ # complete the subcommand in the hint
+ set prefix "[string range $subcmd [string length $arg] end] "
+ } else {
+ set prefix " "
+ }
+ }
+ set result [list $prefix$hint {*}$::tcl::stdhint_col]
+ }
+ }
+ }
+ }
+ return $result
+}
+
_jimsh_init
diff --git a/jim-history.c b/jim-history.c
index e1cc1d4..076fd0b 100644
--- a/jim-history.c
+++ b/jim-history.c
@@ -41,6 +41,12 @@ static int history_cmd_setcompletion(Jim_Interp *interp, int argc, Jim_Obj *cons
return JIM_OK;
}
+static int history_cmd_sethints(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ Jim_HistorySetHints(interp, Jim_Length(argv[0]) ? argv[0] : NULL);
+ return JIM_OK;
+}
+
static int history_cmd_load(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
Jim_HistoryLoad(Jim_String(argv[0]));
@@ -95,6 +101,13 @@ static const jim_subcmd_type history_command_table[] = {
1,
/* Description: Sets an autocompletion callback command, or none if "" */
},
+ { "hints",
+ "command",
+ history_cmd_sethints,
+ 1,
+ 1,
+ /* Description: Sets a hints callback command, or none if "" */
+ },
{ "getline",
"prompt ?varname?",
history_cmd_getline,
diff --git a/jim-interactive.c b/jim-interactive.c
index abdf491..ba3ed65 100644
--- a/jim-interactive.c
+++ b/jim-interactive.c
@@ -18,8 +18,18 @@
#endif
#ifdef USE_LINENOISE
+struct JimCompletionInfo {
+ Jim_Interp *interp;
+ Jim_Obj *completion_command;
+ Jim_Obj *hints_command;
+ /*Jim_Obj *hint;*/
+};
+
+static struct JimCompletionInfo *JimGetCompletionInfo(Jim_Interp *interp);
static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata);
static const char completion_callback_assoc_key[] = "interactive-completion";
+static char *JimHintsCallback(const char *prefix, int *color, int *bold, void *userdata);
+static void JimFreeHintsCallback(void *hint, void *userdata);
#endif
/**
@@ -28,24 +38,30 @@ static const char completion_callback_assoc_key[] = "interactive-completion";
char *Jim_HistoryGetline(Jim_Interp *interp, const char *prompt)
{
#ifdef USE_LINENOISE
- struct JimCompletionInfo *compinfo = Jim_GetAssocData(interp, completion_callback_assoc_key);
+ struct JimCompletionInfo *compinfo = JimGetCompletionInfo(interp);
char *result;
Jim_Obj *objPtr;
long mlmode = 0;
/* Set any completion callback just during the call to linenoise()
* to allow for per-interp settings
*/
- if (compinfo) {
+ if (compinfo->completion_command) {
linenoiseSetCompletionCallback(JimCompletionCallback, compinfo);
}
+ if (compinfo->hints_command) {
+ linenoiseSetHintsCallback(JimHintsCallback, compinfo);
+ linenoiseSetFreeHintsCallback(JimFreeHintsCallback);
+ }
objPtr = Jim_GetVariableStr(interp, "history::multiline", JIM_NONE);
if (objPtr && Jim_GetLong(interp, objPtr, &mlmode) == JIM_NONE) {
linenoiseSetMultiLine(mlmode);
}
result = linenoise(prompt);
- /* unset the callback */
+ /* unset the callbacks */
linenoiseSetCompletionCallback(NULL, NULL);
+ linenoiseSetHintsCallback(NULL, NULL);
+ linenoiseSetFreeHintsCallback(NULL);
return result;
#else
int len;
@@ -124,18 +140,13 @@ int Jim_HistoryGetMaxLen(void)
}
#ifdef USE_LINENOISE
-struct JimCompletionInfo {
- Jim_Interp *interp;
- Jim_Obj *command;
-};
-
static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata)
{
struct JimCompletionInfo *info = (struct JimCompletionInfo *)userdata;
Jim_Obj *objv[2];
int ret;
- objv[0] = info->command;
+ objv[0] = info->completion_command;
objv[1] = Jim_NewStringObj(info->interp, prefix, -1);
ret = Jim_EvalObjVector(info->interp, 2, objv);
@@ -151,37 +162,109 @@ static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp
}
}
+static char *JimHintsCallback(const char *prefix, int *color, int *bold, void *userdata)
+{
+ struct JimCompletionInfo *info = (struct JimCompletionInfo *)userdata;
+ Jim_Obj *objv[2];
+ int ret;
+ char *result = NULL;
+
+ objv[0] = info->hints_command;
+ objv[1] = Jim_NewStringObj(info->interp, prefix, -1);
+
+ ret = Jim_EvalObjVector(info->interp, 2, objv);
+
+ /* XXX: Consider how best to handle errors here. bgerror? */
+ if (ret == JIM_OK) {
+ Jim_Obj *listObj = Jim_GetResult(info->interp);
+ Jim_IncrRefCount(listObj);
+ /* Should return a list of {hintstring color bold} where the second two are optional */
+ int len = Jim_ListLength(info->interp, listObj);
+ if (len >= 1) {
+ long x;
+ result = Jim_StrDup(Jim_String(Jim_ListGetIndex(info->interp, listObj, 0)));
+ if (len >= 2 && Jim_GetLong(info->interp, Jim_ListGetIndex(info->interp, listObj, 1), &x) == JIM_OK) {
+ *color = x;
+ }
+ if (len >= 3 && Jim_GetLong(info->interp, Jim_ListGetIndex(info->interp, listObj, 2), &x) == JIM_OK) {
+ *bold = x;
+ }
+ }
+ Jim_DecrRefCount(info->interp, listObj);
+ }
+ return result;
+}
+
+static void JimFreeHintsCallback(void *hint, void *userdata)
+{
+ Jim_Free(hint);
+}
+
static void JimHistoryFreeCompletion(Jim_Interp *interp, void *data)
{
struct JimCompletionInfo *compinfo = data;
- Jim_DecrRefCount(interp, compinfo->command);
+ if (compinfo->completion_command) {
+ Jim_DecrRefCount(interp, compinfo->completion_command);
+ }
+ if (compinfo->hints_command) {
+ Jim_DecrRefCount(interp, compinfo->hints_command);
+ }
Jim_Free(compinfo);
}
+
+static struct JimCompletionInfo *JimGetCompletionInfo(Jim_Interp *interp)
+{
+ struct JimCompletionInfo *compinfo = Jim_GetAssocData(interp, completion_callback_assoc_key);
+ if (compinfo == NULL) {
+ compinfo = Jim_Alloc(sizeof(*compinfo));
+ compinfo->interp = interp;
+ compinfo->completion_command = NULL;
+ compinfo->hints_command = NULL;
+ Jim_SetAssocData(interp, completion_callback_assoc_key, JimHistoryFreeCompletion, compinfo);
+ }
+ return compinfo;
+}
#endif
/**
* Sets a completion command to be used with Jim_HistoryGetline()
* If commandObj is NULL, deletes any existing completion command.
*/
-void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *commandObj)
+void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *completionCommandObj)
{
#ifdef USE_LINENOISE
- if (commandObj) {
+ struct JimCompletionInfo *compinfo = JimGetCompletionInfo(interp);
+
+ if (completionCommandObj) {
/* Increment now in case the existing object is the same */
- Jim_IncrRefCount(commandObj);
+ Jim_IncrRefCount(completionCommandObj);
}
+ if (compinfo->completion_command) {
+ Jim_DecrRefCount(interp, compinfo->completion_command);
+ }
+ compinfo->completion_command = completionCommandObj;
+#endif
+}
- Jim_DeleteAssocData(interp, completion_callback_assoc_key);
-
- if (commandObj) {
- struct JimCompletionInfo *compinfo = Jim_Alloc(sizeof(*compinfo));
- compinfo->interp = interp;
- compinfo->command = commandObj;
+/**
+ * Sets a hints command to be used with Jim_HistoryGetline()
+ * If commandObj is NULL, deletes any existing hints command.
+ */
+void Jim_HistorySetHints(Jim_Interp *interp, Jim_Obj *hintsCommandObj)
+{
+#ifdef USE_LINENOISE
+ struct JimCompletionInfo *compinfo = JimGetCompletionInfo(interp);
- Jim_SetAssocData(interp, completion_callback_assoc_key, JimHistoryFreeCompletion, compinfo);
+ if (hintsCommandObj) {
+ /* Increment now in case the existing object is the same */
+ Jim_IncrRefCount(hintsCommandObj);
+ }
+ if (compinfo->hints_command) {
+ Jim_DecrRefCount(interp, compinfo->hints_command);
}
+ compinfo->hints_command = hintsCommandObj;
#endif
}
@@ -201,6 +284,7 @@ int Jim_InteractivePrompt(Jim_Interp *interp)
}
Jim_HistorySetCompletion(interp, Jim_NewStringObj(interp, "tcl::autocomplete", -1));
+ Jim_HistorySetHints(interp, Jim_NewStringObj(interp, "tcl::stdhint", -1));
#endif
printf("Welcome to Jim version %d.%d\n",
diff --git a/jim.h b/jim.h
index 1c4b5c8..98d21e2 100644
--- a/jim.h
+++ b/jim.h
@@ -951,7 +951,8 @@ JIM_EXPORT int Jim_InteractivePrompt (Jim_Interp *interp);
JIM_EXPORT void Jim_HistoryLoad(const char *filename);
JIM_EXPORT void Jim_HistorySave(const char *filename);
JIM_EXPORT char *Jim_HistoryGetline(Jim_Interp *interp, const char *prompt);
-JIM_EXPORT void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *commandObj);
+JIM_EXPORT void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *completionCommandObj);
+JIM_EXPORT void Jim_HistorySetHints(Jim_Interp *interp, Jim_Obj *hintsCommandObj);
JIM_EXPORT void Jim_HistoryAdd(const char *line);
JIM_EXPORT void Jim_HistoryShow(void);
JIM_EXPORT void Jim_HistorySetMaxLen(int length);
diff --git a/tests/history.test b/tests/history.test
index 70b90bf..9abf0ef 100644
--- a/tests/history.test
+++ b/tests/history.test
@@ -9,7 +9,7 @@ Use "history -help ?command?" for help}
test history-1.2 {history -help} -body {
history -help
-} -result {Usage: "history command ... ", where command is one of: add, completion, getline, keep, load, save, show}
+} -result {Usage: "history command ... ", where command is one of: add, completion, getline, hints, keep, load, save, show}
test history-1.2 {history add} {
history add line1
@@ -26,7 +26,7 @@ test history-1.3 {history load} {
test history-1.4 {history completion usage} -body {
history completion
-} -returnCodes error -result {wrong # args: should be "history completion command"}
+} -returnCodes error -match glob -result {wrong # args: should be "history completion *"}
test history-1.5 {history completion} {
history completion command
@@ -36,6 +36,14 @@ test history-1.6 {history completion} {
history completion {}
} {}
+test history-1.7 {history hints} {
+ history hints command
+} {}
+
+test history-1.8 {history hints} {
+ history hints {}
+} {}
+
catch {
file delete $name
}