Com: iocsh: add tab completion for command names

This commit is contained in:
Michael Davidsaver
2022-06-19 16:09:06 -07:00
parent 07ffc1ffae
commit b189991f9d
2 changed files with 135 additions and 5 deletions

View File

@@ -16,3 +16,6 @@ Com_SRCS += iocsh.cpp
Com_SRCS += initHooks.c
Com_SRCS += registry.c
Com_SRCS += libComRegister.c
iocsh_CXXFLAGS += -DEPICS_COMMANDLINE_LIBRARY=EPICS_COMMANDLINE_LIBRARY_$(COMMANDLINE_LIBRARY)
iocsh_INCLUDES += $(INCLUDES_$(COMMANDLINE_LIBRARY))

View File

@@ -34,6 +34,27 @@
#include "cantProceed.h"
#include "iocsh.h"
#include "epicsReadlinePvt.h"
#if EPICS_COMMANDLINE_LIBRARY == EPICS_COMMANDLINE_LIBRARY_READLINE
# include <readline/readline.h>
# define USE_READLINE
/* libedit also provides readline.h, but isn't fully compatible with
* GNU readline. It also doesn't specifically identify itself.
*
* libedit pretends to be GNU readline 4.2 (circa 2001), but lacks
* some definitions found in that GNU release, but has others not
* added until later GNU releases.
*/
# if RL_READLINE_VERSION == 0x402 && !defined(RL_STATE_NONE)
/* actual GNU readline 4.2 defines RL_STATE_NONE, but libedit.h
* circa libedit-39 (aka. CVS 1.33) does not. It also does not
* provide rl_basic_quote_characters
*/
static const char *rl_basic_quote_characters;
# endif
#endif
extern "C" {
/*
@@ -157,6 +178,114 @@ const iocshCmdDef * epicsStdCall iocshFindCommand(const char *name)
return (iocshCmdDef *) registryFind(iocshCmdID, name);
}
namespace {
#ifdef USE_READLINE
char* iocsh_complete_command(const char* word, int notfirst)
{
// ick! ... readline is not re-entrant anyway
static const iocshCommand *next;
if(!notfirst) { // aka. first call
next = iocshCommandHead;
}
const size_t wlen = strlen(word);
while(next) {
const iocshCommand *cur = next;
next = next->next;
if(strncmp(word, cur->def.pFuncDef->name, wlen)==0) {
return strdup(cur->def.pFuncDef->name);
}
}
return NULL;
}
char** iocsh_attempt_completion(const char* word, int start, int end)
{
const char *line = rl_line_buffer;
if(!line || !word || start<0 || end <0 || start>end)
return NULL; // paranoia
// skip any leading space
while(isspace(*line)) {
line++;
start--;
end--;
}
if(start==0) { // complete command name
return rl_completion_matches(word, iocsh_complete_command);
} else { // complete some argument
return NULL;
}
}
#endif
struct ReadlineContext {
void *context;
#ifdef USE_READLINE
// readline on BSD/OSX missing 'rl_completion_func_t' typedef, and 'const'
char* prev_rl_readline_name;
char* prev_rl_basic_word_break_characters;
char* prev_rl_completer_word_break_characters;
char* prev_rl_basic_quote_characters;
char* prev_rl_completer_quote_characters;
//rl_completion_func_t* prev_rl_attempted_completion_function;
char** (*prev_rl_attempted_completion_function)(const char* word, int start, int end);
#endif
ReadlineContext()
:context(NULL)
{}
bool setup(FILE *fp) {
context = epicsReadlineBegin(fp);
#ifdef USE_READLINE
if(context) {
prev_rl_readline_name = (char*)rl_readline_name;
prev_rl_basic_word_break_characters = (char*)rl_basic_word_break_characters;
prev_rl_completer_word_break_characters = (char*)rl_completer_word_break_characters;
prev_rl_basic_quote_characters = (char*)rl_basic_quote_characters;
prev_rl_completer_quote_characters = (char*)rl_completer_quote_characters;
prev_rl_attempted_completion_function = rl_attempted_completion_function;
rl_readline_name = (char*)"iocsh";
rl_basic_word_break_characters = (char*)"\t (),";
rl_completer_word_break_characters = (char*)"\t (),";
rl_basic_quote_characters = (char*)"\"";
rl_completer_quote_characters = (char*)"\"";
rl_attempted_completion_function = &iocsh_attempt_completion;
rl_bind_key('\t', rl_complete);
}
#endif
return context;
}
~ReadlineContext() {
if(context) {
#ifdef USE_READLINE
rl_readline_name = prev_rl_readline_name;
rl_basic_word_break_characters = prev_rl_basic_word_break_characters;
rl_completer_word_break_characters = prev_rl_completer_word_break_characters;
rl_basic_quote_characters = prev_rl_basic_quote_characters;
rl_completer_quote_characters = prev_rl_completer_quote_characters;
rl_attempted_completion_function = prev_rl_attempted_completion_function;
// cf. osdReadlineBegin() in gnuReadline.c
rl_bind_key('\t', rl_insert);
#endif
epicsReadlineEnd(context);
}
}
};
} // namespace
/*
* Register the "var" command and any variable(s)
*/
@@ -580,7 +709,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
iocshArgBuf *argBuf = NULL;
int argBufCapacity = 0;
struct iocshCommand *found;
void *readlineContext = NULL;
ReadlineContext readline;
int wasOkToBlock;
static const char * pairs[] = {"", "environ", NULL, NULL};
iocshScope scope;
@@ -617,7 +746,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
/*
* Create a command-line input context
*/
if ((readlineContext = epicsReadlineBegin(fp)) == NULL) {
if (!readline.setup(fp)) {
fprintf(epicsGetStderr(), "Can't allocate command-line object.\n");
if (fp)
fclose(fp);
@@ -711,7 +840,7 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
raw = commandLine;
}
else {
if ((raw = epicsReadline(prompt, readlineContext)) == NULL)
if ((raw = epicsReadline(prompt, readline.context)) == NULL)
break;
}
lineno++;
@@ -1009,8 +1138,6 @@ iocshBody (const char *pathname, const char *commandLine, const char *macros)
free (argv);
free (argBuf);
errlogFlush();
if (readlineContext)
epicsReadlineEnd(readlineContext);
epicsThreadSetOkToBlock(wasOkToBlock);
return ret;
}