Monaco Editor 中使用在线版 Copilot
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:文长
引言
现代软件开发中,代码编辑器的功能不断演进,以满足开发者对高效和智能化工具的需求。Monaco Editor 作为一种轻量级但功能强大的代码编辑器,广泛应用于多种开发环境中。在此背景下,Copilot,一款由 GitHub 开发的 AI 编程助手,凭借其智能代码补全和建议功能,迅速吸引了开发者的关注。
本文将探讨如何在 Monaco Editor 中实现在线版 Copilot 功能的代码续写,旨在为用户提供更加高效的编程体验。
Copilot
什么是 Copilot?
Copilot 是由 GitHub 开发的一款人工智能编程助手,它利用机器学习和自然语言处理技术,旨在帮助开发者更高效地编写代码。Copilot 通过分析大量的开源代码库和文档,能够理解开发者的意图并提供实时的代码建议和补全。当然,除了 Copilot ,还有很多类似的产品,如 Cursor、CodeWhisperer、CodeGeeX、通义灵码、iFlyCode …
工作原理
Copilot 基于 OpenAI 的 Codex 模型,该模型经过大量代码和自然语言数据的训练,能够生成符合语法和逻辑的代码。它通过分析开发者的输入和上下文,预测最可能的代码片段,并将其呈现给用户。
使用效果
Copilot 可以在当前光标处自动生成补全代码。如下图所示

简版实现
github copilot 提供了 vs code 的插件,支持在 vs code 中使用,那是否可以在 Web Editor 中也实现一个 Copilot 呢?通过查看 Monaco Editor 的 API ,可以看到是提供了这么一个 Provider 的。
registerInlineCompletionsProvider
registerInlineCompletionsProvider 是 Monaco Editor 中的一个方法,用于注册一个内联补全 Provider。这个功能允许开发者在代码编辑器中提供上下文相关的补全建议,提升用户的编码效率。
registerInlineCompletionsProvider 支持接收 2 个参数:
- languageId:要给哪个 language 注册这个
Provider。这个Provider只会在 Monaco Editor 的 language 设置为该 language 时,才会被触发。 - provider:
- provideInlineCompletions:该方法用于提供内联补全建议,它根据当前文本模型、光标位置和上下文信息生成适合的补全项,并返回给编辑器。
- 参数:
model: editor.ITextModel:当前编辑器的文本模型,包含用户正在编辑的文本。position: Position:光标的当前位置,指示补全建议的上下文。context: InlineCompletionContext:提供有关补全上下文的信息,例如用户输入状态和触发条件。token: CancellationToken:用于取消操作的令牌,确保性能和可控性。
- 参数:
- freeInlineCompletions:当补全列表不再使用且可以被垃圾回收时,该方法会被调用。允许开发者执行清理操作,释放资源。
- 参数:
completions: T:需要释放的补全项集合。
- 参数:
- handleItemDidShow:当补全项被展示给用户时,该方法会被调用。允许开发者执行特定的逻辑,例如记录日志、更新UI或执行其他操作。
- 参数:
completions: T:当前的补全项集合。item: T['items'][number]:被展示的具体补全项。
- 参数:
- provideInlineCompletions:该方法用于提供内联补全建议,它根据当前文本模型、光标位置和上下文信息生成适合的补全项,并返回给编辑器。
如下图例子所示

