CodeMirror介绍

10,154 阅读9分钟

CodeMirror 是一个用 JavaScript 编写的功能强大的文本编辑器库,专为网页端设计,尤其适用于代码编辑场景。它提供了丰富的功能,适用于开发者、教育平台、在线 IDE 或需要代码编辑支持的 Web 应用。以下是其主要功能:


一、CodeMirror简介

1. 核心功能

  • 语法高亮
    支持 100+ 编程语言(通过模式插件实现),包括 JavaScript、Python、HTML/CSS、C++ 等,高亮规则可自定义。
  • 行号显示
    自动显示行号,支持自定义格式或动态隐藏。
  • 代码折叠
    根据语法结构(如函数、循环块)折叠代码区域,提升长代码的可读性。
  • 智能缩进
    根据语言特性自动缩进,支持手动调整缩进规则。

2. 交互与编辑

  • 多光标编辑
    支持同时操作多个光标,批量修改代码(类似 Sublime Text/VSCode)。
  • 键盘快捷键
    内置常用快捷键(复制、注释等),支持自定义绑定,可集成 Vim/Emacs 键位模式(通过插件)。
  • 括号匹配与自动闭合
    自动高亮匹配的括号,输入时自动补全括号、引号等符号。
  • 撤销/重做历史
    完整的编辑历史记录,可配置历史深度。

3. 扩展性与集成

  • 插件系统
    通过插件扩展功能,例如:

    • Linter 集成:实时代码检查(需结合 ESLint 等工具)。
    • 自动补全:上下文感知的代码提示(通过 @codemirror/autocomplete)。
    • Git Diff 显示:高亮代码变更(如 @codemirror/history)。
  • 主题定制
    提供多种内置主题(如 Dracula、Solarized),支持 CSS 自定义样式。

  • 事件处理 API
    监听内容变化、光标移动、焦点事件等,便于与其他组件交互。


4. 高级功能

  • 协同编辑
    通过附加库(如 @codemirror/collab)实现多人实时协作编辑。
  • 移动端支持
    优化触屏操作,支持手势和虚拟键盘交互。
  • 可访问性(a11y)
    支持屏幕阅读器和键盘导航,符合 ARIA 标准(CodeMirror 6 重点改进)。
  • 性能优化
    虚拟滚动技术处理大文件,响应式设计保障流畅体验。

5. 框架兼容性

  • 可与主流前端框架(React、Vue.js、Angular)集成,提供官方或社区维护的封装库(如 @uiw/react-codemirror)。
  • 模块化设计,支持按需加载功能,减少打包体积。

6. 版本特性

  • CodeMirror 6 是当前主要版本,采用现代技术栈(TypeScript 重写),提供更清晰的 API 和更强的扩展性。旧版(CodeMirror 5)仍广泛使用但不再更新。

适用场景

  • 在线代码编辑器(如 JSFiddle、CodePen)
  • 文档工具中的代码片段嵌入
  • 教育平台的编程练习环境
  • 浏览器的开发者工具扩展

资源

CodeMirror 的灵活性和丰富的生态使其成为 Web 端代码编辑的首选解决方案之一。

CodeMirror与Monacoeditor对比

CodeMirror 和 Monaco Editor 都是流行的 Web 端代码编辑器库,但它们的设计目标、功能侧重和适用场景有所不同。以下是两者的详细对比:


1. 背景与定位

特性CodeMirrorMonaco Editor
开发者Marijn Haverbeke(独立开发者)Microsoft(VS Code 团队)
核心目标轻量级、高度可定制、通用代码编辑场景提供接近 IDE 的编辑体验(VS Code 核心)
体积约 200–500 KB(按需加载)约 20–30 MB(完整功能,需优化加载)
开源协议MITMIT

2. 核心功能对比

语法高亮与语言支持

  • CodeMirror

    • 支持 100+ 语言(通过模式插件)。
    • 高亮规则基于正则表达式,灵活性高但复杂语言(如 TypeScript)支持较弱。
  • Monaco

    • 内置 VS Code 的语言服务(TypeScript/JavaScript、CSS、HTML、JSON 等)。
    • 对 TypeScript 的智能感知(类型推断、重构)支持最佳。

代码智能提示(IntelliSense)

  • CodeMirror

    • 需通过插件(如 @codemirror/autocomplete)实现基础提示,功能较简单。
    • 需要手动集成语言服务(如 Tern.js)。
  • Monaco

    • 原生深度集成 VS Code 的智能提示、参数提示、快速修复等。
    • 支持 TypeScript 类型检查、跳转到定义、引用查找等 IDE 级功能。

