opensumi / core

🚀 A framework helps you quickly build AI Native IDE products.
https://opensumi.com
MIT License
2.98k stars 381 forks source link

[FEATURE] 关于多行补全和智能重写的能力支持 #3860

Closed Ricbet closed 2 months ago

Ricbet commented 3 months ago

背景

https://docs.cursor.com/tab/overview

cursor 的 copilot++ 的代码补全功能不仅可以行补全和块补全,还能一次性进行多行补全(在光标的上下 xx 范围内提示补全)

类似这种效果 image

除此之外还有“智能重写”的能力,与多行补全不同的是,智能重写会将光标上下 xx 范围内对代码进行重写,然后以悬浮窗口的形式提供预览 image

两者通常是一起搭配出现的

https://github.com/user-attachments/assets/ffaea3c5-bcbc-48fb-b4e0-b84511d0810a

他会以当前活动光标的位置信息来决定是要显示多行补全还是显示智能重写的预览。

与传统的智能补全有什么不同?

传统的智能补全解决的是用户的 “写新代码” 场景。 也就是说,当你要开始写一段新代码了,你回个车或者先写个注释,亦或者先输入一点代码,AI 会自动帮你 “续写” 后面的内容,AI 会认为你光标之前的代码就是正确的。

但在真实的编码工作中,并不全是 “写新代码”,大多数是 改写代码。 所以 多行补全 和 智能重写 解决的就是这个问题,他能对光标所在的局部区域进行 改写 和 新增 的推荐,与传统的 智能补全 相融合,让 AI 能参与更多的 编码任务

实现思路拆解

从体验的效果以及源码来看,通常是会在编码完一定时间之后,触发一次补全。 与业界的补全方案不同的是,cursor 会有某个策略来决定补全的范围,而不是简单的只是在光标位置后面进行补全。

然后 cursor 会将补全结果与补全范围内的代码进行 diff 计算,根据计算的结果可能会出现以下 3 种情况

  1. 只有当前光标位置有差异,则与传统的代码补全表现一致
  2. 多行文本有差异,且光标位置在"差异代码块"前面,则显示 多行补全
  3. 多行文本有差异,且光标位置在"差异代码块"后面,则显示 智能重写预览

模块 API 实现

待补充,会结合 https://github.com/opensumi/core/issues/3853 一起来实现

Ricbet commented 3 months ago

多行补全需求拆解

多行补全示例(仅新增) image image

智能重写示例(仅修改) image

智能重写示例(有删除) image image

假定原始文本是:

'merge-conflicts.o.non-conflicts.from.left': 'target branch: {0} places',
'merge-conflicts.non-conflicts.from.right': 'source branch: {0} places',
'merge-conflicts.non-conflicts.from.base': 'base branch: {0} places',

补全的文本是:

'merge-conflicts.opensumi.non-conflicts.from.left': 'target branch: {0} places',
'merge-conflicts.opensumi.non-conflicts.from.right': 'source branch: {0} places',
'merge-conflicts.opensumi.non-conflicts.from.base': 'base branch: {0} places',

仅新增

对于 仅新增 的情况,从多行补全的最终渲染效果来说,我们应该按 字符 级去进行 diff 差异计算。 因为从补全的文本结果来看,有一部分是编辑器里就有的原始字符,这部分你不能去覆盖他,只能显示编辑器里没有的字符。

我们把原始文本和补全文本都按 "" 去做 split,然后一个个去比较字符,并构造数据结构

我们假设定义的数据结构是

interface ICharChange {
  // 下标
  index: number,
  /**
  * 类型
  * unchanged: 表示这个字符(或词)与原始文本相等
  */
  mode: 'added' | 'removed' | 'unchanged',
  // 文本 
  content: string
}

将 diff 计算过程中的结果构造出以上数据。 diff 的算法有很多种,我们可以采用窗口滑动算法来计算他们的文本差异。 当计算出来后,利用 monaco 的 decoration api 则可以渲染阴影部分的字符

有删除

对于 有删除 的情况比较特殊,因为没法通过 阴影字符 去表达已删除。所以只能打开智能重写的预览窗口。

从智能重写的预览窗口来看,差异的色块部分并不是按 字符 去渲染的,而是以 为单位。 我们可以用正则表达式去切 ,然后还是与上述一样,进行差异 diff 计算并构造数据结构。

右侧的预览窗口我们可以用 monaco 的 content widget api 去渲染,然后在里面创建一个小的 monaco editor 去展示

Ricbet commented 2 months ago
Ricbet commented 2 months ago

智能重写 PR: https://github.com/opensumi/core/pull/3962