如何正确定义CodeMirror 6语言?我的代码为何无自动补全?
Firestore语句自动补全无响应的问题排查与修复
问题背景
想要为以下Firestore语句实现基础自动补全:
db.collection("Items").where("name", "==", "temp").limit(1).get();
已通过CodeMirror 6的StreamLanguage编写语言定义,React中集成后编辑器可正常加载,但输入时无自动补全功能。
原语言定义代码
import { IndentContext, LanguageSupport, StreamLanguage, StringStream } from "@codemirror/language"; import { tags as t } from "@lezer/highlight"; export const FireStoreLanguage = StreamLanguage.define({ name: "firestore", startState: (indentUnit: number) => { return {}; }, token: (stream: StringStream, state: any = {}): string | null => { console.log(stream); if (stream.match("db")) { state.db = true; return "keyword"; } if (stream.match(".")) { if (state.db) { state.db = false; state.collection = true; return "keyword"; } else if (state.collection) { state.collection = false; state.where = true; return "keyword"; } else if (state.where) { state.where = false; state.limit = true; return "keyword"; } else if (state.limit) { state.limit = false; return "keyword"; } } if (stream.match("collection")) { if (state.db) { state.collection = true; return "keyword"; } } if (stream.match("where")) { if (state.collection) { state.where = true; return "keyword"; } } if (stream.match("limit")) { if (state.where) { state.limit = true; return "keyword"; } } if (stream.match("get")) { if (state.limit) { state.limit = false; return "keyword"; } } if (stream.match(/"(?:[^\"]|\.)*"/)) { if (state.collection) { return "string"; } if (state.where) { state.where = false; state.whereValue = true; return "string"; } if (state.whereValue) { state.whereValue = false; return "string"; } if (stream.match("==")) { if (state.whereValue) { state.whereValue = false; state.whereOperator = true; return "operator"; } } if (stream.match(/[0-9]+/)) { if (state.limit) { return "number"; } } } stream.next(); return null; }, blankLine: (state: {}, indentUnit: number): void => {}, copyState: (state: {}) => {}, indent: ( state: {}, textAfter: string, context: IndentContext ): number | null => { return 1; }, languageData: { commentTokens: { line: ";" }, }, tokenTable: { db: t.keyword, dot: t.punctuation, collection: t.keyword, get: t.keyword, lParen: t.punctuation, rParen: t.punctuation, string: t.string, }, }); export function firestore() { return new LanguageSupport(FireStoreLanguage); }
原React集成代码
import CodeMirror from "@uiw/react-codemirror"; import React from "react"; import { firestore } from "./firestore"; function App() { const onChange = React.useCallback((value, viewUpdate) => { console.log("value:", value); }, []); return ( <CodeMirror value={``} height="100vh" extensions={[firestore()]} onChange={onChange} /> ); } export default App;
问题排查
当前代码存在以下关键问题:
- 未实现自动补全逻辑:
StreamLanguage仅负责语法高亮和词法分析,CodeMirror的自动补全功能需要通过@codemirror/autocomplete模块单独实现,你的代码完全未涉及补全相关配置。 - Token函数逻辑漏洞:字符串匹配分支内的
==运算符、数字匹配代码永远不会执行(stream.match(/".../)已消耗流内容,后续匹配无法生效);状态切换逻辑不严谨,容易出现状态混乱导致词法分析错误。 - 未启用补全扩展:即使实现了补全逻辑,也需要将
autocompletion扩展添加到编辑器配置中,否则补全功能无法触发。
修复方案
1. 安装自动补全依赖
执行命令安装补全模块:
npm install @codemirror/autocomplete
2. 修改语言定义,集成补全功能
更新firestore.ts代码,修复Token逻辑并添加补全配置:
import { IndentContext, LanguageSupport, StreamLanguage, StringStream } from "@codemirror/language"; import { tags as t } from "@lezer/highlight"; import { autocompletion, completeFromList, CompletionContext } from "@codemirror/autocomplete"; // 定义Firestore相关补全项 const firestoreCompletions = [ { label: "db", type: "keyword" }, { label: "collection", type: "function" }, { label: "where", type: "function" }, { label: "limit", type: "function" }, { label: "get", type: "function" }, { label: "==", type: "operator" }, { label: "Items", type: "variable" }, { label: "name", type: "variable" } ]; // 自定义补全源,根据上下文提供精准补全 function firestoreCompletionSource(context: CompletionContext) { const tokenBefore = context.tokenBefore(); if (tokenBefore?.type === "keyword" && tokenBefore.value === "db") { return completeFromList([{ label: ".collection", type: "function" }])(context); } if (tokenBefore?.value === "collection" || tokenBefore?.value === ")") { return completeFromList([{ label: ".where", type: "function" }])(context); } if (tokenBefore?.value === "where" || tokenBefore?.value === ")") { return completeFromList([{ label: ".limit", type: "function" }])(context); } if (tokenBefore?.value === "limit" || tokenBefore?.value === ")") { return completeFromList([{ label: ".get", type: "function" }])(context); } return completeFromList(firestoreCompletions)(context); } export const FireStoreLanguage = StreamLanguage.define({ name: "firestore", startState: (indentUnit: number) => { return { db: false, collection: false, where: false, limit: false, whereValue: false }; }, token: (stream: StringStream, state: any): string | null => { if (stream.eatSpace()) return null; // 匹配关键字 if (stream.match("db")) { state.db = true; state.collection = false; state.where = false; state.limit = false; return "keyword"; } if (stream.match("collection")) { if (state.db) { state.collection = true; state.db = false; return "keyword"; } } if (stream.match("where")) { if (state.collection) { state.where = true; state.collection = false; return "keyword"; } } if (stream.match("limit")) { if (state.where) { state.limit = true; state.where = false; return "keyword"; } } if (stream.match("get")) { if (state.limit) { state.limit = false; return "keyword"; } } // 匹配点运算符 if (stream.match(".")) { return "punctuation"; } // 匹配字符串 if (stream.match(/"(?:[^"\\]|\\.)*"/)) { if (state.collection) state.collection = false; else if (state.where) { state.where = false; state.whereValue = true; } else if (state.whereValue) state.whereValue = false; return "string"; } // 匹配运算符 if (stream.match(/==|!=|<|>|<=|>=/)) { if (state.whereValue) state.whereValue = false; return "operator"; } // 匹配数字 if (stream.match(/[0-9]+/)) { if (state.limit) state.limit = false; return "number"; } // 匹配括号 if (stream.match(/\(|\)/)) { return "punctuation"; } stream.next(); return null; }, blankLine: (state: {}, indentUnit: number): void => {}, copyState: (state: any) => ({ ...state }), indent: ( state: {}, textAfter: string, context: IndentContext ): number | null => { return null; }, languageData: { commentTokens: { line: ";" }, }, tokenTable: { db: t.keyword, dot: t.punctuation, collection: t.keyword, get: t.keyword, lParen: t.punctuation, rParen: t.punctuation, string: t.string, operator: t.operator, number: t.number, }, }); export function firestore() { return new LanguageSupport(FireStoreLanguage, [ autocompletion({ override: [firestoreCompletionSource] }) ]); }
3. 确认React集成配置
原React代码无需修改,因为firestore()函数已包含补全扩展,编辑器会自动启用补全功能。
关键说明
- 补全逻辑通过
firestoreCompletionSource实现,可根据输入上下文提供精准补全(如输入db后自动提示.collection)。 - 修复了Token函数的状态管理问题,确保语法高亮和状态切换准确。
- 通过
autocompletion扩展启用补全功能,并将自定义补全源注入到语言支持中。
内容的提问来源于stack exchange,提问作者Omar Dulaimi
相关产品推荐
相关产品推荐

