// Created At 2026-07-23// P2
// VSCode · Engineering

VS Code CompletionProvider 中的 filterText 陷阱

1. 现象

最近在开发一个 VS Code 扩展,为自定义的 .meph 文件提供语法支持。其中有一个很自然的需求:用户输入 时,自动弹出标准区块名的补全列表。

注册方式按官方文档来:

vscode.languages.registerCompletionItemProvider(
  { language: "mephisto" },
  new MephistoCompletionProvider(),
  "", // trigger character
  ".",
  "[",
)

预期行为是:输入 → 弹出补全 → 选择”角色名” → 自动变成 【角色名】

实际表现:输入 后,auto-closing 自动补了 ,行变成 【】,但补全列表什么都没有

2. 第一次排查:triggerCharacter 的问题

起初怀疑是 triggerCharacter 没生效。在 provideCompletionItems 开头加了一行测试代码:

provideCompletionItems() {
    return [new vscode.CompletionItem('测试-如果能看到我', CompletionItemKind.Text)];
    // ... 原有逻辑
}

重新加载扩展后,按 Ctrl+Space 手动触发补全,”测试-如果能看到我” 正常显示

这说明 completion provider 本身注册成功了,也能正常返回数据。问题出在触发环节

进一步测试发现,在中文输入法下 '【' 作为 trigger character 并不可靠。不是 本身不能作为 trigger——在英文输入法下直接输入 是可以触发补全的。问题在于中文输入法(如拼音)中, 通常通过候选窗口选择输入,这个输入路径绕过了 VS Code 的 trigger character 检测机制。

改用 onDidChangeTextDocument 事件配合 triggerSuggest 命令,不依赖 trigger character,而是监听文档内容变化后强制弹出补全:

vscode.workspace.onDidChangeTextDocument((e) => {
  if (e.document.languageId === "mephisto") {
    for (const change of e.contentChanges) {
      if (change.text === "") {
        vscode.commands.executeCommand("editor.action.triggerSuggest")
        break
      }
    }
  }
})

注意这里不需要 setTimeout(0)。根据 VS Code 的事件模型,onDidChangeTextDocument 在文档内容修改完成后才触发,此时 auto-closing 已经同步插入了 ,当前行已经是 【】 了。不过在极慢的扩展宿主环境下,setTimeout(0) 可以作为保险措施,但通常不需要。

3. 第二次排查:补全数据去哪了

onDidChangeTextDocument 替换 triggerCharacter 后,provider 确实被调用了,也返回了 9 个标准区块名。但补全列表依然是空的

没有报错,没有警告,只有空荡荡的补全弹窗。

这就奇怪了:provider 返回了数据,VS Code 没有报错,为什么用户看不到?

4. 真正的原因:filterText

问题出在 VS Code 的补全过滤机制上。

当用户输入 后,auto-closing 立即插入 ,当前行变成了:

【】

光标在索引 1 的位置( 之间)。此时 triggerSuggest 弹出补全,VS Code 自动提取光标位置的”当前词”作为过滤前缀。在 【】 中间,当前词就是

然后 VS Code 拿 去匹配 provider 返回的每个补全项:

补全项 label 默认用于匹配的文本(filterText = label) 匹配
角色名 角色名
锚点 锚点
世界观 世界观

全部不匹配,所以全部被过滤掉。

这就是 filterText 的作用:它告诉 VS Code 用什么文本来做匹配,而不是用 label 作为匹配依据。

修复方式是在 getBlockCompletions 中设置 filterText

private getBlockCompletions(): CompletionItem[] {
    const items: CompletionItem[] = [];
    for (const name of STANDARD_BLOCKS) {
        const item = new CompletionItem(name, CompletionItemKind.Module);
        item.insertText = name + '】\n';
        item.detail = '标准区块';
        item.filterText = '' + name;  // ← 关键
        items.push(item);
    }
    return items;
}
  • label(显示文本)仍然是”角色名”
  • insertText(插入内容)仍然是 角色名】\n
  • filterText 设为 '【角色名',VS Code 拿到当前词 去匹配 '【角色名'匹配成功 → 显示在列表中

5. CompletionItem 的文本相关属性

CompletionItem 有三个与文本相关的属性:

