[归档][2022-05-16]opensumi看码记录

贡献点(contribution)

@opensumi/ide-xxx 提供向各个模块提供贡献点的数据结构,如果想为某个模块提供贡献点,要做的是实现这个模块的数据结构,完成自己的逻辑,然后把自己实现的这个 class 注册到 extends BrowserModule 中的class 中。

比如:

所有向编辑器模块贡献功能的贡献点统一使用 BrowserEditorContribution

要写一个 contributions,需要实现以下的方法

typescript 复制代码
interface BrowserEditorContribution {
    /**
     * 用来在合适的时机向 `ResourceService` 注册可以在编辑器内打开的资源。
     *
     * 为了让一个 uri 能够在编辑器中被打开,首先需要向 `ResourceService` 注册一个用于解析 uri 至一个编辑器资源(`IResource`) 的 `IResourceProvider`。
     * 它的主要职责是在这个 uri 在编辑器标签 Tab 上显示时提供它的名称、图标、是否被编辑等状态,以及相应这个 tab 被关闭时的回调等等。
     *
     * @param resourceService
     */
    registerResource?(resourceService: ResourceService): void;
    /**
     * 用来在合适的时机向 `EditorComponentRegistry` 注册编辑器组件、打开方式等功能。
     *
     * 一个 uri 对应的编辑器资源 (`IResource`) 需要能够在编辑器中展示,还需要为它注册对应的一个或者多个打开方式,以及对应打开方式使用的 React 组件。
     * @param editorComponentRegistry
     */
    registerEditorComponent?(editorComponentRegistry: EditorComponentRegistry): void;
    registerEditorDocumentModelContentProvider?(registry: IEditorDocumentModelContentRegistry): void;
    /**
     * @deprecated
     * @param editorActionRegistry
     */
    registerEditorActions?(editorActionRegistry: IEditorActionRegistry): void;
    /**
     * 当进入 IDE 时,编辑器会尝试恢复上一次打开的编辑器组和组内打开的文件
     * 完成后会执行 onDidRestoreState 这个 hook
     */
    onDidRestoreState?(): void;
    registerEditorFeature?(registry: IEditorFeatureRegistry): any;
}

比如说注册可以在编辑器打开的资源,要实现 ResourceService 一个抽象类。

typescript 复制代码
declare abstract class ResourceService {
    /**
     * 注册一个新的 ResourceProvider 会触发该事件
     */
    readonly onRegisterResourceProvider: Event<IResourceProvider>;
    /**
     * 写在一个 ResourceProvider 会触发该事件
     */
    readonly onUnregisterResourceProvider: Event<IResourceProvider>;
    /**
     * 根据uri获得一个资源信息
     * 如果uri没有对应的resource提供者,则会返回null
     * @param uri
     */
    abstract getResource(uri: URI): Promise<IResource | null>;
    /**
     * 注册一个resource提供方
     * @param provider
     */
    abstract registerResourceProvider(provider: IResourceProvider): IDisposable;
    /**
     * 是否能关闭一个资源
     */
    abstract shouldCloseResource(resource: IResource, openedResources: IResource[][]): Promise<boolean>;
    abstract getResourceDecoration(uri: URI): IResourceDecoration;
    abstract getResourceSubname(resource: IResource, groupResources: IResource[]): string | null;
    /**
     * 销毁一个 resource
     * @param resource
     */
    abstract disposeResource(resource: IResource<any>): void;
    /**
     * 是否存在 provider 可以处理某个 uri
     */
    abstract handlesUri(uri: URI): boolean;
}

registerResource(resourceService: ResourceService) {
    // 处理 git 协议的 editor tab 展示信息
    resourceService.registerResourceProvider(xxxProvider);
 }

要注册的资源有一个别名叫 Provider(可以理解提供资源的一方,资源 == Provider)

xxxProvider 就是要注册的资源,provider 实现了 IResourceProvider

typescript 复制代码
interface IResourceProvider {
    scheme?: string;
    /**
     * 一个 provider 是否处理某个资源
     * 返回优先级,这个值越高的 provider 越优先处理, 小于 0 表示不处理
     * 这个比较的计算结果会被缓存,仅仅当 provider 数量变更时才会清空
     * 存在 handlesURI 时, 上面的scheme会被忽略
     */
    handlesUri?(uri: URI): number;
    provideResource(uri: URI): MaybePromise<IResource>;
    provideResourceSubname?(resource: IResource, groupResources: IResource[]): string | null;
    shouldCloseResource?(resource: IResource, openedResources: IResource[][]): MaybePromise<boolean>;
    onDisposeResource?(resource: IResource): void;
}

registerEditorDocumentModelContentProvider 注册 git content provider provider 提供 doc / 文档的内容和 meta 信息