性能与渲染

  • CodeMirror

    • 虚拟滚动优化,适合大文件(10k+ 行)编辑,内存占用低。
    • 默认渲染较简单,依赖 DOM 操作。
  • Monaco

    • 基于 canvas 渲染,性能极高,但大文件(如 100k 行)可能卡顿。
    • 集成复杂语言服务时资源消耗较高。

扩展性

  • CodeMirror

    • 模块化设计,通过插件系统灵活扩展,社区插件丰富。
    • 可自定义主题、快捷键、交互逻辑。
  • Monaco

    • 扩展性依赖 VS Code 的 API 设计,功能更强大但定制成本高。
    • 支持加载 VS Code 主题(如 monaco-themes 库)。

3. 集成与使用场景

场景CodeMirror 优势Monaco 优势
轻量级编辑器✅ 体积小,适合嵌入博客、文档工具❌ 体积过大,需按需加载
在线 IDE✅ 灵活定制,适合教学/简单编码环境✅ 接近本地 IDE 体验,适合复杂项目
移动端兼容性✅ 触屏优化较好❌ 对移动端支持有限(如虚拟键盘问题)
框架集成✅ 官方支持 React/Vue 封装库✅ 提供 React 封装(如 @monaco-editor/react
协作编辑✅ 通过插件实现(如 @codemirror/collab❌ 需自行实现或依赖其他库

4. 典型用例

  • CodeMirror

    • 轻量级场景:CodePen、JSFiddle 的早期版本、Markdown 编辑器(如 Jupyter Notebook)。
    • 教育平台:LeetCode 的代码练习框、在线编程课程。
  • Monaco Editor

    • 复杂 IDE:VS Code 网页版(vscode.dev)、GitHub Codespaces。
    • 企业工具:云开发环境(如 CodeSandbox)、低代码平台的高级模式。

5. 开发体验

维度CodeMirrorMonaco Editor
文档清晰但分散(需查插件文档)官方文档详细,但部分 API 复杂
调试基于 DOM,易排查样式问题依赖浏览器开发者工具,调试渲染层较难
社区生态插件丰富,但质量参差不齐依赖 VS Code 生态,插件标准化程度高
学习曲线较平缓(适合快速集成)较陡峭(需理解 VS Code 架构)

6. 如何选择?

优先选 CodeMirror 的情况

  • 需要轻量级、快速加载的编辑器(如移动端优先)。
  • 项目对定制化要求极高(如非标准语法高亮)。
  • 资源受限(服务器带宽或低端设备兼容性)。

优先选 Monaco Editor 的情况

  • 需要接近 VS Code 的编辑体验(如智能提示、代码导航)。
  • 项目重度依赖 TypeScript/JavaScript 语言服务。
  • 已有 VS Code 生态的技术栈(如使用 LSP 协议)。

7. 优化技巧

  • CodeMirror

    • 使用 Tree Shaking 仅导入必要模块(CodeMirror 6 支持 ES 模块)。
    • 延迟加载语言模式插件。
  • Monaco Editor

    • 通过 monaco-editor-webpack-plugin 按需打包语言和功能。
    • 使用 CDN 加载核心文件(如 unpkg.com)。

总结

  • CodeMirror 是「瑞士军刀」:轻便灵活,适合通用代码编辑场景。
  • Monaco Editor 是「重型武器」:功能强大,适合需要 IDE 级体验的复杂应用。

根据项目需求权衡体积、功能和开发成本,两者均可通过配置达到较优平衡。

CodeMirror的sql语法高亮、错误提示、自动补全

在 CodeMirror 中实现 SQL 的 语法高亮错误提示 和 自动补全,需要结合语言包、Linter 插件和自定义自动补全逻辑。以下是完整实现步骤和示例代码:


1. 安装依赖

npm install @codemirror/lang-sql @codemirror/lint @codemirror/autocomplete

2. 基础配置(语法高亮 + 行号)

import { EditorView, basicSetup } from "codemirror";
import { sql } from "@codemirror/lang-sql";

// 创建编辑器实例(默认 SQL 语法高亮)
const editor = new EditorView({
  extensions: [
    basicSetup, // 包含行号、基础快捷键等
    sql({ 
      dialect: 'mysql', // 可选方言: 'postgresql', 'sqlite', 'mssql'
      upperCaseKeywords: true, // 自动大写关键字(如 SELECT)
    }),
  ],
  parent: document.querySelector("#editor"),
});

3. 错误提示(Linting)

使用 @codemirror/lint 集成 SQL 语法检查工具(例如 sql-lint 或自定义逻辑):

3.1 安装 sql-lint

npm install sql-lint

3.2 配置 Linter

import { linter, lintGutter } from "@codemirror/lint";
import { SQLint } from "sql-lint";

// 自定义 SQL 语法检查逻辑
const sqlLinter = linter((view) => {
  const code = view.state.doc.toString();
  const errors = SQLint.lint(code); // 使用 sql-lint 检查
  
  return errors.map((error) => ({
    from: error.position.start, // 转换为 CodeMirror 位置
    to: error.position.end,
    message: error.message,
    severity: "error", // 或 'warning'
  }));
});

// 在编辑器扩展中添加 lint
const editor = new EditorView({
  extensions: [
    basicSetup,
    sql(),
    lintGutter(), // 显示行号旁的错误图标
    sqlLinter,    // 应用语法检查
  ],
  parent: document.querySelector("#editor"),
});

4. 自动补全(Autocomplete)

通过 @codemirror/autocomplete 实现静态关键字补全和动态元数据补全。

4.1 静态关键字补全

import { autocompletion, CompletionContext } from "@codemirror/autocomplete";

// SQL 关键字列表
const SQL_KEYWORDS = [
  "SELECT", "FROM", "WHERE", "JOIN", "GROUP BY", "ORDER BY",
  "INSERT INTO", "UPDATE", "DELETE", "CREATE TABLE", "ALTER TABLE",
];

// 自定义补全逻辑
function sqlKeywordCompletion(context: CompletionContext) {
  const word = context.matchBefore(/\w*/);
  if (!word) return null;

  return {
    from: word.from,
    options: SQL_KEYWORDS.map((keyword) => ({
      label: keyword,
      type: "keyword", // 显示关键字图标
    })),
  };
}

4.2 动态表名/列名补全(需结合后端 API)

// 假设从 API 获取表名和列名
async function fetchTableNames() {
  const res = await fetch("/api/tables");
  return await res.json(); // 返回 ['users', 'orders']
}

async function fetchColumns(table: string) {
  const res = await fetch(`/api/tables/${table}/columns`);
  return await res.json(); // 返回 ['id', 'name', 'email']
}

// 动态上下文补全
function dynamicTableCompletion(context: CompletionContext) {
  const line = context.state.doc.lineAt(context.pos);
  const textBefore = line.text.slice(0, context.pos - line.from);

  // 检测是否在 FROM 子句后输入表名
  if (textBefore.match(/FROM\s*$/i)) {
    return {
      from: context.pos,
      options: (await fetchTableNames()).map((table) => ({
        label: table,
        type: "table",
      })),
    };
  }

  // 检测是否在 WHERE 子句后输入列名
  if (textBefore.match(/WHERE\s*(\w+.)?/i)) {
    const table = "users"; // 需根据上下文推断当前表名(复杂逻辑需自行实现)
    return {
      from: context.pos,
      options: (await fetchColumns(table)).map((col) => ({
        label: col,
        type: "column",
      })),
    };
  }

  return null;
}

4.3 合并补全逻辑

const editor = new EditorView({
  extensions: [
    basicSetup,
    sql(),
    autocompletion({
      override: [sqlKeywordCompletion, dynamicTableCompletion],
    }),
  ],
  parent: document.querySelector("#editor"),
});

5. 完整集成示例

import { basicSetup, EditorView } from "codemirror";
import { sql } from "@codemirror/lang-sql";
import { linter, lintGutter } from "@codemirror/lint";
import { autocompletion, CompletionContext } from "@codemirror/autocomplete";
import { SQLint } from "sql-lint";

// 1. 配置 Linter
const sqlLinter = linter((view) => {
  // ... 同前文错误检查逻辑
});

// 2. 配置自动补全
const sqlCompleter = (context: CompletionContext) => {
  // ... 合并静态关键字和动态补全逻辑
};

// 3. 创建编辑器
const editor = new EditorView({
  extensions: [
    basicSetup,
    sql({ dialect: "mysql" }),
    lintGutter(),
    sqlLinter,
    autocompletion({ override: [sqlCompleter] }),
  ],
  parent: document.querySelector("#editor"),
});

6. 高级优化技巧

  • 按需加载方言

    import { MySQL, PostgreSQL } from "@codemirror/lang-sql";
    sql({ dialect: MySQL }); // 明确指定 MySQL 方言
    
  • 主题定制
    使用 @codemirror/theme-one-dark 或自定义 CSS:

    .cm-editor {
      height: 400px;
      border: 1px solid #ddd;
    }
    .cm-lint-marker-error { 
      background-image: url("error-icon.svg");
    }
    
  • 性能优化

    • 对动态补全请求添加防抖(debounce):

      import { debounce } from "lodash";
      const debouncedFetch = debounce(fetchTableNames, 300);
      
    • 缓存元数据(如表结构)减少 API 调用。