具体实现
思路
在编辑器中,每当内容发生变更时,都会触发 registerInlineCompletionsProvider 。在这个 Provider 中执行补全。整个补全的过程:
- 修改光标状态
- 获取上下文内容,发送给 AI
- 等待 AI 返回补全结果,将 AI 的结果进行返回。这里返回的格式(这里以 Monaco Editor@0.31.1 为例,@0.34 版本开始与此有些差别):
interface InlineCompletion {
/**
* The text to insert.
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*/
readonly text: string;
/**
* The range to replace.
* Must begin and end on the same line.
*/
readonly range?: IRange;
readonly command?: Command;
}
interface InlineCompletions<TItem extends InlineCompletion = InlineCompletion> {
readonly items: readonly TItem[];
}
interface InlineCompletionsProvider<T extends InlineCompletions = InlineCompletions> {
provideInlineCompletions(model: editor.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<T>;
}
@0.34 及以上版本返回格式:
interface InlineCompletion {
/**
* The text to insert.
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*
* The text can also be a snippet. In that case, a preview with default parameters is shown.
* When accepting the suggestion, the full snippet is inserted.
*/
readonly insertText: string | {
snippet: string;
};
/**
* A text that is used to decide if this inline completion should be shown.
* An inline completion is shown if the text to replace is a subword of the filter text.
*/
readonly filterText?: string;
/**
* An optional array of additional text edits that are applied when
* selecting this completion. Edits must not overlap with the main edit
* nor with themselves.
*/
readonly additionalTextEdits?: editor.ISingleEditOperation[];
/**
* The range to replace.
* Must begin and end on the same line.
*/
readonly range?: IRange;
readonly command?: Command;
/**
* If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed.
* Defaults to `false`.
*/
readonly completeBracketPairs?: boolean;
}
- 补全结束,恢复光标状态
过程
- 设置光标
在发起补全时,需要将光标变为 loading 状态,但是 monaco editor 自身的配置不满足想要的样式(只支持:'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin')。
monaco editor 的光标并不是原生输入框自带的,也是自行实现的

通过操作 dom 的形式,使用 createPortal 方法,将 loading 组件渲染到该容器下,然后通过状态控制光标的状态切换。具体实现如下所示:
class Editor extends React.Component {
...
switchToLoadingCursor = () => {
const defaultCursor = document.querySelector('.cursors-layer .cursor') as HTMLDivElement;
const defaultCursorRect = defaultCursor.getBoundingClientRect();
const cursorLoadingRect = document
.querySelector('.cursors-layer .cursorLoading')
.getBoundingClientRect();
defaultCursor.style.display = 'none';
this.setState({
cursorLoading: {
left: defaultCursorRect.left - cursorLoadingRect.left + 2,
top: defaultCursorRect.top - cursorLoadingRect.top + 2,
visible: 'visible',
},
});
};
switchToDefaultCursor = () => {
clearTimeout(this.copilotTimer);
if (this.abortController && !this.abortController.signal.aborted) {
this.abortController.abort();
}
const defaultCursor = document.querySelector('.cursors-layer .cursor') as HTMLDivElement;
defaultCursor.style.display = 'block';
this.setState({
cursorLoading: {
left: 0,
top: 0,
visible: 'hidden',
},
});
};
render() {
const cursorLayer = document.querySelector('.monaco-editor .cursors-layer');
return <>
...
{cursorLayer &&
ReactDOM.createPortal(
<Spin
className="cursorLoading"
style={{
position: 'absolute',
top: cursorLoading.top,
left: cursorLoading.left,
visibility:
cursorLoading.visible as React.CSSProperties['visibility'],
zIndex: 999,
}}
indicator={<LoadingOutlined spin />}
size="small"
/>,
cursorLayer
)}
...
</>
}
}
效果如下所示:

- 获取上下文内容,发送 AI 补全,并将内容返回
这一步这里做的比较简单,只是将内容获取,发送给 AI ,然后等待结果的返回,结果返回后,将补全内容返回,并切换光标状态。同时,在鼠标点到其他位置时,会取消补全。
不过,这里没有做规则校验,去校验什么情况下才发起补全行为。
注意:registerInlineCompletionsProvider是只要内容变化就会触发,所以可能需要做一些优化(如防抖等),避免一直发送/取消请求。
this.keyDownDisposable = this.editorInstance.onKeyDown(this.switchToDefaultCursor);
this.mouseDownDisposable = this.editorInstance.onMouseDown(this.switchToDefaultCursor);
this.inlineCompletionDispose = languages.registerInlineCompletionsProvider(language, {
provideInlineCompletions: (model, position, context, token) => {
return new Promise((resolve) => {
clearTimeout(this.copilotTimer);
if (this.abortController && !this.abortController.signal.aborted) {
this.abortController.abort();
}
this.copilotTimer = window.setTimeout(() => {
const codeBeforeCursor = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});
const codeAfterCursor = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: model.getLineCount(),
endColumn: model.getLineMaxColumn(model.getLineCount()),
});
let result = '';
this.switchToLoadingCursor();
this.abortController = new AbortController();
api.chatOneAIGC(
{
message: `你是一个${language}补全器,以下是我的上下文:\n上文内容如下:\n${codeBeforeCursor}\n,下文内容如下:\n${codeAfterCursor}\n请你帮我进行补全,只需要返回对应的代码,不需要进行解释。`,
},
).then(({data, code}) => {
if (code === 1) {
resolve({
items: data?.map((content) => ({
text: content,
range: {
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: content.length,
},
}),
});
} else {
resolve({ items: [] });
}
this.switchToDefaultCursor();
})
}, 500);
});
},
freeInlineCompletions(completions) {
console.log('wenchang freeInlineCompletions', completions);
},
handleItemDidShow(completions) {
console.log('wenchang handleItemDidShow', completions);
},
} as languages.InlineCompletionsProvider);
效果