属性 作用 在这个场景中的值
label 用户看到的补全项文本 角色名
filterText VS Code 用于做模糊匹配的文本 【角色名
insertText 用户选择后实际插入的文本 角色名】\n

默认情况下,filterText 等于 label。当补全项 label 与触发字符(当前词)不匹配时,需要显式设置 filterText 来提供正确的匹配文本。

值得注意的是,VS Code 的过滤是 fuzzy matching(模糊匹配),不是严格的前缀匹配。filterText 的值会参与评分算法,当前词不需要严格作为 filterText 的前缀也能匹配。但在这个场景中,设 filterText'【角色名' 已经足够让 匹配上了。

此外,sortText 控制补全列表的排序顺序:

item.sortText = "0" + name // 标准区块排在前面
item.sortText = "1" + name // 其他项排在后面

CompletionItemKind 则影响显示的图标:

item.kind = CompletionItemKind.Module // 方块图标
item.kind = CompletionItemKind.Class // 菱形图标
item.kind = CompletionItemKind.Struct // 结构体图标

挑一个视觉上顺眼的即可。

6. 最终代码

将上述改动整合在一起,最终实现如下:

// extension.ts — 监听中文输入法下的 【 输入
vscode.workspace.onDidChangeTextDocument(e => {
    if (e.document.languageId !== 'mephisto') return;
    for (const change of e.contentChanges) {
        if (change.text === '') {
            vscode.commands.executeCommand('editor.action.triggerSuggest');
            break;
        }
    }
});

// completion.ts — CompletionProvider 中设置 filterText 和 range
private getBlockCompletions(): CompletionItem[] {
    return STANDARD_BLOCKS.map(name => {
        const item = new CompletionItem(name, CompletionItemKind.Module);
        item.insertText = name + '】\n';
        item.filterText = '' + name;
        item.detail = '标准区块';
        return item;
    });
}

7. 总结

核心要点回顾

问题 原因 解决方案
输入 不触发补全 中文输入法下 triggerCharacter 不可靠 改用 onDidChangeTextDocument + triggerSuggest
补全弹出了但列表为空 filterText 默认与 label 相同,无法匹配当前词 设置 filterText'【' + name
VS Code 提取的当前词不对 wordPattern 不包含 设置 range 手动指定当前词范围

一句话总结

filterText 解决的是”拿什么去匹配“的问题,range 解决的是”当前要匹配的词是哪个“的问题。两者结合,能应对大多数补全过滤异常的场景。

最终的完整解决方案

// 1. 监听输入触发补全(不依赖 triggerCharacter)
vscode.workspace.onDidChangeTextDocument(e => {
    if (e.document.languageId !== 'mephisto') return;
    for (const change of e.contentChanges) {
        if (change.text === '') {
            vscode.commands.executeCommand('editor.action.triggerSuggest');
            break;
        }
    }
});

// 2. 在 CompletionProvider 中设置 filterText 和 range
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
    const line = document.lineAt(position.line).text;
    const openIdx = line.indexOf('');
    const closeIdx = line.indexOf('');

    if (openIdx !== -1 && closeIdx !== -1 && position.character > openIdx && position.character <= closeIdx) {
        const items = STANDARD_BLOCKS.map(name => {
            const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Module);
            item.insertText = name + '】\n';
            item.filterText = '' + name;
            item.range = new vscode.Range(
                new vscode.Position(position.line, openIdx),
                new vscode.Position(position.line, openIdx + 1)
            );
            item.detail = '标准区块';
            return item;
        });
        return items;
    }
    // 其他补全逻辑...
}

排查清单

当你在 VS Code 扩展中发现补全不显示时:

  1. 补全列表完全没出现 → 检查 triggerCharacter / triggerSuggest 是否生效
  2. 补全列表弹出了但什么都没有 → 检查 provider 是否返回了数据(加一个测试项验证)
  3. 确认返回了数据但用户看不到 → 检查 filterText 是否匹配当前词
  4. 以上都无效 → 检查 wordPattern 或设置 range

其中第 3 步是最容易忽略的,因为 VS Code 不会告诉你它过滤掉了什么。

如果这篇文档对你有帮助,可以请我喝杯咖啡 ☕️
Ali PayWechat Pay
© 2026 MOONGATE