registerEditorDocumentModelContentProvider(registry: IEditorDocumentModelContentRegistry) {
    // 注册 git content provider provider 提供 doc / 文档的内容和 meta 信息
    registry.registerEditorDocumentModelContentProvider(xxxProvider);
 }

此时的 xxxProvider 也是一种资源,注入到 EditorDocumentModelContent 中

xxxProvider 是对 IEditorDocumentModelContentProvider 的实现

php 复制代码
interface IEditorDocumentModelContentProvider {
    /**
     * 是否处理这个Scheme的uri
     * 权重等级等同于 handlesUri => 10
     * @param scheme
     */
    handlesScheme?(scheme: string): MaybePromise<boolean>;
    /**
     * 处理一个URI的权重, -1表示不处理, 如果存在handlesUri, handlesScheme将被忽略
     * @param scheme
     */
    handlesUri?(uri: URI): MaybePromise<number>;
    /**
     * 提供文档内容
     * @param uri
     * @param encoding 以某种编码获取内容
     */
    provideEditorDocumentModelContent(uri: URI, encoding?: string): MaybePromise<string>;
    /**
     * 这个文档是否只读(注意只读和无法保存的区别)
     * @param uri
     */
    isReadonly(uri: URI): MaybePromise<boolean>;
    /**
     * 保存一个文档, 如果不存在这个方法,那这个文档无法被保存
     * 当文档无法保存时, 文档永远不会进入dirty状态,并且来自provider的
     * @param uri
     * @param content
     * @param baseContent dirty前的内容
     * @param ignoreDiff 无视diff错误, 强行覆盖保存
     */
    saveDocumentModel?(uri: URI, content: string, baseContent: string, changes: IEditorDocumentChange[], encoding?: string, ignoreDiff?: boolean, eol?: EOL): MaybePromise<IEditorDocumentModelSaveResult>;
    /**
     * 为一个uri提供喜好的语言id,返回undefined则交由编辑器自己去判断
     * @param uri
     */
    preferLanguageForUri?(uri: URI): MaybePromise<string | undefined>;
    provideEOL?(uri: URI): MaybePromise<EOL>;
    /**
     * 为一个uri提供encoding信息, 如果不实现,则默认UTF-8
     * @param uri;
     */
    provideEncoding?(uri: URI): MaybePromise<string>;
    /**
     * 提供这个文件当前内容的md5值,如果不实现这个函数,会使用content再执行计算
     * @param uri
     */
    provideEditorDocumentModelContentMd5?(uri: URI, encoding?: string): MaybePromise<string | undefined>;
    /**
     * 文档内容变更事件,交由modelManager决定是否处理
     */
    onDidChangeContent: Event<URI>;
    onDidDisposeModel?(uri: URI): void;
    /**
     * 是否永远显示 dirty
     * 有些类型的文档(untitled)可能刚创建就是 dirty,允许它以空文件的状态保存
     */
    isAlwaysDirty?(uri: URI): MaybePromise<boolean>;
    /**
     * 是否关闭自动保存功能
     */
    closeAutoSave?(uri: URI): MaybePromise<boolean>;
    /**
     * 猜测编码
     */
    guessEncoding?(uri: URI): Promise<string | undefined>;
}

用一个抽象类来实现 IEditorDocumentModelContentProvider,抽象类中有一个抽象方法。

如何实现抽象方法?

scala 复制代码
@Injectable()
export class IndexModule extends BrowserModule {
  providers: Provider[] = [
    SchemeContribution,
    ChangesTreeContribution,
    CodeReviewContribution,
    DiffFoldingContribution,
    {
      token: AbstractSCMDocContentProvider, // 以抽象类为token
      useClass: AoneGitDocContentProvider, // 把具体的实现放在 useClass 中,后续xxxProvider 的方法调用来自于 useClass 中的类方法(好特么绕)
    }
  ];
}

题外话: 代码通过 useClass 的方式把 Provider 的具体行为传入。行为中又灌入了 api Injectable,这样 GitDocContentProvider 可以使用 api.XXXX 来进行接口调用,到 api 这层,就是业务方自定义了(真特么的绕啊...)

题外话2:registerToolbarItems 中,注册 toolbar 的 item,registerItem 中参数的数据结构如下。

字段 when 表示的是是否在视图层展示。

php 复制代码
interface ICoreMenuItem {
    order?: number;
    /**
     * 决定是否在视图层展示
     */
    when?: string | ContextKeyExpr; // not true/false !!!
    /**
     * 单独变更此 menu action 的 args
     */
    argsTransformer?: ((...args: any[]) => any[]);
}


// usage

{
  id: ChangesTreeViewId,
  when: '!sourceMode' // 通过表达式得到 true/false 的结果。有点像 sql 语法
}

