typedef struct {
char **matches; // 所有匹配项
int count; // 匹配项总数
char *base_dir; // 当前基础目录
char *original_prefix; // 原始前缀
char *current_text; // 当前输入文本
int last_state; // 上一次使用的state值
int current_level; // 当前目录层级
char *persistent_path; // 持久化路径(同时作为初始化标志)
int is_directory;
int max_level; // 新增:最大层级限制
int initialized;
int flash_mode;
} CompletionState;
static CompletionState comp_state = {
.matches = NULL,
.count = 0,
.base_dir = NULL,
.original_prefix = NULL,
.current_text = NULL,
.last_state = -1,
.current_level = 0,
.persistent_path = NULL,
.is_directory = 0,
.max_level = -1,
.initialized = 0,
.flash_mode = 0
};
// 重置补全状态
void reset_completion_state() {
if (comp_state.matches) {
for (int i = 0; comp_state.matches[i]; i++) {
XFREE(MTYPE_TMP, comp_state.matches[i]);
}
XFREE(MTYPE_TMP, comp_state.matches);
comp_state.matches = NULL;
}
if (comp_state.original_prefix) {
XFREE(MTYPE_TMP, comp_state.original_prefix);
}
if (comp_state.base_dir) {
XFREE(MTYPE_TMP, comp_state.base_dir);
}
if (comp_state.current_text) {
XFREE(MTYPE_TMP, comp_state.current_text);
}
if (comp_state.persistent_path) {
XFREE(MTYPE_TMP, comp_state.persistent_path);
}
comp_state.count = 0;
comp_state.original_prefix = NULL;
comp_state.base_dir = NULL;
comp_state.current_text = NULL;
comp_state.last_state = -1;
comp_state.current_level = 0;
comp_state.persistent_path = NULL;
comp_state.is_directory = 0;
comp_state.max_level = -1;
comp_state.initialized = 0;
comp_state.flash_mode = 0;
}
int compare_paths(const void *a, const void *b) {
const char *sa = *(const char **)a;
const char *sb = *(const char **)b;
// 获取第一个字符
char first_a = sa[0];
char first_b = sb[0];
// 1. 处理小写字母开头的情况
int is_lower_a = islower(first_a);
int is_lower_b = islower(first_b);
if (is_lower_a && !is_lower_b) return -1; // 小写优先
if (!is_lower_a && is_lower_b) return 1; // 小写优先
// 2. 处理大写字母开头的情况
int is_upper_a = isupper(first_a);
int is_upper_b = isupper(first_b);
if (is_upper_a && !is_upper_b) return -1; // 大写优先于非字母
if (!is_upper_a && is_upper_b) return 1; // 大写优先于非字母
// 3. 同类别比较(都是小写或都是大写或都是非字母)
return strcasecmp(sa, sb); // 不区分大小写比较
}
// 构建完整路径(自动加 /)
char *build_completion_path(const char *base, const char *match, int is_dir) {
char *path = (char *)XCALLOC(MTYPE_TMP, strlen(base) + strlen(match) + 2);
if (base && strlen(base) > 0) {
int needs_slash = base[strlen(base) - 1] != '/';
if (needs_slash) {
snprintf(path, strlen(base) + strlen(match) + 2, "%s/%s", base, match);
} else {
snprintf(path, strlen(base) + strlen(match) + 2, "%s%s", base, match);
}
} else {
strcpy(path, match);
}
if (is_dir) {
char *tmp = (char *)XREALLOC(MTYPE_TMP, path, strlen(path) + 2);
strcat(tmp, "/");
XFREE(MTYPE_TMP, path);
path = tmp;
}
return path;
}
// 优化后的路径解析函数
void parse_input_text(const char *text, char **base_dir, char **prefix, int *level) {
*base_dir = NULL;
*prefix = NULL;
// 处理 flash:/ 虚拟路径
if (strncmp(text, "flash:/", 7) == 0) {
const char *path_part = text + 7;
const char *last_slash = strrchr(path_part, '/');
if (last_slash) {
// 提取目录部分
size_t base_len = last_slash - path_part + 1;
*base_dir = (char *)XCALLOC(MTYPE_TMP, base_len + 1);
strncpy(*base_dir, path_part, base_len);
(*base_dir)[base_len] = '\0';
// 提取前缀部分
*prefix = XSTRDUP(MTYPE_TMP, last_slash + 1);
*level = comp_state.current_level + 1;
} else {
*base_dir = XSTRDUP(MTYPE_TMP, "");
*prefix = XSTRDUP(MTYPE_TMP, path_part);
*level = 0;
}
return;
}
// 普通路径解析
const char *slash = strrchr(text, '/');
if (slash) {
size_t base_len = slash - text + 1;
*base_dir = (char *)XCALLOC(MTYPE_TMP, base_len + 1);
strncpy(*base_dir, text, base_len);
(*base_dir)[base_len] = '\0';
*prefix = XSTRDUP(MTYPE_TMP, slash + 1);
*level = comp_state.current_level + 1;
} else {
*base_dir = XSTRDUP(MTYPE_TMP, "");
*prefix = XSTRDUP(MTYPE_TMP, text);
*level = 0;
}
}
// 上下文一致性判断(优化后)
int is_same_completion_context(const char *text) {
if (!comp_state.persistent_path || comp_state.max_level == -1) {
return 0;
}
char *temp_base = NULL;
char *temp_prefix = NULL;
int current_level = 0;
parse_input_text(text, &temp_base, &temp_prefix, ¤t_level);
printf("is_same_completion_context: text = %s, temp_base = %s, current_level = %d\n", text, temp_base, current_level);
int same_path = strncmp(text, comp_state.persistent_path, strlen(comp_state.persistent_path)) == 0;
// 只有在当前路径是持久化路径的子目录或匹配时才继续
int within_level = (current_level <= comp_state.max_level);
XFREE(MTYPE_TMP, temp_base);
XFREE(MTYPE_TMP, temp_prefix);
printf("is_same_completion_context: same_path=%d, within_level=%d\n", same_path, within_level);
return same_path && within_level;
}
// 生成当前目录下的路径匹配项
char **generate_current_dir_paths(const char *base_dir, const char *prefix, int add_empty) {
const char *scan_dir = base_dir;
if (!scan_dir || !*scan_dir) {
scan_dir = ".";
}
printf("[generate] Scanning directory: '%s' with prefix: '%s'\n", scan_dir, prefix ? prefix : "(none)");
DIR *dir = opendir(scan_dir);
if (!dir) {
printf("\n opendir scan_dir = %s failed.\n", scan_dir);
if (add_empty) {
char **matches = (char **)XCALLOC(MTYPE_TMP, sizeof(char *));
matches[0] = XSTRDUP(MTYPE_TMP, "");
return matches;
}
return NULL;
}
int capacity = 32;
int count = 0;
char **matches = (char **)XCALLOC(MTYPE_TMP, capacity * sizeof(char *));
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
const char *name = entry->d_name;
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
// 跳过隐藏文件
if (name[0] == '.') continue;
// 跳过软链接(直接判断 d_type)
if (entry->d_type == DT_LNK) {
printf("跳过软链接: %s\n", name);
continue;
}
if (prefix && *prefix && strncmp(name, prefix, strlen(prefix)) != 0) continue;
int is_dir = (entry->d_type == DT_DIR);
if (entry->d_type == DT_UNKNOWN) {
char full_path[PATH_MAX];
snprintf(full_path, sizeof(full_path), "%s/%s", scan_dir, name);
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
is_dir = 1;
}
}
char *new_name;
if (is_dir) {
new_name = (char *)XCALLOC(MTYPE_TMP, strlen(name) + 2);
snprintf(new_name, strlen(name) + 2, "%s/", name);
} else {
new_name = XSTRDUP(MTYPE_TMP, name);
}
if (count >= capacity) {
capacity *= 2;
matches = (char **)XREALLOC(MTYPE_TMP, matches, capacity * sizeof(char *));
}
matches[count++] = new_name;
}
closedir(dir);
if (count > 0) {
qsort(matches, count, sizeof(char *), compare_paths);
}
matches = (char **)XREALLOC(MTYPE_TMP, matches, (count + 2) * sizeof(char *));
matches[count] = NULL;
if (!add_empty) {
matches[count] = XSTRDUP(MTYPE_TMP, "");
matches[count + 1] = NULL;
}
printf("\n[generate] Found %d matches.\n", count);
if (matches && matches[0]) {
printf("\ngenerate_current_dir_paths: matches = ");
for (int i = 0; matches[i]; i++) {
printf("%s, ", matches[i]);
}
printf("\n");
}
return matches;
}
// 主补全函数(关键优化点)
char *filename_completion_function(const char *text, int state) {
printf("\n[COMP] Entering: text='%s', state=%d, last_state=%d\n",
text, state, comp_state.last_state);
// 拦截空输入
if (text == NULL || *text == '\0') {
printf("用户未输入任何文本,不提供补全\n");
return NULL;
}
char current_dir[PATH_MAX];
char abs_path[PATH_MAX];
int reset_completion = 0;
int is_flash_mode = (strncmp(text, "flash:/", 7) == 0);
int is_f_prefix = (strncmp(text, "f", 1) == 0 && strncmp(text, "flash:/", 7) != 0);
// 核心优化:添加前缀匹配检查
int is_prefix_of_existing = 0;
if (comp_state.matches && comp_state.count > 0) {
for (int i = 0; i < comp_state.count; i++) {
if (comp_state.count == 1) {
is_prefix_of_existing = 0;
}
if (is_flash_mode) {
if (strncmp(text + 7, comp_state.matches[i], strlen(text + 7)) == 0) {
is_prefix_of_existing = 1;
break;
}
} else if (strncmp(text, comp_state.matches[i], strlen(text)) == 0) {
is_prefix_of_existing = 1;
break;
}
}
}
printf(" Text is prefix of existing matches, is_prefix_of_existing = %d\n", is_prefix_of_existing);
// 仅在需要时初始化新补全
if (state == 0) {
printf(" Starting new completion check\n");
// 获取当前工作目录
getcwd(current_dir, sizeof(current_dir));
// 关键优化:特殊处理flash路径拼接
if (is_flash_mode) {
// 对于flash模式,直接使用虚拟路径
snprintf(abs_path, sizeof(abs_path), "/mnt/switch/%s", text + 7);
} else {
// 普通路径拼接
snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, text);
}
// 核心优化:检查是否是现有匹配项的前缀
if (is_prefix_of_existing) {
printf(" Text is prefix of existing matches, no reset needed\n");
reset_completion = 0;
} else {
// 原始重置逻辑
if (access(abs_path, F_OK) != 0) {
reset_completion = 1;
}
}
printf("\n111filename_completion_function: reset_completion = %d, abs_path = '%s'\n", reset_completion, abs_path);
char *tmp_base = NULL;
char *tmp_prefix = NULL;
int tmp_level = 0;
parse_input_text(text, &tmp_base, &tmp_prefix, &tmp_level);
printf("\n222filename_completion_function: text = '%s', tmp_base = '%s', tmp_prefix = '%s'\n",
text, tmp_base, tmp_prefix);
int same_reset = 0;
struct stat st_tmp;
char tmp_path[PATH_MAX];
if (comp_state.matches && comp_state.matches[0]) {
for (int i = 0; comp_state.matches[i]; i++) {
printf("\n333filename_completion_function: comp_state.matches[%d] = '%s'\n", i, comp_state.matches[i]);
if (strncmp(comp_state.matches[i], tmp_prefix, strlen(tmp_prefix)) == 0) {
char *old_base = comp_state.base_dir ? comp_state.base_dir : "";
snprintf(tmp_path, sizeof(tmp_path), "%s/%s%s", current_dir, old_base, comp_state.matches[i]);
printf("\n444filename_completion_function: tmp_path = '%s', current_dir = '%s', comp_state.matches[%d] = '%s'\n",
tmp_path, current_dir, i, comp_state.matches[i]);
if (stat(tmp_path, &st_tmp) == 0 && S_ISDIR(st_tmp.st_mode)) {
same_reset = 1;
printf("\nfilename_completion_function: same_reset = %d\n", same_reset);
break;
}
}
}
}
printf("\n555filename_completion_function: current_dir = '%s', text = '%s', abs_path = '%s', reset_completion = %d, same_reset = %d\n",
current_dir, text, abs_path, reset_completion, same_reset);
printf("\n666filename_completion_function: comp_state.persistent_path = '%s'\n", comp_state.persistent_path);
int same_context = is_same_completion_context(text);
printf(" Same context: %d\n", same_context);
// 核心优化:添加匹配数量和类型判断
int should_reset = 0;
if (is_prefix_of_existing) {
printf(" Using existing matches for prefix\n");
should_reset = 0;
// 新增:检查单目录匹配情况
if (comp_state.matches && comp_state.count == 1) {
char *single_match = comp_state.matches[0];
char full_path[PATH_MAX];
// 构建完整路径
if (comp_state.base_dir) {
snprintf(full_path, sizeof(full_path), "%s/%s", comp_state.base_dir, single_match);
} else {
snprintf(full_path, sizeof(full_path), "%s", single_match);
}
// 检查文件类型
struct stat st;
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
printf(" Single directory match, forcing reset\n");
should_reset = 1;
}
}
} else if ((!same_context) || (reset_completion && same_reset) ||
(!comp_state.persistent_path || comp_state.persistent_path[0] == '\0')) {
should_reset = 1;
}
// 统一检查单目录匹配情况
if (!should_reset && comp_state.matches && comp_state.count == 1) {
char *single_match = comp_state.matches[0];
char full_path[PATH_MAX];
// 构建完整路径
if (comp_state.base_dir) {
snprintf(full_path, sizeof(full_path), "%s/%s", comp_state.base_dir, single_match);
} else {
snprintf(full_path, sizeof(full_path), "%s", single_match);
}
// 检查文件类型
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
printf(" Single directory match, forcing reset\n");
should_reset = 1;
} else {
printf(" Single file match, no reset needed\n");
}
}
}
if (should_reset) {
reset_completion_state();
printf("\n -----------------common reset---------------\n");
char *current_base = NULL;
char *current_prefix = NULL;
int current_level = 0;
parse_input_text(text, ¤t_base, ¤t_prefix, ¤t_level);
printf("\n777filename_completion_function: current_base = '%s', current_prefix = '%s', current_level = %d\n",
current_base, current_prefix, current_level);
// 关键优化:特殊处理flash模式的基础路径
if (is_flash_mode) {
comp_state.base_dir = XSTRDUP(MTYPE_TMP, "flash:/");
// 关键修复:确保正确设置当前文本
comp_state.current_text = XSTRDUP(MTYPE_TMP, text);
} else {
comp_state.base_dir = current_base;
comp_state.current_text = XSTRDUP(MTYPE_TMP, text);
}
comp_state.current_text = XSTRDUP(MTYPE_TMP, text);
comp_state.original_prefix = XSTRDUP(MTYPE_TMP, current_prefix);
comp_state.current_level = current_level;
comp_state.max_level = current_level + 3;
// 检查是否用户已经明确进入了子目录
int ends_with_slash = text[strlen(text) - 1] == '/';
if (ends_with_slash) {
comp_state.persistent_path = XSTRDUP(MTYPE_TMP, text);
comp_state.max_level = current_level + 3;
} else {
comp_state.persistent_path = XSTRDUP(MTYPE_TMP, current_base);
comp_state.max_level = current_level;
}
// 生成当前目录的补全项
if (is_flash_mode) {
comp_state.matches = generate_current_dir_paths("/mnt/switch/", text + 7, 1);
comp_state.flash_mode = 1;
} else if (is_f_prefix) {
comp_state.matches = generate_current_dir_paths(current_base, current_prefix, 1);
if (current_level == 0) {
if (comp_state.matches == NULL || comp_state.matches[0] == NULL) {
comp_state.base_dir = XSTRDUP(MTYPE_TMP, "flash:/");
comp_state.matches = generate_current_dir_paths("/mnt/switch/", text, 1);
comp_state.flash_mode = 1;
} else {
int count = 0;
while (comp_state.matches[count]) count++;
int flash_already_in_list = 0;
for (int i = 0; comp_state.matches[i]; i++) {
if (strncmp(comp_state.matches[i], "flash:/", 7) == 0) {
flash_already_in_list = 1;
break;
}
}
if (!flash_already_in_list && count < 1024 - 1) {
char **temp = (char **)XREALLOC(MTYPE_TMP, comp_state.matches, (count + 2) * sizeof(char*));
comp_state.matches = temp;
comp_state.matches[count] = XSTRDUP(MTYPE_TMP, "flash:/");
comp_state.matches[count + 1] = NULL;
if (comp_state.matches && comp_state.matches[0]) {
printf("\n flash: comp_state.matches = ");
for (int i = 0; comp_state.matches[i]; i++) {
printf("%s, ", comp_state.matches[i]);
}
printf("\n");
}
}
}
}
} else {
comp_state.matches = generate_current_dir_paths(current_base, current_prefix, current_level == 0);
}
comp_state.count = 0;
if (comp_state.matches) {
while (comp_state.matches[comp_state.count]) {
comp_state.count++;
}
printf(" Generated %d matches\n", comp_state.count);
} else {
comp_state.count = 0;
}
comp_state.last_state = -1; // 重置索引
}
}
printf("\n888filename_completion_function: comp_state.base_dir = '%s', comp_state.current_text = '%s', comp_state.original_prefix = '%s', comp_state.persistent_path = '%s'\n",
comp_state.base_dir, comp_state.current_text, comp_state.original_prefix, comp_state.persistent_path);
// 没有匹配项
if (!comp_state.matches || comp_state.count == 0) {
printf("[COMP] No matches found\n");
return NULL;
}
// 获取下一个匹配项
int next_index = (comp_state.last_state + 1) % comp_state.count;
comp_state.last_state = next_index;
char *match = comp_state.matches[next_index];
// 构建完整路径
char *full_path = NULL;
if (strncmp(match, "flash:/", 7) == 0) {
full_path = XSTRDUP(MTYPE_TMP, match);
} else if (comp_state.flash_mode) {
// 关键优化:正确处理flash模式下的路径拼接
int base_len = strlen(comp_state.base_dir);
int needs_slash = base_len > 0 && comp_state.base_dir[base_len - 1] != '/';
if (needs_slash) {
full_path = XCALLOC(MTYPE_TMP, strlen(comp_state.base_dir) + strlen(match) + 2);
snprintf(full_path, strlen(comp_state.base_dir) + strlen(match) + 2, "%s/%s", comp_state.base_dir, match);
} else {
full_path = XCALLOC(MTYPE_TMP, strlen(comp_state.base_dir) + strlen(match) + 1);
snprintf(full_path, strlen(comp_state.base_dir) + strlen(match) + 1, "%s%s", comp_state.base_dir, match);
}
} else {
if (comp_state.base_dir && strlen(comp_state.base_dir) > 0) {
int base_len = strlen(comp_state.base_dir);
int needs_slash = base_len > 0 && comp_state.base_dir[base_len - 1] != '/';
full_path = XCALLOC(MTYPE_TMP, strlen(match) + base_len + 2);
if (needs_slash) {
snprintf(full_path, strlen(match) + base_len + 2, "%s/%s", comp_state.base_dir, match);
} else {
snprintf(full_path, strlen(match) + base_len + 2, "%s%s", comp_state.base_dir, match);
}
} else {
full_path = XSTRDUP(MTYPE_TMP, match);
}
}
printf("[COMP] Returning: '%s'\n", full_path);
return full_path;
}
在上边这个代码的基础上优化,主要是优化filename_completion_function函数,现在的要求是:
1、加入我当前路径下有个etc目录,然后etc目录下有个ssh目录,我现在进入到etc目录中,用s去补全,那只能唯一补全成ssh/,然后后续再tab则是遍历ssh目录下的文件和子目录了
2、如果当前只有一个匹配到输入的前缀的文件,那就直接补全,即使后续tab也是返回这个文件名
3、如果当前只有一个匹配到输入的前缀的目录,那就直接补全,然欧后后边连续tab则是自动遍历这个目录下的文件和子目录
4、不要使用state判断状态,这个值一直是0
5、可以考虑,当匹配项只有一个的时候,那count=1,可以使用count=1联合判断
6、代码中的flash:/虚拟路径的问题,flash:/是映射实际的根路径/mnt/switch/,加入我输入的是f,然后tab补全,当前路径下有fa目录,则是循环显示fa/ flash:/,当显示到flash:/是,我再后边追加字符,则是想补全这个匹配字符的文件或目录,如果又在这个匹配的目录后追加字符,也要成功匹配;而且当前代码中还有另外的问题,判断用户输入的f前缀不严谨,如果用户输入的是fa前缀,那这个代码还是会强行匹配到flash:/
7、最初的入参text是每次tab都会改变的
最新发布