feat(libcmd): add command processing framework with built-in linenoise
- 纯 C 接口:命令注册/查找/补全/帮助 - CMD_REGISTER / CMD_REGISTER_C 宏(__attribute__((constructor)) 自动注册) - 内置 linenoise 行编辑(termios raw mode + 历史 + Tab 补全) - x86 编译通过,产物 libcmd.a
This commit is contained in:
parent
d9546b7c2a
commit
dfb0a46146
|
|
@ -0,0 +1,26 @@
|
|||
# API-libcmd — 命令处理框架
|
||||
|
||||
> 命令注册/分发/补全,内置 linenoise 行编辑,供 libdatacenter 等模块调试使用。
|
||||
|
||||
## 接口
|
||||
|
||||
| 函数/宏 | 说明 |
|
||||
|---------|------|
|
||||
| `cmd_manager_add_command(name, func, desc, complete)` | 注册命令 |
|
||||
| `cmd_manager_get_commands(out_count)` | 获取所有已注册命令列表 |
|
||||
| `cmd_help(argc, argv)` | 打印帮助信息 |
|
||||
| `cmd_find(name)` | 按名称查找命令 |
|
||||
| `cmd_complete(buf, completions, ncomp)` | Tab 自动补全回调 |
|
||||
| `cmd_sub_complete(buf, completions, ncomp, subs, sub_count)` | 子命令补全 |
|
||||
| `linenoise(prompt)` | 行编辑+历史交互式输入 |
|
||||
| `linenoiseFree(ptr)` | 释放 linenoise 返回的内存 |
|
||||
| `linenoiseHistoryAdd(line)` | 添加历史记录 |
|
||||
| `linenoiseHistoryFree()` | 释放历史 |
|
||||
| `lineniseSetCompletionCallback(cb)` | 设置自动补全回调 |
|
||||
| `CMD_REGISTER(name, func, desc)` | 注册命令宏(无补全) |
|
||||
| `CMD_REGISTER_C(name, func, desc, complete)` | 注册命令宏(带补全) |
|
||||
|
||||
## 依赖
|
||||
|
||||
- `myBase.h`
|
||||
- 无外部依赖(linenoise 内置实现)
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @file myCmd.h
|
||||
* @brief 命令处理框架 — 纯 C 接口
|
||||
* @details 命令注册/分发/补全,内置 linenoise 行编辑。
|
||||
* 通过 `__attribute__((constructor))` 实现模块自动注册。
|
||||
*/
|
||||
#ifndef _MY_CMD_H_
|
||||
#define _MY_CMD_H_
|
||||
|
||||
#include "myBase.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
void (*func)(int argc, char *argv[]);
|
||||
const char *desc;
|
||||
void (*complete)(const char *buf, char ***completions, int *ncomp);
|
||||
} stru_cmd;
|
||||
|
||||
/** 注册命令 */
|
||||
void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]),
|
||||
const char *desc, void (*complete)(const char *, char ***, int *));
|
||||
|
||||
/** 获取所有已注册命令列表 */
|
||||
stru_cmd *cmd_manager_get_commands(unsigned int *out_count);
|
||||
|
||||
/** 打印帮助信息 */
|
||||
void cmd_help(int argc, char *argv[]);
|
||||
|
||||
/** 按名称查找命令 */
|
||||
stru_cmd *cmd_find(const char *name);
|
||||
|
||||
/** Tab 自动补全回调 */
|
||||
void cmd_complete(const char *buf, char ***completions, int *ncomp);
|
||||
|
||||
/** 子命令补全 */
|
||||
void cmd_sub_complete(const char *buf, char ***completions, int *ncomp,
|
||||
const char **subs, int sub_count);
|
||||
|
||||
/** 行编辑交互式输入 */
|
||||
char *linenoise(const char *prompt);
|
||||
|
||||
/** 释放 linenoise 返回的内存 */
|
||||
void linenoiseFree(void *ptr);
|
||||
|
||||
/** 添加历史记录 */
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
|
||||
/** 释放所有历史 */
|
||||
void linenoiseHistoryFree(void);
|
||||
|
||||
/** 设置自动补全回调 */
|
||||
void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *));
|
||||
|
||||
/** 注册命令宏(无补全) */
|
||||
#define CMD_REGISTER(cmd_name, func_ptr, cmd_desc) \
|
||||
__attribute__((constructor)) static void register_##func_ptr(void) \
|
||||
{ \
|
||||
cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, NULL); \
|
||||
}
|
||||
|
||||
/** 注册命令宏(带补全) */
|
||||
#define CMD_REGISTER_C(cmd_name, func_ptr, cmd_desc, complete) \
|
||||
__attribute__((constructor)) static void register_##func_ptr(void) \
|
||||
{ \
|
||||
cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, complete); \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
include ./../../../linux.mk
|
||||
L := $(notdir $(realpath $(CURDIR)/..))
|
||||
M := libcmd
|
||||
O := $(LIB_REL)/$(M).a
|
||||
S := $(SRC_ROOT_DIR)/$(L)/libcmd/src
|
||||
I := -I$(SRC_ROOT_DIR)/$(L)/libcmd/inc
|
||||
B := $(CURDIR)/$(M)/obj
|
||||
SRCS := $(wildcard $(S)/*.cpp)
|
||||
OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(SRCS))
|
||||
F := $(CXX_FLAGS) $(I)
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
@mkdir -p $(ROOT_DIR)/release/inc
|
||||
@cp -f $(S:src=inc)/*.h $(ROOT_DIR)/release/inc/ 2>/dev/null; true
|
||||
@$(MAKE) $(O)
|
||||
|
||||
$(O): $(OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AR) rcs $@ $^
|
||||
@echo "[$(M)] built"
|
||||
|
||||
$(B)/%.o: $(S)/%.cpp
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(F) -c $< -o $@
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(B) $(O)
|
||||
|
||||
.PHONY: rebuild
|
||||
rebuild: clean all
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @file myCmd.h
|
||||
* @brief 命令处理框架 — 纯 C 接口
|
||||
* @details 命令注册/分发/补全,内置 linenoise 行编辑。
|
||||
* 通过 `__attribute__((constructor))` 实现模块自动注册。
|
||||
*/
|
||||
#ifndef _MY_CMD_H_
|
||||
#define _MY_CMD_H_
|
||||
|
||||
#include "myBase.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *name;
|
||||
void (*func)(int argc, char *argv[]);
|
||||
const char *desc;
|
||||
void (*complete)(const char *buf, char ***completions, int *ncomp);
|
||||
} stru_cmd;
|
||||
|
||||
/** 注册命令 */
|
||||
void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]),
|
||||
const char *desc, void (*complete)(const char *, char ***, int *));
|
||||
|
||||
/** 获取所有已注册命令列表 */
|
||||
stru_cmd *cmd_manager_get_commands(unsigned int *out_count);
|
||||
|
||||
/** 打印帮助信息 */
|
||||
void cmd_help(int argc, char *argv[]);
|
||||
|
||||
/** 按名称查找命令 */
|
||||
stru_cmd *cmd_find(const char *name);
|
||||
|
||||
/** Tab 自动补全回调 */
|
||||
void cmd_complete(const char *buf, char ***completions, int *ncomp);
|
||||
|
||||
/** 子命令补全 */
|
||||
void cmd_sub_complete(const char *buf, char ***completions, int *ncomp,
|
||||
const char **subs, int sub_count);
|
||||
|
||||
/** 行编辑交互式输入 */
|
||||
char *linenoise(const char *prompt);
|
||||
|
||||
/** 释放 linenoise 返回的内存 */
|
||||
void linenoiseFree(void *ptr);
|
||||
|
||||
/** 添加历史记录 */
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
|
||||
/** 释放所有历史 */
|
||||
void linenoiseHistoryFree(void);
|
||||
|
||||
/** 设置自动补全回调 */
|
||||
void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *));
|
||||
|
||||
/** 注册命令宏(无补全) */
|
||||
#define CMD_REGISTER(cmd_name, func_ptr, cmd_desc) \
|
||||
__attribute__((constructor)) static void register_##func_ptr(void) \
|
||||
{ \
|
||||
cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, NULL); \
|
||||
}
|
||||
|
||||
/** 注册命令宏(带补全) */
|
||||
#define CMD_REGISTER_C(cmd_name, func_ptr, cmd_desc, complete) \
|
||||
__attribute__((constructor)) static void register_##func_ptr(void) \
|
||||
{ \
|
||||
cmd_manager_add_command(cmd_name, func_ptr, cmd_desc, complete); \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,482 @@
|
|||
/**
|
||||
* @file myCmd.cpp
|
||||
* @brief 命令处理框架实现 — 命令注册/分发 + 内置 linenoise 行编辑
|
||||
*/
|
||||
#include "myCmd.h"
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
/* ===== 命令注册表 ===== */
|
||||
|
||||
static std::vector<stru_cmd> &get_cmd_list(void)
|
||||
{
|
||||
static std::vector<stru_cmd> s_cmd_list;
|
||||
|
||||
return s_cmd_list;
|
||||
}
|
||||
|
||||
void cmd_manager_add_command(const char *name, void (*func)(int argc, char *argv[]),
|
||||
const char *desc, void (*complete)(const char *, char ***, int *))
|
||||
{
|
||||
if (!name || !func || !desc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto &list = get_cmd_list();
|
||||
|
||||
list.push_back({name, func, desc, complete});
|
||||
}
|
||||
|
||||
stru_cmd *cmd_manager_get_commands(unsigned int *out_count)
|
||||
{
|
||||
auto &list = get_cmd_list();
|
||||
|
||||
if (out_count)
|
||||
{
|
||||
*out_count = (unsigned int)list.size();
|
||||
}
|
||||
|
||||
if (list.empty())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return list.data();
|
||||
}
|
||||
|
||||
stru_cmd *cmd_find(const char *name)
|
||||
{
|
||||
if (!name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto &list = get_cmd_list();
|
||||
|
||||
for (auto &cmd : list)
|
||||
{
|
||||
if (0 == strcmp(cmd.name, name))
|
||||
{
|
||||
return &cmd;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cmd_complete(const char *buf, char ***completions, int *ncomp)
|
||||
{
|
||||
auto &list = get_cmd_list();
|
||||
|
||||
/* 判断是否有空格 —— 有空格则进入子命令补全 */
|
||||
const char *space = strchr(buf, ' ');
|
||||
|
||||
if (space)
|
||||
{
|
||||
size_t cmd_len = (size_t)(space - buf);
|
||||
|
||||
for (auto &cmd : list)
|
||||
{
|
||||
if (0 == strncmp(cmd.name, buf, cmd_len) && '\0' == cmd.name[cmd_len])
|
||||
{
|
||||
if (cmd.complete)
|
||||
{
|
||||
cmd.complete(buf, completions, ncomp);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*ncomp = 0;
|
||||
*completions = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* 无空格:命令名前缀匹配 */
|
||||
int cnt = 0;
|
||||
char **c = NULL;
|
||||
|
||||
for (auto &cmd : list)
|
||||
{
|
||||
if (!strncmp(cmd.name, buf, strlen(buf)))
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
c = (char **)malloc(sizeof(char *) * (list.size() + 1));
|
||||
}
|
||||
|
||||
c[cnt] = strdup(cmd.name);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!c)
|
||||
{
|
||||
*ncomp = 0;
|
||||
*completions = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
*ncomp = cnt;
|
||||
*completions = c;
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_sub_complete(const char *buf, char ***completions, int *ncomp,
|
||||
const char **subs, int sub_count)
|
||||
{
|
||||
const char *prefix = strrchr(buf, ' ');
|
||||
|
||||
if (!prefix)
|
||||
{
|
||||
*ncomp = 0;
|
||||
*completions = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
prefix++;
|
||||
|
||||
int cnt = 0;
|
||||
char **c = NULL;
|
||||
size_t prefix_len = strlen(prefix);
|
||||
|
||||
for (int i = 0; i < sub_count; i++)
|
||||
{
|
||||
if (0 == strncmp(subs[i], prefix, prefix_len))
|
||||
{
|
||||
if (!c)
|
||||
{
|
||||
c = (char **)malloc(sizeof(char *) * (size_t)(sub_count + 1));
|
||||
}
|
||||
|
||||
c[cnt] = strdup(subs[i]);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
*completions = c;
|
||||
*ncomp = cnt;
|
||||
}
|
||||
|
||||
void cmd_help(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
auto &list = get_cmd_list();
|
||||
|
||||
for (auto &cmd : list)
|
||||
{
|
||||
printf("%-24s [%s]\n", cmd.name, cmd.desc);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 内置 linenoise ===== */
|
||||
|
||||
#define SAFE_WRITE(fd, buf, sz) \
|
||||
do \
|
||||
{ \
|
||||
ssize_t _r = write(fd, buf, sz); \
|
||||
(void)_r; \
|
||||
} while (0)
|
||||
|
||||
static char *g_history[100];
|
||||
static int g_history_len = 0;
|
||||
static void (*g_completion_cb)(const char *, char ***, int *) = NULL;
|
||||
|
||||
static struct termios g_orig_termios;
|
||||
static int g_raw_enabled = 0;
|
||||
|
||||
static void disable_raw(void)
|
||||
{
|
||||
if (!g_raw_enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_termios);
|
||||
g_raw_enabled = 0;
|
||||
}
|
||||
|
||||
static void sigint_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
|
||||
if (g_raw_enabled)
|
||||
{
|
||||
disable_raw();
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int enable_raw(void)
|
||||
{
|
||||
signal(SIGINT, sigint_handler);
|
||||
tcgetattr(STDIN_FILENO, &g_orig_termios);
|
||||
atexit(disable_raw);
|
||||
|
||||
struct termios raw = g_orig_termios;
|
||||
|
||||
raw.c_lflag &= ~(ECHO | ICANON);
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
|
||||
int ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
|
||||
if (0 == ret)
|
||||
{
|
||||
g_raw_enabled = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int linenoise_edit(char *buf, int bufsize, const char *prompt)
|
||||
{
|
||||
int pos = 0;
|
||||
int len = 0;
|
||||
|
||||
memset(buf, 0, (size_t)bufsize);
|
||||
SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt));
|
||||
|
||||
int history_idx = g_history_len;
|
||||
|
||||
while (1)
|
||||
{
|
||||
char c;
|
||||
ssize_t n = read(STDIN_FILENO, &c, 1);
|
||||
|
||||
if (n <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (c == '\n' || c == '\r')
|
||||
{
|
||||
buf[len] = '\0';
|
||||
SAFE_WRITE(STDOUT_FILENO, "\n", 1);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
if (c == 127 || c == '\b')
|
||||
{
|
||||
if (len > 0)
|
||||
{
|
||||
len--;
|
||||
pos--;
|
||||
buf[len] = '\0';
|
||||
SAFE_WRITE(STDOUT_FILENO, "\b \b", 3);
|
||||
history_idx = g_history_len;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == 27)
|
||||
{
|
||||
char seq[2];
|
||||
|
||||
if (read(STDIN_FILENO, &seq[0], 1) == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (read(STDIN_FILENO, &seq[1], 1) == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (seq[0] == '[')
|
||||
{
|
||||
if (seq[1] == 'A')
|
||||
{
|
||||
if (g_history_len == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
history_idx--;
|
||||
|
||||
if (history_idx < 0)
|
||||
{
|
||||
history_idx = 0;
|
||||
}
|
||||
|
||||
const char *hist = g_history[history_idx];
|
||||
|
||||
strncpy(buf, hist, (size_t)(bufsize - 1));
|
||||
len = pos = (int)strlen(buf);
|
||||
SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4);
|
||||
SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt));
|
||||
SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len);
|
||||
}
|
||||
else if (seq[1] == 'B')
|
||||
{
|
||||
if (g_history_len == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
history_idx++;
|
||||
|
||||
if (history_idx >= g_history_len)
|
||||
{
|
||||
history_idx = g_history_len;
|
||||
memset(buf, 0, (size_t)bufsize);
|
||||
len = pos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *hist = g_history[history_idx];
|
||||
|
||||
strncpy(buf, hist, (size_t)(bufsize - 1));
|
||||
len = pos = (int)strlen(buf);
|
||||
}
|
||||
|
||||
SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4);
|
||||
SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt));
|
||||
SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == 9)
|
||||
{
|
||||
char **completions = NULL;
|
||||
int ncomp = 0;
|
||||
|
||||
if (g_completion_cb)
|
||||
{
|
||||
g_completion_cb(buf, &completions, &ncomp);
|
||||
|
||||
if (ncomp > 0)
|
||||
{
|
||||
char *last_space = NULL;
|
||||
|
||||
for (int i = pos - 1; i >= 0; i--)
|
||||
{
|
||||
if (' ' == buf[i])
|
||||
{
|
||||
last_space = &buf[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_space)
|
||||
{
|
||||
int keep_len = (int)(last_space - buf + 1);
|
||||
|
||||
strncpy(last_space + 1, completions[0],
|
||||
(size_t)(bufsize - keep_len - 1));
|
||||
buf[bufsize - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(buf, completions[0], (size_t)(bufsize - 1));
|
||||
}
|
||||
|
||||
len = pos = (int)strlen(buf);
|
||||
SAFE_WRITE(STDOUT_FILENO, "\r\033[K", 4);
|
||||
SAFE_WRITE(STDOUT_FILENO, prompt, strlen(prompt));
|
||||
SAFE_WRITE(STDOUT_FILENO, buf, (size_t)len);
|
||||
|
||||
for (int j = 0; j < ncomp; j++)
|
||||
{
|
||||
free(completions[j]);
|
||||
}
|
||||
|
||||
free(completions);
|
||||
}
|
||||
}
|
||||
|
||||
history_idx = g_history_len;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (len < bufsize - 1)
|
||||
{
|
||||
buf[len++] = c;
|
||||
pos++;
|
||||
SAFE_WRITE(STDOUT_FILENO, &c, 1);
|
||||
history_idx = g_history_len;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *linenoise(const char *prompt)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
if (0 != enable_raw())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int n = linenoise_edit(buf, (int)sizeof(buf), prompt);
|
||||
|
||||
disable_raw();
|
||||
|
||||
if (n <= 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *copy = strdup(buf);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void linenoiseFree(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
int linenoiseHistoryAdd(const char *line)
|
||||
{
|
||||
if (g_history_len >= 100)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_history[g_history_len] = strdup(line);
|
||||
g_history_len++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void linenoiseHistoryFree(void)
|
||||
{
|
||||
for (int j = 0; j < g_history_len; j++)
|
||||
{
|
||||
free(g_history[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void lineniseSetCompletionCallback(void (*cb)(const char *, char ***, int *))
|
||||
{
|
||||
g_completion_cb = cb;
|
||||
}
|
||||
|
||||
} /* extern "C" */
|
||||
Loading…
Reference in New Issue