diff options
-rw-r--r-- | examples/jtclsh.tcl | 4 | ||||
-rw-r--r-- | initjimsh.tcl | 59 | ||||
-rw-r--r-- | jim-history.c | 13 | ||||
-rw-r--r-- | jim-interactive.c | 124 | ||||
-rw-r--r-- | jim.h | 3 | ||||
-rw-r--r-- | tests/history.test | 12 |
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", @@ -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 } |