总结
上述例子只是介绍了如何在 Monaco Editor 中实现类似 Copilot 的代码智能补全功能,但是,我们可以发现,只要内容发生变动,都会触发 Provider ,实际上有些场景下,是不应该触发的,这里还需要写相应的判断条件,而非像例子中所示,任何情况下都进行补全。
最后
欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star
- 大数据分布式任务调度系统——Taier
- 轻量级的 Web IDE UI 框架——Molecule
- 针对大数据领域的 SQL Parser 项目——dt-sql-parser
- 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
- 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
- 一个针对 antd 的组件测试工具库——ant-design-testing
Monaco Editor 中使用在线版 Copilot的更多相关文章
- 手把手教你实现在Monaco Editor中使用VSCode主题
背景 笔者开源了一个小项目code-run,类似codepen的一个工具,其中代码编辑器使用的是微软的Monaco Editor,这个库是直接从VSCode的源码中生成的,只不过是做了一点修改让它支持 ...
- Monaco Editor 中的 Keybinding 机制
一.前言 前段时间碰到了一个 Keybinding 相关的问题,于是探究了一番,首先大家可能会有两个问题:Monaco Editor 是啥?Keybinding 又是啥? Monaco Editor: ...
- 【软工】[技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE
[技术博客] 用Monaco Editor打造接近vscode体验的浏览器IDE 官方文档与重要参考资料 官方demo 官方API调用样例 Playground 官方API Doc,但其搜索框不支持模 ...
- Vue cli2.0 项目中使用Monaco Editor编辑器
monaco-editor 是微软出的一条开源web在线编辑器支持多种语言,代码高亮,代码提示等功能,与Visual Studio Code 功能几乎相同. 在项目中可能会用带代码编辑功能,或者展示代 ...
- js 在浏览器中使用 monaco editor
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Asp.Net Core 使用Monaco Editor 实现代码编辑器
在项目中经常有代码在线编辑的需求,比如修改基于Xml的配置文件,编辑Json格式的测试数据等.我们可以使用微软开源的在线代码编辑器Monaco Editor实现这些功能.Monaco Editor是著 ...
- Monaco Editor 使用入门
以前项目是用ace编辑器的,但是总有些不敬人意的地方.前端事件看见的VS Code编辑器Monaco Editor准备更换下,下面介绍一些使用中遇到的一点问题.代码提示 1.项目引用 import * ...
- 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)
译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...
- TogetherJS – 酷!在网站中添加在线实时协作功能
TogetherJS是一个免费.开源的 JavaScript 库,来自 Mozilla 实验室,可以实现基于 Web 的在线协作功能.把 TogetherJS 添加到您的网站中,您的用户可以在实时的互 ...
- 在Docker中运行torch版的neural style
相关的代码都在Github上,请参见我的Github,https://github.com/lijingpeng/deep-learning-notes 敬请多多关注哈~~~ 在Docker中运行to ...
随机推荐
- 设计模式:可复用面向对象软件的基础 pdf电子书分享
<设计模式:可复用面向对象软件的基础>是引导读者走出软件设计迷宫的指路明灯,凝聚了软件开发界几十年设计经验的结晶.四位顶尖的面向对象领域专家精心选取了最具价值的设计实践,加以分类整理和命名 ...
- Java Playwright 浏览器最大化
Playwright 是一个用于自动化 Web 应用测试的现代工具,支持多种语言(包括 Java)及多个浏览器(如 Chromium.Firefox 和 WebKit).它提供了一致的 API 来控制 ...
- 硬盘空间消失之谜:Linux 服务器存储排查与优化全过程
前言 最近线上服务经常出现一些奇奇怪怪的问题,比如网页上的静态资源加载不出来,或者请求后端莫名报错,又或者 Redis 报错- 当我 SSH 登录到服务器上时,更不对劲了,敲个命令都卡顿- 如果是以前 ...
- 2.mysql授权认证
权限系统介绍 ● 什么是权限系统 权限系统是授予来自某个主机的某个用户可以查询.插入.修改.删除等数据库操作的权限 不能明确的指定拒接某个用户的连接 权限控制(授权与收回)的执行语句包括 create ...
- 武装你的WEBAPI-OData使用Endpoint
本文属于 OData 系列文章 Introduction 更新: 由于新版的 OData 已经默认使用了 endpoint 模式(Microsoft.AspNetCore.OData 8.0.0),不 ...
- ORM框架与数据库交互
title: ORM框架与数据库交互 date: 2024/12/22 updated: 2024/12/22 author: cmdragon excerpt: 对象关系映射(Object-Rela ...
- 【JavaWeb】前后端分离SpringBoot项目快速排错指南
1 发起业务请求 打开浏览器开发者工具,同时显示网络(Internet)和控制台(console) 接着,清空控制台和网络的内容,如下图 然后,点击你的业务按钮,发起请求. 首先看控制台有没有报错信息 ...
- [转]xmanager和xshell什么关系 xmanager怎么使用
xmanager是一款小巧实用且运行于Windows系统上的X服务器软件,可以帮助用户快速连接并访问Unix/Linux服务器.那xmanager和xshell什么关系,xmanager怎么使用,本文 ...
- KMS for Windows 11
I. 镜像下载 Windows 镜像下载地址:站点1,站点2 II. 手动激活 参考文档:Easy ways to activate Windows 11 for FREE without a pro ...
- 字节二面:你怎么理解信道是golang中的顶级公民
1. 信道是golang中的顶级公民 goroutine结合信道channel是golang中实现并发编程的标配. 信道给出了一种不同于传统共享内存并发通信的新思路,以一种通道复制的思想解耦了并发编程 ...