再比如:文件变更树。需要用到组件/命令/toolbar,则需要implements ComponentContribution, CommandContribution, TabBarToolbarContribution

// usage

@Domain(ComponentContribution, CommandContribution, TabBarToolbarContribution)
export class ChangesTreeContribution implements ComponentContribution, CommandContribution, TabBarToolbarContribution {
  // MR Explorer 只注册容器
  registerComponent(registry: ComponentRegistry) {
    registry.register(
      'mr-explorer',
      [
        {
          id: ChangesTreeViewId,
          name: localize('changes.tree', '变更信息'),
          weight: 5,
          priority: 8,                // 按权重 决定排列的顺序
          collapsed: false,
          component: ChangesTreeView, // **注册自己写的组件**
          when: '!sourceMode'
        }
      ],
      {
        title: localize('mr.explorer', '代码变更'),
        iconClass: getIcon('PR'),
        priority: 9,
        containerId: 'mr-explorer',
      },
    );
  }
}

// 如果要注册命令,则需要 implements CommandContribution
@Domain(CommandContribution)
export class xxCommandContribution implements CommandContribution {
  registryCommands(registry: CommandRegistry): void {
    commands.registerCommand(command, {      //command 为复合结构, { id: 唯一标识,...others 非必需}
      execute: () => {
        // do yourself code...
      },
    });
  }
}

// 同理,如果需要对某个tab的 toolbar 添加小部件,需要 implements TabBarToolbarContribution
@Domain(TabBarToolbarContribution)
export class xxCommandContribution implements TabBarToolbarContribution {
  registerToolbarItems(registry: ToolbarRegistry) {
    registry.registerItem({
      id: ChangesTreeCommands.ToggleTree.id, // 如果想使用命令达到同样的效果,需要配置已注册的 command
      command: ChangesTreeCommands.ToggleTree.id, //应该是用 registryCommand 中的command 命令,这样写不是跟id一样??
      viewId: ChangesTreeViewId, // 要注册到哪个 tab 上去,需要标志 tab 的 id
      label: localize('codereview.tree.showTreeModel', '树形模式'),
      toggledWhen: 'changes-tree-mode', //全局注册的上下文 key globalContextKeyService
      order: 4, // toolbar 的顺序
    });
}

看到这儿相信你一定会有这种感觉:opensumi 只是一个框架,它只负责对外提供接口和生命周期,具体要实现一个编辑器,需要自己来完成,像 vue 框架一样,语法(数据结构)已经给定,至于是杀猪还是煮菜端看业务方如何使用。(Yep,我又在说废话了,手动狗头:)

service 一般为 Injectable@Autowired

链路真的太长太长了,看着看着不知道看到哪里了

业务逻辑

文件变更树:ChangesTreeContribution

kotlin 复制代码
class ChangesTreeContribution implements ComponentContribution, CommandContribution, TabBarToolbarContribution

changesTreeModelService 中结构出来 decorationService / labelService / commandService,注入到 ChangeTreeNode 组件中

changeTree 本质上是用 FilterableRecycleTree(RecycleTreeFilterDecorator(RecycleTree)) 组件实现

编辑器主干区域:CodeReviewContribution

class CodeReviewContribution extends WithEventBus implements CommandContribution, ClientAppContribution, CommentsContribution, NextMenuContribution, ComponentContribution, BrowserEditorContribution, KeybindingContribution, SlotRendererContribution

依赖 8 个 contribution

相关推荐
清风徐来QCQ2 小时前
跨域问题(CORS-Cross-Origin Resource Sharing跨域资源共享)
前端
DanCheOo2 小时前
我写了一个 AI 代码质量流水线,一行命令搞定 Review + 修复 + 测试 + 报告
前端·ai编程
yaaakaaang2 小时前
(六)前端,如此简单!--- 三类通讯
前端
Jinuss3 小时前
源码分析之React中副作用Effect全流程
前端·javascript·react.js
踩着两条虫3 小时前
VTJ.PRO 在线应用开发平台的低代码引擎与DSL系统
前端·低代码·ai编程
Yiyaoshujuku3 小时前
医院API接口,从医院真实世界数据HIS、LJS、EMR、PACS系统到医院药品流向数据....
大数据·前端·人工智能
Shirley~~3 小时前
力扣hot100:相交链表
前端·算法
Jay叶湘伦3 小时前
【极简】用 Vue 写一个 ChatGPT 前端应用,支持连续对话、Markdown 渲染与本地记忆
前端·vue.js·chatgpt
大家的林语冰3 小时前
《前端周刊》尤大官宣 Vite 8 稳定版首发!npm 新官网?React 官网更新。focusgroup 新功能!
前端·javascript·vite