monaco-editor-full
v0.55.1-full.3
Published
A browser based code editor
Readme
Monaco Editor(xiezhongfu fork)
本仓库是 microsoft/monaco-editor 的个人 fork,在官方版本基础上新增了若干扩展功能。官方文档请参阅 上游 README。
与官方版本的差异
| # | 差异项 | 官方版本 | 本 fork |
|---|--------|----------|---------|
| 1 | 核心依赖 | monaco-editor-core | monaco-editor-core-full(更多未裁剪模块),当前 0.55.1-full.2 |
| 1 | 构建产物 / 包名 | out/monaco-editor / monaco-editor | out/monaco-editor-full / monaco-editor-full,版本号追加 -full 后缀 |
| 2 | Hover 自定义 DOM 渲染 | 不支持,MarkdownString.domElement 会被过滤或经 DOMPurify 清洗 | 支持,domElement 字段存在时直接挂载,完全绕过 DOMPurify |
| 2 | exports 通配符映射 | ./* → ./esm/vs/*(强制重定向) | ./* → ./*(顶层直通,任意路径可导入) |
| 3 | ML 语言自动检测 | 不支持(workbench 层能力,浏览器侧无) | 内置,支持神经网络 + 正则双模型,自动 / 手动两种模式 |
| 4 | LLM 语言检测 | 不支持 | 内置,适配器模式,手动触发,LRU 缓存 |
1. 依赖 monaco-editor-core-full & 输出包名变更
官方 monaco-editor 依赖 monaco-editor-core;本 fork 切换为自定义的 monaco-editor-core-full(同样由 VS Code 源码生成,但包含了更多未裁剪的模块),当前版本为 0.55.1-full.2。
构建产物也相应重命名:输出目录由 out/monaco-editor 改为 out/monaco-editor-full,发布包名为 monaco-editor-full,版本号在原版本号后追加 -full 后缀(如 0.55.1-full)。
2. monaco-editor-core-full 补丁整合
对 monaco-editor-core-full 依赖包打了三处补丁,弥补上游缺失的能力。补丁通过 patch-package 管理,npm install 后自动应用到 node_modules,消费方无需任何额外操作。
① 支持在 Hover 中直接渲染 DOM 节点
- 改动文件:
esm/vs/base/common/htmlContent.js—isEmptyMarkdownString - 问题:上游
isEmptyMarkdownString只检查value字段是否为空,当MarkdownString携带自定义domElement字段时会被误判为空串,导致 Hover 内容被过滤掉、永远不渲染。 - 改动:在
isMarkdownString分支中优先判断domElement instanceof HTMLElement,若存在则直接返回false(非空)。 - 效果:
domElement字段的MarkdownString能正常进入渲染流程,不再被提前丢弃。
② Hover 渲染时绕过 DOMPurify sanitize
- 改动文件:
esm/vs/editor/contrib/hover/browser/markdownHoverParticipant.js—renderMarkdown - 问题:上游
renderMarkdown统一走markdownRendererService.render()渲染路径,该路径内部会经过 DOMPurify 清洗。当 Hover 内容是业务方自行构造的 DOM 节点时(如带有自定义交互逻辑的富文本),DOMPurify 会剥离事件监听器、自定义属性等,导致渲染结果不符合预期。 - 改动:在
renderMarkdown中检测markdownString.domElement instanceof HTMLElement,若存在则直接appendChild挂载到容器并调用onFinishedRendering(),完全跳过markdownRendererService.render()流程;否则仍走原有路径。 - 效果:业务方可通过在
MarkdownString上挂载domElement属性,将任意 DOM 节点原样渲染进 Hover 弹层,保留完整的事件绑定和自定义属性,不受 DOMPurify 干扰。
③ 修复 package.json exports 通配符映射
- 改动文件:
package.json→exports字段(源头修改,构建时自动带入产物) - 问题:上游 exports 将
./*.js和./*强制重定向到./esm/vs/*.js,导致消费方无法按完整路径(如esm/vs/editor/editor.main.js、min/vs/…)直接导入,只能访问esm/vs/子目录下的文件。 - 改动:将通配符映射改为顶层直通:
- "./*.js": "./esm/vs/*.js", - "./*": "./esm/vs/*.js" + "./*.js": "./*.js", + "./*": "./*" - 效果:消费方可按包内任意路径导入文件,
esm/、min/、根目录文件均可正常解析,不再受路径重映射限制。
实现方式
| 文件 | 作用 |
|------|------|
| patches/monaco-editor-core+0.55.1-full.2.patch | patch-package 格式的 patch 文件,包含改动 ① ② |
| build/postinstall.ts | npm install 后自动调用 patch-package,将 patch 应用到 node_modules |
| package.json → exports | 直接在源头修改,npm run build 时由 build-monaco-editor.ts 自动写入产物的 package.json,覆盖改动 ③ |
3. ML 语言自动检测功能(Language Detection)
在 src/features/languageDetection/ 下新增了一套完整的语言自动检测模块,将 VS Code 工作区层(workbench)的语言检测能力移植到了浏览器侧独立运行。官方 monaco-editor 不包含此功能。
核心文件
| 文件 | 说明 |
|------|------|
| register.ts | 公开入口,调用 registerLanguageDetection() 即可启用自动检测 |
| browser/languageDetectionService.ts | 主线程服务,负责历史偏置计算和 Worker 通信 |
| browser/languageDetectionWorker.ts | Web Worker 内部逻辑,运行神经网络模型和正则模型 |
| browser/languageDetectionWorkerMain.ts | Worker 入口,调用 bootstrapWebWorker 启动 |
| browser/languageDetectionProtocol.ts | 主线程↔Worker 的 RPC 协议定义 |
功能亮点
- 神经网络检测:通过
@vscode/vscode-languagedetection(TF.js 驱动)识别代码语言,支持url模式(动态加载远程模型)和bundle模式(跳过神经网络,仅用正则)。 - 正则兜底检测:可选接入
vscode-regexp-languagedetection(通过regexpModelUrl配置),在神经网络无结果时作为降级方案。两种模型的优先级可通过preferHistoryModel反转。 - 历史偏置加权:复刻 VS Code 的偏置算法(session +7、workspace +5、历史 workspace +3、全局历史 +1),让检测结果向用户近期使用的语言倾斜。
- 持久化支持:通过
storage.read/write回调将历史记录持久化到localStorage等任意存储。 - 自动 & 手动两种模式:默认自动监听所有 model 的创建和内容变更(节流 600 ms,与 VS Code 一致);设置
autoDetect: false后可手动调用detectLanguage()。 - 无侵入:只在 model 无文件扩展名且语言为
plaintext时才介入,已有语言设置或用户手动切换后不再覆盖。
快速接入示例
import { registerLanguageDetection } from 'monaco-editor-full/src/features/languageDetection/register';
// ① 神经网络 + 正则双保险(推荐)
const service = registerLanguageDetection({
modelLoadMode: 'url',
indexJsUrl: 'https://cdn.jsdelivr.net/npm/@vscode/vscode-languagedetection/dist/lib/index.js',
modelJsonUrl: 'https://cdn.jsdelivr.net/npm/@vscode/vscode-languagedetection/model/model.json',
weightsUrl: 'https://cdn.jsdelivr.net/npm/@vscode/vscode-languagedetection/model/group1-shard1of1.bin',
// 正则兜底:神经网络无结果时自动回落
regexpModelUrl: 'https://cdn.jsdelivr.net/npm/vscode-regexp-languagedetection/dist/index.js',
storage: {
read: (key) => localStorage.getItem(key),
write: (key, val) => localStorage.setItem(key, val),
},
onLanguageDetected: (model, languageId) => {
console.log(`检测到语言:${languageId}`);
},
});
// ② 仅正则模型(轻量,无需加载 TF.js)
const service = registerLanguageDetection({
modelLoadMode: 'bundle', // 禁用神经网络
regexpModelUrl: 'https://cdn.jsdelivr.net/npm/vscode-regexp-languagedetection/dist/index.js',
});
// ③ 正则优先,神经网络兜底
const service = registerLanguageDetection({
modelLoadMode: 'url',
/* ...其他 url 配置... */
regexpModelUrl: 'https://cdn.jsdelivr.net/npm/vscode-regexp-languagedetection/dist/index.js',
preferHistoryModel: true, // 正则先跑,有结果就用,无结果再走神经网络
});
// ④ 手动检测模式
const service = registerLanguageDetection({ modelLoadMode: 'url', /* ... */, autoDetect: false });
const lang = await service.detectLanguage(myModel);
if (lang) myModel.setLanguage(lang);
// 不再使用时释放资源
service.dispose();4. LLM 语言检测功能(LLM Language Detection)
在 src/features/llmLanguageDetection/ 下新增了一套由大语言模型(LLM)驱动的语言检测模块。与 ML 检测不同,本模块:
- 纯手动触发:不自动监听内容变更,由调用方决定何时检测(如按钮点击)。
- 候选语言可控:调用方声明 LLM 可以返回哪些语言,超出范围的结果一律视为
undefined。 - 适配器模式:通过
ILLMAdapter接口解耦 LLM 调用细节(prompt 构造、鉴权、重试等)。 - LRU 缓存:相同内容哈希在 TTL 内复用上次结果,避免重复调用 LLM。
- 用户锁定保护:用户手动切换语言后,
detectLanguage()直接返回undefined,不再覆盖。
核心文件
| 文件 | 说明 |
|------|------|
| src/features/llmLanguageDetection/register.ts | 公开入口,调用 registerLLMLanguageDetection() 创建服务 |
| src/features/llmLanguageDetection/llmDetectionService.ts | 服务实现:LRU 缓存、用户锁定、适配器调用 |
配套 Sample
samples/browser-esm-vite-react-llm-detection/ 提供了完整的 React + Vite 演示:
- 默认使用内置 mock 适配器(关键词启发式,无需 API Key,离线可用)。
- 配置
VITE_OPENAI_API_KEY环境变量后自动切换为真实 OpenAI 调用(gpt-4o-mini)。 - 演示了手动触发检测、状态栏展示检测结果、用户锁定交互等完整流程。
快速接入示例
import {
registerLLMLanguageDetection,
type ILLMAdapter,
type LLMLanguageCandidate,
} from 'monaco-editor-full/src/features/llmLanguageDetection/register';
// ① 实现适配器(你来决定 prompt 和 LLM 调用方式)
const myAdapter: ILLMAdapter = {
async detect({ candidates, codeSnippet }) {
const response = await fetch('/api/detect-language', {
method: 'POST',
body: JSON.stringify({ candidates, code: codeSnippet }),
});
const { languageId } = await response.json();
return languageId; // 须为 candidates 中的某个 id,否则视为 undefined
},
};
// ② 声明候选语言
const candidates: LLMLanguageCandidate[] = [
{
id: 'pgsql',
description: 'PostgreSQL — 使用 :: 类型转换、ILIKE、$1 参数、RETURNING 子句。',
examples: [`SELECT id, name::text FROM users WHERE email ILIKE '%@example.com' RETURNING id;`],
},
{
id: 'mysql',
description: 'MySQL — 使用反引号标识符、SHOW TABLES、GROUP_CONCAT、LIMIT n。',
examples: [`SELECT user_id, GROUP_CONCAT(tag) FROM \`user_tags\` GROUP BY user_id LIMIT 100;`],
},
{ id: 'python', description: 'Python — def/class、缩进、print()、列表推导式。' },
{ id: 'typescript', description: 'TypeScript — 带类型注解的 JavaScript,含 interface 和泛型。' },
];
// ③ 注册服务(应用启动时调用一次)
const llmService = registerLLMLanguageDetection({
adapter: myAdapter,
candidates,
maxContentLength: 1500, // 发给 LLM 的最大字符数
minContentLength: 30, // 内容过短时跳过检测
cacheTtl: 5 * 60_000, // 5 分钟缓存
onLanguageDetected: (model, languageId) => {
console.log(`LLM 检测到语言:${languageId}`);
},
onDetectionError: (model, error) => {
console.error('LLM 检测失败:', error);
},
});
// ④ 手动触发检测(如绑定到按钮)
const languageId = await llmService.detectLanguage(editor.getModel());
// languageId 已被自动应用到 model,这里可用于更新 UI
// ⑤ 不再使用时释放资源
llmService.dispose();License
Licensed under the MIT License.
