Com: iocsh: Tab completion

Add tab completion for "help ...", record names, and "pdbbase"
This commit is contained in:
Michael Davidsaver
2022-06-20 20:33:56 -07:00
parent 60128ee924
commit d9ca8a70f0
10 changed files with 284 additions and 32 deletions
@@ -28,5 +28,6 @@ dbCore_SRCS += dbYacc.c
dbCore_SRCS += dbPvdLib.c
dbCore_SRCS += dbStaticRun.c
dbCore_SRCS += dbStaticIocRegister.c
dbCore_SRCS += dbCompleteRecord.cpp
CLEANS += dbLex.c dbYacc.c
@@ -0,0 +1,161 @@
/*************************************************************************\
* Copyright (c) 2022 Michael Davidsaver
* SPDX-License-Identifier: EPICS
* EPICS Base is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <algorithm>
#include <limits>
#include <string>
#include <set>
#include <string.h>
#include <epicsStdio.h>
#include <dbAccess.h>
#include "dbStaticPvt.h"
namespace {
// immutable C string slice. (avoid allocating many temporary std::string)
class CStr {
const char* p;
size_t l;
public:
CStr() :p(NULL), l(0u) {}
CStr(const CStr& o) :p(o.p), l(o.l) {}
explicit CStr(const char* p) :p(p), l(p ? strlen(p) : 0u) {}
CStr(const char* p, size_t n) :p(p), l(n) {}
CStr& operator=(const CStr& o) {
p = o.p;
l = o.l;
return *this;
}
bool operator==(const CStr& o) const {
return l==o.l && (p==o.p || memcmp(p, o.p, l)==0);
}
bool operator!=(const CStr& o) const {
return !(*this==o);
}
bool operator<(const CStr& o) const {
size_t pl = std::min(l, o.l);
int cmp = memcmp(p, o.p, pl);
return cmp<0 || (cmp==0 && l < o.l);
}
bool empty() const { return !l; }
size_t size() const { return l; }
bool prefixOf(const CStr& full) const {
return full.l >= l && memcmp(full.p, p, l)==0;
}
CStr commonPrefix(const CStr& o, size_t startFrom=0u) const {
size_t n, N;
for(n=startFrom, N=std::min(l, o.l); n<N; n++) {
if(p[n]!=o.p[n])
break;
}
return CStr(p, n);
}
void chop_at_first_of(const char* sep, size_t startFrom=0u) {
size_t n;
for(n=startFrom; n<l; n++) {
char c = p[n];
for(const char *s = sep; *s; s++) {
if(c == *s) {
l = n+1u; // include trailing separator
return;
}
}
}
}
char* dup() const {
char* ret = (char*)malloc(l+1u);
if(ret) {
memcpy(ret, p, l);
ret[l] = '\0';
}
return ret;
}
};
} // namespace
char** dbCompleteRecord(const char *cword)
{
const CStr word(cword);
DBENTRY ent;
dbInitEntry(pdbbase, &ent);
try{
// iterating all record twice...
CStr prefix;
bool first = true;
// find longest prefix match
for(long status = dbFirstRecordType(&ent); !status; status = dbNextRecordType(&ent)) {
for(status = dbFirstRecord(&ent); !status; status = dbNextRecord(&ent)) {
const CStr name(ent.precnode->recordname);
if(!word.prefixOf(name))
continue;
if(first) { // first match
prefix = name;
first = false;
} else {
prefix = prefix.commonPrefix(name, word.size());
}
}
}
// with prefix size known, iterate again to find suggestions
typedef std::set<CStr> suggestions_t;
suggestions_t suggestions;
for(long status = dbFirstRecordType(&ent); !status; status = dbNextRecordType(&ent)) {
for(status = dbFirstRecord(&ent); !status; status = dbNextRecord(&ent)) {
CStr name(ent.precnode->recordname);
if(!prefix.prefixOf(name))
continue;
name.chop_at_first_of(":<>{}-", prefix.size());
suggestions.insert(name);
}
}
dbFinishEntry(&ent);
char** ret = NULL;
if(!prefix.empty() || !suggestions.empty()) {
ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size()));
ret[0] = prefix.dup();
size_t n=1u;
for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end());
it!=end; ++it)
{
ret[n++] = it->dup();
}
ret[n] = NULL;
}
return ret;
} catch(std::exception& e){
fprintf(stderr, "dbCompleteRecord error: %s\n", e.what());
dbFinishEntry(&ent);
return NULL;
}
}
@@ -115,6 +115,9 @@ PVDENTRY *dbPvdAdd(DBBASE *pdbbase,dbRecordType *precordType,dbRecordNode *precn
void dbPvdDelete(DBBASE *pdbbase,dbRecordNode *precnode);
void dbPvdFreeMem(DBBASE *pdbbase);
DBCORE_API
char** dbCompleteRecord(const char *word);
#ifdef __cplusplus
}
#endif