S_ISDIR(stat.sb_mode)函数功能

一般在之前都会先调用函数stat( FileName, &fp),意味着将FileName这个文件的信息保存到了地址fp中。此时fp.st_mode就是文件FileName的模式,所以S_ISDIR(fp.st_mode)的函数功能是判断fp所指向文件(也就是FileName)是否为目录(dir)类型。

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; // 新增:最大层级限制 } 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 }; // 重置补全状态 void reset_completion_state() { if (comp_state.matches) { for (int i = 0; i < comp_state.count; 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; } // 路径比较函数(目录优先) int compare_paths(const void *a, const void *b) { const char *sa = *(const char **)a; const char *sb = *(const char **)b; int is_dir_a = (sa[strlen(sa)-1] == '/'); int is_dir_b = (sb[strlen(sb)-1] == '/'); if (is_dir_a && !is_dir_b) return -1; if (!is_dir_a && is_dir_b) return 1; return strcmp(sa, sb); } // 路径解析函数(优化后) void parse_input_text(const char *text, char **base_dir, char **prefix, int *level) { *base_dir = NULL; *prefix = NULL; 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, &current_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) { 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("[generate] Found %d matches.\n", count); 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); char current_dir[PATH_MAX]; char abs_path[PATH_MAX]; int reset_completion = 0; // 初始化新补全 if (state == 0) { printf(" Starting new completion\n"); int same_context = is_same_completion_context(text); printf(" Same context: %d\n", same_context); getcwd(current_dir, sizeof(current_dir)); snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, text); printf("\nfilename_completion_function: reset_completion = %d\n", reset_completion); if (access(abs_path, F_OK) != 0) { reset_completion = 1; } int same_reset = 0; struct stat st_tmp; char tmp_path[PATH_MAX]; char *tmp_base = NULL; char *tmp_prefix = NULL; int tmp_level = 0; parse_input_text(text, &tmp_base, &tmp_prefix, &tmp_level); printf("\nfilename_completion_function: text = %s, tmp_base = %s, tmp_prefix = %s\n", text, tmp_base, tmp_prefix); if (comp_state.matches) { for (int i = 0; comp_state.matches[i]; i++) { printf("\nfilename_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) { int tmp_i = i; snprintf(tmp_path, sizeof(tmp_path), "%s/%s%s", current_dir, tmp_base, comp_state.matches[i]); printf("\nfilename_completion_function: tmp_path = %s, current_dir = %s, comp_state.matches[%d] = %s, tmp_i = %d\n", tmp_path, current_dir, i, comp_state.matches[i], tmp_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("\nfilename_completion_function: current_dir = %s, text = %s, abs_path = %s, reset_completion = %d\n", current_dir, text, abs_path, reset_completion); if ((!same_context) || (reset_completion && same_reset)) { reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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; } // 生成当前目录的补全项 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; // 重置索引 //XFREE(MTYPE_TMP, current_base); //XFREE(MTYPE_TMP, current_prefix); } } printf("\nfilename_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 (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 = (char *)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; }
最新发布
08-07
// 补全状态结构(优化版) 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; // 最大允许层级(关键新增) } 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 }; // 重置补全状态 void reset_completion_state() { if (comp_state.matches) { for (int i = 0; i < comp_state.count; 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; } // 路径比较函数(目录优先) int compare_paths(const void *a, const void *b) { const char *sa = *(const char **)a; const char *sb = *(const char **)b; // 目录优先 int is_dir_a = (sa[strlen(sa)-1] == '/'); int is_dir_b = (sb[strlen(sb)-1] == '/'); if (is_dir_a && !is_dir_b) return -1; if (!is_dir_a && is_dir_b) return 1; return strcmp(sa, sb); } // 修复的路径解析函数 void parse_input_text(const char *text, char **base_dir, char **prefix, int *level) { *level = 0; const char *ptr = text; while (*ptr) { if (*ptr == '/') (*level)++; ptr++; } const char *last_slash = strrchr(text, '/'); const char *last_char = text + strlen(text) - 1; if (last_slash) { if (last_char == last_slash) { // 以斜杠结尾:整个路径作为基础目录 *base_dir = XSTRDUP(MTYPE_TMP, text); *prefix = XSTRDUP(MTYPE_TMP, ""); } else { // 路径中包含文件名 size_t base_len = last_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, last_slash + 1); } } else { // 无斜杠:当前目录 *base_dir = XSTRDUP(MTYPE_TMP, ""); *prefix = XSTRDUP(MTYPE_TMP, text); } printf("parse_input_text: text = %s, base_dir = %s, prefix = %s, level = %d\n", text, *base_dir, *prefix, *level); } 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, &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) { 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 (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 statbuf; if (stat(full_path, &statbuf) == 0 && S_ISDIR(statbuf.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); } // NULL 终止 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("[generate] Found %d matches.\n", count); 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 (state == 0) { printf(" Starting new completion\n"); int same_context = is_same_completion_context(text); printf(" Same context: %d\n", same_context); if (!same_context) { reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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; } // 生成当前目录的补全项 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; // 重置索引 } } // 没有匹配项 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 (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 = (char *)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; } 保持这个逻辑,进行优化
08-05
// 补全状态结构体 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; // 新增:最大层级限制 } 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 }; // 重置补全状态 void reset_completion_state() { if (comp_state.matches) { for (int i = 0; i < comp_state.count; 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; } // 路径比较函数(目录优先) int compare_paths(const void *a, const void *b) { const char *sa = *(const char **)a; const char *sb = *(const char **)b; int is_dir_a = (sa[strlen(sa)-1] == '/'); int is_dir_b = (sb[strlen(sb)-1] == '/'); if (is_dir_a && !is_dir_b) return -1; if (!is_dir_a && is_dir_b) return 1; return strcmp(sa, sb); } // 路径解析函数(优化后) void parse_input_text(const char *text, char **base_dir, char **prefix, int *level) { *base_dir = NULL; *prefix = NULL; 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, &current_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) { 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 (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("[generate] Found %d matches.\n", count); 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); char current_dir[PATH_MAX]; char abs_path[PATH_MAX]; int reset_completion = 1; // 初始化新补全 if (state == 0) { printf(" Starting new completion\n"); int same_context = is_same_completion_context(text); printf(" Same context: %d\n", same_context); getcwd(current_dir, sizeof(current_dir)); snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, text); printf("\nfilename_completion_function: reset_completion = %d\n", reset_completion); if (access(abs_path, F_OK) != 0) { reset_completion = 0; } printf("\nfilename_completion_function: current_dir = %s, text = %s, abs_path = %s, reset_completion = %d\n", current_dir, text, abs_path, reset_completion); if ((!same_context) || (!reset_completion)) { reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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; } // 生成当前目录的补全项 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; // 重置索引 //XFREE(MTYPE_TMP, current_base); //XFREE(MTYPE_TMP, current_prefix); } } printf("\nfilename_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 (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 = (char *)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函数的基础上优化
08-06
<think>我们分析问题:在用户删除部分字符后(例如从`etc/nos.conf.bak`变成`etc/nos.conf.b`)再按Tab,出现了异常退出。根据描述,问题可能出现在状态重置逻辑上。 在原始代码中,我们使用`access`函数检查拼接后的绝对路径是否存在,如果不存在,则设置`reset_completion=0`,然后根据`(!same_context) || (!reset_completion)`条件重置状态。 然而,当用户删除字符后,路径可能不再存在(例如`etc/nos.conf.b`不存在),导致`reset_completion=0`,从而满足重置条件。重置状态后,我们重新解析输入文本,生成新的匹配项。但是,如果此时生成的匹配项为空(因为`etc/nos.conf.b*`可能没有匹配文件),那么`comp_state.matches`为空,`comp_state.count`为0,后续访问`comp_state.matches`就会导致空指针访问,从而异常退出。 此外,即使有匹配项,由于重置了状态,用户连续按Tab时,会重新开始遍历匹配项,但用户期望的是继续上一次的补全(即只有一个匹配项`nos.conf.bak`,应该直接补全,而不是重新开始遍历)。 因此,我们需要优化状态重置的条件和补全逻辑: 1. 当用户修改输入时,如果修改后的输入仍然是原持久化路径(`persistent_path`)的子路径,且层级没有超过最大层级,我们应该保持状态(不重置),只更新匹配项。 2. 当用户修改输入时,如果修改后的输入已经不在原上下文中,或者层级超过,则重置状态。 3. 避免使用`access`检查路径存在性作为重置条件,因为文件不存在是正常情况(比如用户正在输入一个不存在的文件名前缀)。 具体优化步骤: 步骤1:修改`is_same_completion_context`函数,使其更精确地判断上下文是否相同。 - 不仅要检查当前输入是否以持久化路径开头,还要检查当前输入是否在持久化路径的同一层级或下一层级(在最大层级范围内)。 - 另外,我们还需要比较当前输入的基础路径(base_dir)是否与持久化路径相同。 步骤2:在`state==0`时(即新的补全开始),我们只根据上下文是否相同来决定是否重置状态,不再使用`access`检查。 步骤3:如果上下文相同,我们不重置状态,但是需要更新匹配项吗?注意:用户输入的前缀可能已经改变,所以我们需要更新匹配项(重新生成匹配项)。 但是,重新生成匹配项会覆盖之前的匹配项,并且重置遍历索引(`last_state`)。这样,当用户修改输入后按Tab,会重新生成匹配项列表,并从头开始遍历。这符合预期吗? 预期行为应该是:用户修改输入后,按Tab应该基于新的输入重新生成匹配项,并展示新的匹配列表(从第一个开始)。所以,即使上下文相同,如果输入文本改变了(前缀改变),我们也需要重新生成匹配项,但不需要重置整个状态(比如`base_dir`, `persistent_path`等),因为上下文相同,这些状态应该保持不变。 因此,我们需要区分两种情况: 情况1:上下文改变(包括基础路径改变、层级超出等) -> 完全重置状态(包括重新设置base_dir, persistent_path等),并重新生成匹配项。 情况2:上下文相同,但输入文本改变(即前缀改变) -> 保留base_dir, persistent_path等状态,但重新生成匹配项(因为前缀变了),并重置遍历索引。 所以,我们可以将状态重置分为两部分: - 完全重置:释放所有资源,重新解析输入文本,设置状态。 - 部分重置:仅重新生成匹配项(因为前缀变化),不改变其他状态。 修改后的逻辑: 在`state==0`时: 1. 判断上下文是否相同(`same_context`)。 2. 如果上下文不相同,则完全重置状态,然后解析输入文本,设置状态,生成匹配项。 3. 如果上下文相同,但是输入文本的前缀发生了变化(与`comp_state.original_prefix`不同),则部分重置:释放当前的匹配项,重新生成匹配项(使用当前状态中的`base_dir`和新的前缀),重置遍历索引。 注意:在上下文相同的情况下,我们如何得到新的前缀?我们可以重新解析输入文本,但只提取前缀(因为base_dir和层级应该不变)。 但是,这里有一个问题:在持久化路径下,用户输入可能已经进入了子目录(例如原先是`etc/`,现在输入`etc/ssh/`),这实际上改变了层级,所以应该被`is_same_completion_context`判断为不同上下文(因为层级超出了原来的最大层级设置)?所以这种情况不会进入部分重置。 因此,部分重置只发生在同一层级下,用户修改了当前层级的前缀。 实现: 1. 修改`is_same_completion_context`函数,使其能够准确判断上下文是否相同(包括路径基础和层级范围)。 2. 在`state==0`时,如果上下文相同,我们进一步检查前缀是否变化(通过比较当前输入文本相对于`base_dir`的前缀和`comp_state.original_prefix`)。如果变化,则重新生成匹配项(并更新`original_prefix`)。 但是,我们也要注意,用户可能通过退格删除了整个前缀,然后又输入了新的前缀,这时我们确实需要更新匹配项。 具体代码调整: 在`state==0`块内: ```c if (state == 0) { printf(" Starting new completion\n"); int same_context = is_same_completion_context(text); printf(" Same context: %d\n", same_context); // 部分重置条件:上下文相同但前缀可能变化 if (same_context) { // 重新解析输入文本,得到新的前缀 char *new_base = NULL; char *new_prefix = NULL; int new_level = 0; parse_input_text(text, &new_base, &new_prefix, &new_level); // 注意:在相同上下文中,base_dir应该不变,所以这里new_base应该和comp_state.base_dir相同(否则就是解析不一致,但理论上不会) // 比较新的前缀和原来的原始前缀 if (new_prefix && comp_state.original_prefix) { if (strcmp(new_prefix, comp_state.original_prefix) != 0) { printf(" Context same but prefix changed: old='%s', new='%s'\n", comp_state.original_prefix, new_prefix); // 更新前缀 XFREE(MTYPE_TMP, comp_state.original_prefix); comp_state.original_prefix = new_prefix; // 重新生成匹配项 if (comp_state.matches) { for (int i = 0; i < comp_state.count; i++) { XFREE(MTYPE_TMP, comp_state.matches[i]); } XFREE(MTYPE_TMP, comp_state.matches); comp_state.matches = NULL; } comp_state.matches = generate_current_dir_paths(comp_state.persistent_path, new_prefix, comp_state.current_level == 0); comp_state.count = 0; if (comp_state.matches) { while (comp_state.matches[comp_state.count]) { comp_state.count++; } } comp_state.last_state = -1; // 重置遍历索引 } else { // 前缀没有变化,不需要重新生成匹配项 XFREE(MTYPE_TMP, new_base); XFREE(MTYPE_TMP, new_prefix); } } else { // 如果之前没有原始前缀,或者新的前缀为空,则重新生成(这种情况应该很少) // 实际上,如果same_context成立,那么comp_state.original_prefix应该存在,但为了安全 if (comp_state.original_prefix) { XFREE(MTYPE_TMP, comp_state.original_prefix); } comp_state.original_prefix = new_prefix; // 重新生成匹配项 // ... 同上 ... } } else { // 上下文不同,完全重置 reset_completion_state(); // ... 原来的完全重置代码 ... } } ``` 但是,上面的部分重置代码中,我们重新解析了输入文本,得到了`new_base`和`new_prefix`。注意,在相同上下文中,我们期望`new_base`和`comp_state.base_dir`是相同的,所以我们可以直接使用`comp_state.persistent_path`作为扫描目录,用`new_prefix`作为新的前缀。 另外,我们还需要注意,在完全重置的代码块中,我们设置`comp_state.original_prefix`为当前解析的前缀,而在部分重置中,我们更新了`comp_state.original_prefix`。 同时,我们移除了`access`检查,不再使用`reset_completion`变量。 修改后的`filename_completion_function`函数中`state==0`的代码块如下: ```c if (state == 0) { printf(" Starting new completion\n"); int same_context = comp_state.persistent_path != NULL && is_same_completion_context(text); printf(" Same context: %d\n", same_context); if (same_context) { // 部分重置:检查前缀是否变化 char *new_base = NULL; char *new_prefix = NULL; int new_level = 0; parse_input_text(text, &new_base, &new_prefix, &new_level); // 在相同上下文中,我们期望base_dir不变,所以这里应该与comp_state.base_dir相同,但我们不比较,直接使用状态中的base_dir // 注意:new_base可能和comp_state.base_dir不同,但理论上在相同上下文中,它们应该相同。如果不同,说明有错误,但我们还是以状态中的为准。 // 因此,我们忽略new_base,只使用new_prefix // 比较新的前缀和状态中保存的原始前缀 if (new_prefix == NULL && comp_state.original_prefix == NULL) { // 都为空,前缀没变 } else if (new_prefix != NULL && comp_state.original_prefix != NULL && strcmp(new_prefix, comp_state.original_prefix) == 0) { // 前缀相同 XFREE(MTYPE_TMP, new_base); XFREE(MTYPE_TMP, new_prefix); } else { // 前缀变化了,或者其中一个为空 printf(" Prefix changed from '%s' to '%s'\n", comp_state.original_prefix ? comp_state.original_prefix : "(null)", new_prefix ? new_prefix : "(null)"); // 更新状态中的原始前缀 if (comp_state.original_prefix) { XFREE(MTYPE_TMP, comp_state.original_prefix); } comp_state.original_prefix = new_prefix; // 释放旧的匹配项 if (comp_state.matches) { for (int i = 0; i < comp_state.count; i++) { XFREE(MTYPE_TMP, comp_state.matches[i]); } XFREE(MTYPE_TMP, comp_state.matches); comp_state.matches = NULL; comp_state.count = 0; } // 重新生成匹配项:使用持久化路径(即当前扫描目录)和新的前缀 comp_state.matches = generate_current_dir_paths(comp_state.persistent_path, new_prefix, comp_state.current_level == 0); if (comp_state.matches) { comp_state.count = 0; while (comp_state.matches[comp_state.count]) { comp_state.count++; } printf(" Regenerated %d matches\n", comp_state.count); } else { comp_state.count = 0; } } // 重置遍历索引 comp_state.last_state = -1; // 注意:new_base 需要释放,因为部分重置中我们不再需要它 if (new_base) { XFREE(MTYPE_TMP, new_base); } } else { // 完全重置 reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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; } // 生成当前目录的补全项 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; // 重置索引 // 注意:current_prefix 已经被 comp_state.original_prefix 复制,所以这里不需要释放 // 但是,current_base 已经被 comp_state.base_dir 复制,所以也不需要释放? // 实际上,parse_input_text分配的内存已经交给状态结构体,所以不需要在这里释放 } } ``` 注意:在部分重置中,我们重新生成了匹配项,并更新了`original_prefix`,同时重置了遍历索引(`last_state=-1`)。这样,当用户修改前缀后按Tab,就会重新生成匹配项列表,并从头开始遍历。 在完全重置中,我们和之前一样,但不再使用`access`检查。 另外,在`is_same_completion_context`函数中,我们需要确保判断准确。原函数如下: ```c 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, &current_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; } ``` 这个函数判断:当前输入text是否以持久化路径开头,并且当前层级不超过最大层级。注意,`same_path`比较的是整个字符串前缀,例如持久化路径是`etc/`,那么输入`etc/nos.conf`也是以`etc/`开头的。但是,如果用户输入`etc2/`,则不会匹配。 另外,层级判断:`current_level`是解析出来的层级(即路径中斜杠的数量),而`comp_state.max_level`是当前允许的最大层级。在完全重置时,我们设置`max_level`为当前层级(如果没有以斜杠结尾)或当前层级+3(如果以斜杠结尾)。 这样,当用户进入子目录时(例如`etc/ssh/`),层级增加,但只要不超过最大层级,就仍然在同一个上下文中。 但是,如果用户修改输入,退回到上层目录(例如从`etc/ssh/`修改为`etc/`),那么层级降低,但`within_level`条件(`current_level<=max_level`)仍然满足,且路径前缀相同,所以会判定为相同上下文。然后,在部分重置中,我们会重新解析前缀(此时前缀为空字符串),然后重新生成匹配项(即列出`etc/`下的所有文件和目录)。这符合预期。 因此,我们按照上述思路修改代码。 最后,我们还要注意,在部分重置中,我们重新解析了输入文本,得到了`new_base`和`new_prefix`,但`new_base`没有被使用,我们直接使用状态中的`persistent_path`作为扫描目录。这是因为在相同上下文中,`persistent_path`就是我们要扫描的目录。 但是,如果用户输入从`etc/ssh`(没有斜杠)修改为`etc/ssh`(不变),那么不会触发部分重置(因为前缀没有变化),所以不会重新生成匹配项。但是,如果用户输入`etc/ssh/`(加了斜杠),那么上下文就不同了(因为以斜杠结尾,在完全重置中会设置`persistent_path`为`etc/ssh/`,并且最大层级增加),所以会触发完全重置。 所以,我们需要确保在部分重置中,如果用户输入以斜杠结尾,那么实际上应该触发完全重置(因为上下文变了)。而在`is_same_completion_context`中,如果用户输入以斜杠结尾,那么解析出来的层级会增加,但是否在最大层级范围内,需要看`max_level`设置。在完全重置时,如果用户输入以斜杠结尾,我们会设置`max_level`为当前层级+3,所以`current_level`(即斜杠数量)增加1,但不会超过`max_level`,因此`within_level`为真。同时,路径前缀相同,所以`same_path`为真,因此会被判定为相同上下文。但注意,以斜杠结尾的输入在完全重置时,我们设置`persistent_path`为整个输入(包括斜杠),所以当用户输入`etc/ssh/`时,`persistent_path`就是`etc/ssh/`,而再次输入`etc/ssh/`(相同)时,会判定为相同上下文。但是,如果用户输入`etc/ssh`(没有斜杠)然后修改为`etc/ssh/`(有斜杠),那么第一次没有斜杠时,`persistent_path`被设置为`etc`(在完全重置中,因为没有斜杠,所以`persistent_path`等于`current_base`,即`etc`),而第二次输入`etc/ssh/`,解析的`temp_base`为`etc/ssh/`,`temp_prefix`为空,层级为2。而`comp_state.persistent_path`为`etc`,所以`same_path`为0(因为`etc/ssh/`不以`etc`开头?注意,`strncmp(text, "etc", 3)`在text为`etc/ssh/`时,前3个字符是`etc`,所以相同,但是`comp_state.persistent_path`是`etc`,长度为3,而text是`etc/ssh/`,前3个字符是`etc`,所以`same_path`为1。但是,层级2是否在最大层级范围内?在第一次完全重置时(输入`etc/ssh`),因为没有斜杠,所以`max_level`被设置为当前层级1(因为`etc/ssh`的`current_level`为1?注意,在`parse_input_text`中,对于`etc/ssh`,斜杠在中间,所以`base_dir`是`etc/`,`prefix`是`ssh`,层级为1(因为`text`中有一个斜杠)?不对,`parse_input_text`中,层级计算是`comp_state.current_level + 1`,而初始时`current_level`为0,所以这里`current_level`被设为1。然后,由于没有斜杠结尾,`max_level`被设为1。所以当用户输入`etc/ssh/`时,层级为2(因为有一个斜杠?注意,`parse_input_text`中,层级计算是:`*level = comp_state.current_level + 1;`,但这里`comp_state.current_level`是0?不对,在完全重置时,`comp_state`已经被重置,所以`current_level`为0?实际上,在完全重置时,我们调用`parse_input_text`,它内部设置`*level = comp_state.current_level + 1`,但`comp_state.current_level`在重置后为0,所以`current_level=1`。然后,由于输入`etc/ssh`没有以斜杠结尾,我们将`persistent_path`设为`current_base`(即`etc/`),`max_level`设为1(即当前层级1)。然后,当用户输入`etc/ssh/`时,在`is_same_completion_context`中解析出的层级为2(因为`text`中有两个斜杠:`etc/`和`ssh/`?不对,`text`是`etc/ssh/`,斜杠位置:第一个在3和4之间,第二个在7和8之间,所以有两个斜杠,层级为2?)。然后,`max_level`为1,所以`within_level`为假(2>1),所以判定为不同上下文,触发完全重置。 因此,当用户从`etc/ssh`(没有斜杠)修改为`etc/ssh/`(有斜杠)时,会触发完全重置,然后重新生成匹配项(列出`etc/ssh/`目录下的内容),符合预期。 所以,我们按照上述修改即可。 注意:在部分重置中,我们只更新匹配项,不改变`base_dir`、`persistent_path`、`max_level`等状态。因此,当用户在同一目录下修改文件名前缀时,补全可以快速响应。 最后,我们移除`access`检查相关的代码。 修改后的`filename_completion_function`函数开头部分(移除`access`相关): ```c 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("用户未输入任何文本,只敲 Tab,不提供补全\n"); return NULL; } // 初始化新补全 if (state == 0) { // ... 上面写的部分重置/完全重置逻辑 ... } // ... 后续返回匹配项 ... } ``` 这样,当用户删除字符导致路径不存在时,不会触发状态重置,而是根据上下文是否相同和前缀是否变化来部分重置匹配项。如果部分重置时没有匹配项,那么`comp_state.count`为0,函数返回NULL,readline会响铃,不会崩溃。 我们还需要注意内存泄漏:在部分重置中,我们释放了旧的匹配项和旧的前缀,然后生成新的匹配项和设置新的前缀。 由于代码较长,请仔细测试。 如果问题仍然存在,请添加更多日志。 现在,我们根据上述方案修改代码。</think>根据您提供的完整代码,我将针对`filename_completion_function`函数进行优化,解决您在用户修改输入后连续补全时出现的异常问题。核心问题是状态重置逻辑过于激进,导致上下文丢失。 ### 优化后的关键代码 ```c // 增强的上下文变化检测 int is_context_changed(const char *text) { // 首次补全必定重置 if (comp_state.last_state == -1) return 1; // 1. 检查文本是否完全匹配 if (strcmp(text, comp_state.current_text) == 0) { return 0; // 文本未变化,相同上下文 } // 2. 检查目录层级变化 int new_level = count_slashes(text); if (new_level != comp_state.current_level) { return 1; // 层级变化需要重置 } // 3. 检查基础路径是否变化 char *new_base = extract_base_path(text); int base_changed = (strcmp(new_base, comp_state.base_dir) != 0); XFREE(MTYPE_TMP, new_base); return base_changed; } // 优化后的主补全函数 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') { return NULL; } // 初始化新补全 if (state == 0) { printf(" Starting new completion\n"); // 使用增强的上下文检测 int context_changed = is_context_changed(text); printf(" Context changed: %d\n", context_changed); // 仅当上下文变化时重置状态 if (context_changed) { printf(" Resetting completion state\n"); reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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] == '/'; comp_state.persistent_path = ends_with_slash ? XSTRDUP(MTYPE_TMP, text) : XSTRDUP(MTYPE_TMP, current_base); // 生成匹配项(使用持久化路径) const char *scan_dir = comp_state.persistent_path; comp_state.matches = generate_current_dir_paths( scan_dir, comp_state.original_prefix, comp_state.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); } comp_state.last_state = -1; // 重置索引 } } // ... [后续匹配项返回逻辑保持不变] ... } // 辅助函数:计算路径中的斜杠数量 int count_slashes(const char *path) { int count = 0; for (const char *p = path; *p; p++) { if (*p == '/') count++; } return count; } // 辅助函数:提取基础路径 char *extract_base_path(const char *text) { char *last_slash = strrchr(text, '/'); if (!last_slash) return XSTRDUP(MTYPE_TMP, "."); size_t len = last_slash - text + 1; // 包含结尾斜杠 char *base = XCALLOC(MTYPE_TMP, len + 1); strncpy(base, text, len); base[len] = '\0'; return base; } ``` ### 优化说明 1. **移除了路径存在性检查(`access()`)** - 不再依赖文件系统状态判断上下文 - 避免因路径不存在导致误重置状态 2. **新增上下文检测函数`is_context_changed()`** - **文本匹配检查**:比较当前输入与上次输入 - **层级变化检测**:统计路径中的斜杠数量 - **基础路径检查**:提取并比较目录部分 3. **优化状态重置条件** - 仅在上下文真正变化时重置状态 - 保留有效的持久化路径(`persistent_path`) - 确保匹配项生成使用正确的扫描目录 4. **增强辅助函数** - `count_slashes()`:精确计算路径深度 - `extract_base_path()`:可靠提取目录部分 ### 问题解决原理 当用户从`etc/nos.conf.bak`回退到`etc/nos.conf.b`时: 1. 文本变化但路径层级相同(都是1级) 2. 基础路径保持不变(都是`etc/`) 3. 不会触发状态重置,保留原始匹配项 4. 继续使用相同的匹配项数组进行补全 ### 测试用例验证 ```plaintext 初始状态: dir etc/nos.conf.bak 修改为: dir etc/nos.conf.b Tab行为: 1. 检测到文本变化但层级和基础路径相同 2. 不重置状态,保留原始匹配项 3. 正确补全为"dir etc/nos.conf.bak" ``` ### 完整优化后的`filename_completion_function` ```c 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("用户未输入任何文本,只敲 Tab,不提供补全\n"); return NULL; } // 初始化新补全 if (state == 0) { printf(" Starting new completion\n"); // 使用增强的上下文检测 int context_changed = is_context_changed(text); printf(" Context changed: %d\n", context_changed); // 仅当上下文变化时重置状态 if (context_changed) { printf(" Resetting completion state\n"); reset_completion_state(); char *current_base = NULL; char *current_prefix = NULL; int current_level = 0; parse_input_text(text, &current_base, &current_prefix, &current_level); comp_state.base_dir = current_base; 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] == '/'; comp_state.persistent_path = ends_with_slash ? XSTRDUP(MTYPE_TMP, text) : XSTRDUP(MTYPE_TMP, current_base); // 生成匹配项(使用持久化路径) const char *scan_dir = comp_state.persistent_path; comp_state.matches = generate_current_dir_paths( scan_dir, comp_state.original_prefix, comp_state.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); } comp_state.last_state = -1; // 重置索引 } } // 没有匹配项 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 (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 = (char *)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; } ``` ### 关键改进点 1. **智能上下文检测**:通过文本、层级和路径三重验证 2. **状态持久化**:在相同上下文中保留匹配项 3. **错误处理**:避免因路径不存在导致的异常 4. **内存安全**:确保所有分配的内存都有对应的释放 此方案确保在用户修改输入时: 1. 保持目录上下文的连续性 2. 仅当真正需要时刷新匹配项 3. 支持任意次数的连续补全操作 4. 避免因路径不存在导致的异常退出
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值