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;
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);
}
// 构建完整路径(自动加 /)
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;
// 普通路径解析逻辑(相对路径或绝对路径)
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) {
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);
// 拦截只敲 Tab 的情况
if (text == NULL || *text == '\0') {
printf("用户未输入任何文本,只敲 Tab,不提供补全\n");
return NULL;
}
char current_dir[PATH_MAX];
char abs_path[PATH_MAX];
int reset_completion = 0;
// <<< 新增字段,用于判断是否是 flash:/ 模式
int is_flash_mode = (strncmp(text, "flash:/", 7) == 0);
int is_f_prefix = (strncmp(text, "f", 1) == 0 && strncmp(text, "flash:/", 7) != 0);
// 初始化新补全
if (state == 0) {
printf(" Starting new completion\n");
// 获取当前工作目录
getcwd(current_dir, sizeof(current_dir));
snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, text);
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: 11111111 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);
if ((!same_context) || (reset_completion && same_reset) || (!comp_state.persistent_path || comp_state.persistent_path[0] == '\0')) {
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);
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;
}
// 生成当前目录的补全项
// <<< 第一次调用:根据输入前缀生成匹配项
if (is_flash_mode) {
// <<< flash:/ 开头,直接扫描 /mnt/switch/ 路径
comp_state.matches = generate_current_dir_paths("/mnt/switch/", text + 7, current_level == 0);
comp_state.flash_mode = 1;
} else if (is_f_prefix) {
// <<< f 开头,先扫描当前目录
comp_state.matches = generate_current_dir_paths(current_base, current_prefix, current_level == 0);
if (current_level == 0) {
if (comp_state.matches == NULL || comp_state.matches[0] == NULL) {
// <<< 当前目录没有 f 开头的,自动切换到 flash:/ 路径
comp_state.base_dir = XSTRDUP(MTYPE_TMP, "flash:/");
comp_state.matches = generate_current_dir_paths("/mnt/switch/", text, current_level == 0);
comp_state.flash_mode = 1;
} else {
// <<< 有本地 f 开头项,追加 flash:/ 虚拟项
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 + 3) * sizeof(char*));
comp_state.matches = temp;
comp_state.matches[count] = XSTRDUP(MTYPE_TMP, "flash:/");
comp_state.matches[count + 1] = XSTRDUP(MTYPE_TMP, "");
comp_state.matches[count + 2] = 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 {
// <<< 非 f 开头,正常路径补全
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) {
// <<< 返回 flash:/ 项
full_path = XSTRDUP(MTYPE_TMP, match);
} else if (comp_state.flash_mode) {
// <<< flash:/ 模式,构建 flash:/xxx/yyy 格式
full_path = XCALLOC(MTYPE_TMP, strlen("flash:/") + strlen(match) + 2);
snprintf(full_path, strlen("flash:/") + strlen(match) + 2, "flash:/%s", 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;
}
上边这段代码是可以实现父目录子目录来回切换,但是里边的flash:/虚拟路径的功能,在有其他f前缀的文件时会失败,并且不能实现循环匹配当前目录下某个前缀的文件或目录
下边的代码,能遍历当前目录下匹配的前缀的文件或目录,里边的flash:/虚拟路径的功能,在有其他f前缀的文件时是正常的,但是不能实现父目录子目录来回切换:
// 文件类型枚举
typedef enum {
FILE_TYPE_REGULAR,
FILE_TYPE_DIRECTORY,
FILE_TYPE_VIRTUAL
} file_type_t;
// 匹配项结构
typedef struct {
char *path;
file_type_t type;
} match_item_t;
// 补全状态结构
typedef struct {
char base_path[MAX_PATH_LEN]; // 原始基础路径
char current_path[MAX_PATH_LEN]; // 当前解析的路径
char prefix[MAX_PATH_LEN]; // 当前需要补全的前缀
match_item_t matches[MAX_MATCHES]; // 匹配项列表
int match_count; // 匹配项数量
int current_index; // 当前选择的匹配项索引
char last_user_input[MAX_PATH_LEN]; // 用户最后一次输入
char last_returned_match[MAX_PATH_LEN]; // 上一次返回的匹配项
file_type_t last_match_type; // 上一次返回的匹配项类型
int in_single_directory_mode; // 是否处于单目录模式
int is_virtual_path; // 当前路径是否为虚拟路径
} completion_state;
static completion_state comp_state = {0};
int compare_matches(const void *a, const void *b) {
const match_item_t *ma = (const match_item_t *)a;
const match_item_t *mb = (const match_item_t *)b;
const char *sa = ma->path;
const char *sb = mb->path;
char first_a = sa[0];
char first_b = sb[0];
int is_lower_a = islower((unsigned char)first_a);
int is_lower_b = islower((unsigned char)first_b);
if (is_lower_a && !is_lower_b) return -1;
if (!is_lower_a && is_lower_b) return 1;
int is_upper_a = isupper((unsigned char)first_a);
int is_upper_b = isupper((unsigned char)first_b);
if (is_upper_a && !is_upper_b) return -1;
if (!is_upper_a && is_upper_b) return 1;
return strcasecmp(sa, sb);
}
// 重置补全状态
void reset_completion_state() {
printf("Resetting state (had %d matches)\n", comp_state.match_count);
//for (int i = 0; i < comp_state.match_count; i++) {
// if (comp_state.matches[i].path) {
// XFREE(MTYPE_TMP, comp_state.matches[i].path);
// comp_state.matches[i].path = NULL;
// }
//}
comp_state.match_count = 0;
comp_state.current_index = 0;
comp_state.last_returned_match[0] = '\0';
comp_state.last_match_type = FILE_TYPE_REGULAR;
comp_state.in_single_directory_mode = 0;
comp_state.is_virtual_path = 0;
}
// 虚拟路径映射
const char *map_virtual_path(const char *virt) {
static char real_path[MAX_PATH_LEN];
if (strncmp(virt, "flash:/", 7) == 0) {
snprintf(real_path, sizeof(real_path), "/mnt/switch/%s", virt + 7);
return real_path;
}
// 对于普通路径,也复制(如果需要)
strncpy(real_path, virt, sizeof(real_path) - 1);
real_path[sizeof(real_path) - 1] = '\0';
return real_path;
}
// 解析输入文本
void parse_input_text(const char *text) {
//assert(text != NULL);
// 如果输入为空
if (text[0] == '\0') {
comp_state.current_path[0] = '\0';
comp_state.prefix[0] = '\0';
return;
}
// 查找最后一个 '/'
const char *slash = strrchr(text, '/');
if (slash == NULL) {
// 无 '/' → 整个是前缀,路径为空
comp_state.current_path[0] = '\0';
strncpy(comp_state.prefix, text, MAX_PATH_LEN - 1);
comp_state.prefix[MAX_PATH_LEN - 1] = '\0';
} else {
// 有 '/' → 路径是到 '/' 为止,前缀是后面部分
size_t path_len = (slash - text) + 1; // 包括 '/'
size_t copy_len = path_len > MAX_PATH_LEN - 1 ? MAX_PATH_LEN - 1 : path_len;
memcpy(comp_state.current_path, text, copy_len);
comp_state.current_path[copy_len] = '\0';
const char *prefix_start = slash + 1;
size_t prefix_len = strlen(prefix_start);
if (prefix_len >= MAX_PATH_LEN) prefix_len = MAX_PATH_LEN - 1;
memcpy(comp_state.prefix, prefix_start, prefix_len);
comp_state.prefix[prefix_len] = '\0';
}
printf("[PARSE] text='%s' → path='%s', prefix='%s'\n",
text, comp_state.current_path, comp_state.prefix);
}
// 添加匹配项
void add_match(const char *path, file_type_t type) {
if (comp_state.match_count >= MAX_MATCHES) {
printf("Match list full, skipping '%s'\n", path);
return;
}
comp_state.matches[comp_state.match_count].path = XSTRDUP(MTYPE_TMP, path);
comp_state.matches[comp_state.match_count].type = type;
comp_state.match_count++;
printf("Added match: %s (type: %d)\n", path, type);
}
// 生成匹配项
void generate_matches(void) {
printf("Generating matches for path='%s' prefix='%s'\n",
comp_state.current_path, comp_state.prefix);
// 重置匹配计数
comp_state.match_count = 0;
// 添加虚拟路径选项 (仅当输入以'f'开头)
if (comp_state.prefix[0] == 'f' || comp_state.prefix[0] == 'F') {
add_match("flash:/", FILE_TYPE_VIRTUAL);
printf("Added virtual path option: flash:/\n");
}
// 获取实际扫描路径
const char *scan_path = comp_state.current_path;
if (strncmp(scan_path, "flash:/", 7) == 0) {
scan_path = map_virtual_path(scan_path); // 确保 map_virtual_path 返回 "/mnt/switch/..."
}
// 处理空路径情况
if (*scan_path == '\0') {
scan_path = ".";
}
// 尝试打开目录
DIR *dir = opendir(scan_path);
if (!dir) {
printf("Failed to open dir '%s'\n", scan_path);
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
const char *name = entry->d_name;
// 跳过隐藏文件和特殊目录
if (name[0] == '.' && (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)) {
continue;
}
// 前缀匹配
if (comp_state.prefix[0] != '\0' &&
strncasecmp(name, comp_state.prefix, strlen(comp_state.prefix)) != 0) {
continue;
}
// 构建完整路径
char full_path[MAX_PATH_LEN] = {0};
snprintf(full_path, sizeof(full_path), "%s%s", comp_state.current_path, name);
// 构建真实路径用于 stat()
char check_path[MAX_PATH_LEN] = {0};
if (strncmp(comp_state.current_path, "flash:/", 7) == 0) {
snprintf(check_path, sizeof(check_path), "/mnt/switch/%s/%s",
comp_state.current_path + 7, name);
} else {
snprintf(check_path, sizeof(check_path), "%s/%s", scan_path, name);
}
struct stat st;
if (stat(check_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
// 目录:添加 '/'
snprintf(full_path, sizeof(full_path), "%s%s/", comp_state.current_path, name);
add_match(full_path, FILE_TYPE_DIRECTORY);
} else {
add_match(full_path, FILE_TYPE_REGULAR);
}
} else {
add_match(full_path, FILE_TYPE_REGULAR);
printf("Failed to stat '%s', assuming regular file\n", check_path);
}
}
closedir(dir);
printf("Total matches generated: %d\n", comp_state.match_count);
}
// 检测输入变化
int detect_input_change(const char *text) {
printf("[DEBUG] detect_input_change: last_input='%s', last_match='%s', text='%s'\n",
comp_state.last_user_input,
comp_state.last_returned_match,
text);
// 情况1: 首次调用
if (comp_state.last_user_input[0] == '\0') {
printf("→ First call: input changed\n");
return 1;
}
// 情况2: 用户在上一个补全项后继续输入(如 etc/ → etc/s)
if (comp_state.last_returned_match[0] != '\0') {
size_t match_len = strlen(comp_state.last_returned_match);
if (match_len > 0 &&
match_len < MAX_PATH_LEN &&
strncmp(text, comp_state.last_returned_match, match_len) == 0 &&
strlen(text) > match_len) {
printf("→ Append after match: '%s' → '%s'\n",
comp_state.last_returned_match, text);
return 1;
}
}
// 情况3: 输入变短(用户删除字符)
if (strlen(text) < strlen(comp_state.last_user_input)) {
printf("→ Input shortened: '%s' → '%s'\n",
comp_state.last_user_input, text);
return 1;
}
// 情况4: 输入前缀不一致(全新输入)
size_t last_len = strlen(comp_state.last_user_input);
if (last_len > 0 && strncmp(text, comp_state.last_user_input, last_len) != 0) {
printf("→ New input prefix: '%s' vs '%s'\n",
text, comp_state.last_user_input);
return 1;
}
// 默认:无显著变化 → 循环模式
printf("→ No change detected (cycling mode)\n");
return 0;
}
// 处理单目录模式
void handle_single_directory_mode(void) {
if (comp_state.match_count != 1) return;
if (comp_state.matches[0].type != FILE_TYPE_DIRECTORY) return;
const char *full = comp_state.matches[0].path;
const char *slash = strrchr(full, '/');
if (!slash) return;
const char *dirname = slash + 1;
if (strlen(dirname) == 0) return;
char new_path[MAX_PATH_LEN];
snprintf(new_path, sizeof(new_path), "%s%s/",
comp_state.current_path, dirname);
strncpy(comp_state.current_path, new_path, MAX_PATH_LEN - 1);
comp_state.prefix[0] = '\0';
reset_completion_state();
generate_matches(); // ← 必须成功生成子文件
if (comp_state.match_count == 0) {
printf("→ No files in directory: '%s'\n", comp_state.current_path);
// 至少添加一个占位符,避免后续崩溃
add_match(new_path, FILE_TYPE_DIRECTORY); // 保持当前目录
return;
}
comp_state.current_index = 0;
printf("→ Auto-expanded to directory: '%s' with %d items\n",
comp_state.current_path, comp_state.match_count);
}
// 主补全函数
char *filename_completion_function(const char *text, int state) {
printf("\n=== COMPLETION CALL: text='%s' state=%d ===\n", text, state);
// 检测输入文本是否变化
int input_changed = detect_input_change(text);
// 更新用户输入记录
if (input_changed) {
strncpy(comp_state.last_user_input, text, MAX_PATH_LEN-1);
comp_state.last_user_input[MAX_PATH_LEN-1] = '\0';
}
// 处理不同模式
if (input_changed) {
printf("Input changed: '%s' -> '%s'\n",
comp_state.last_returned_match, text);
// 重置状态
reset_completion_state();
// 解析输入
parse_input_text(text);
// 生成匹配项
generate_matches();
// 如果唯一目录,自动展开并加载其内容
if (comp_state.match_count == 1 && comp_state.matches[0].type == FILE_TYPE_DIRECTORY) {
handle_single_directory_mode(); // ← 已经 generate_matches()
// 注意:此时 match_count 是子目录内容数量
}
comp_state.current_index = 0;
}
// 处理目录追加模式
else if (comp_state.last_match_type == FILE_TYPE_DIRECTORY &&
comp_state.last_returned_match[0] != '\0' &&
strstr(text, comp_state.last_returned_match) == text &&
strlen(text) > strlen(comp_state.last_returned_match)) {
printf("Directory append mode: '%s' + '%s'\n",
comp_state.last_returned_match,
text + strlen(comp_state.last_returned_match));
// 设置当前路径为上次返回的匹配项
strncpy(comp_state.current_path, comp_state.last_returned_match, MAX_PATH_LEN-1);
// 提取新前缀
size_t prefix_start = strlen(comp_state.last_returned_match);
size_t prefix_size = strlen(text) - prefix_start;
if (prefix_size >= MAX_PATH_LEN) prefix_size = MAX_PATH_LEN - 1;
strncpy(comp_state.prefix, text + prefix_start, prefix_size);
comp_state.prefix[prefix_size] = '\0';
// 重新生成匹配项
reset_completion_state();
generate_matches();
comp_state.current_index = 0;
}
// 处理循环模式
else {
if (comp_state.match_count == 0) {
return NULL;
}
comp_state.current_index = (comp_state.current_index + 1) % comp_state.match_count;
printf("Cycling to index %d\n", comp_state.current_index);
}
// 返回当前匹配项
if (comp_state.match_count == 0) {
return NULL;
}
printf("→ comp_state.match_count = %d, comp_state.current_index = %d\n", comp_state.match_count, comp_state.current_index);
const char *path = comp_state.matches[comp_state.current_index].path;
char *ret = XSTRDUP(MTYPE_TMP, path);
// ✅ 关键:只要返回了补全项,就更新 last_returned_match
// 并且,如果这是自动补全(唯一匹配),也要更新 last_user_input!
strncpy(comp_state.last_returned_match, ret, MAX_PATH_LEN - 1);
comp_state.last_returned_match[MAX_PATH_LEN - 1] = '\0';
// ✅ 新增:如果 match_count == 1,说明自动补全,用户“输入”变成了补全结果
if (input_changed && comp_state.match_count == 1) {
strncpy(comp_state.last_user_input, ret, MAX_PATH_LEN - 1);
comp_state.last_user_input[MAX_PATH_LEN - 1] = '\0';
printf("→ Auto-completed. Updating last_user_input to: '%s'\n", comp_state.last_user_input);
}
return ret;
}
将这两段代码的逻辑重新整合一下,整合成一份代码,实现所有的功能
(1)能按前缀匹配,并且能连续tab,然后显示匹配的数组的元素
(2)比如当前路径下有ea.txt,etc目录,然后我用e前缀补全,则先循环显示成ea.txt etc/(目录后自动加/),然后我连续tab,循环显示到etc/时,我再后边加个前缀s,则是etc/s,然后此时我再按tab,则是要补全etc/下的s为前缀的文件或目录(如果有多个,也是按照(1)的防止,循环显示),这里要求的是父目录与子目录灵活切换,灵活补全,这个要做好上下文状态切换
(3)在匹配前缀为f的基础上,增加个虚拟路径flash:/,这个实际上是/mnt/switch/根路径,然后显示的时候要是flash:/;也就是说,我输入的是f前缀,则先扫描当前路径下的f为前缀的文件或目录,然后再最后增加个虚拟的flash:/,如果没有其他f为前缀的文件或目录,则自动回显成flash:/
最新发布