vscode源码之各种part

553 阅读2分钟

vscode把主界面分成了各种part,它们都是Part类的子类,比如editorPart、statusbarPart、titlebarPart等。 148b3202-24e3-45cb-87bd-7a9be989a48a.png 各个Part渲染到页面上的主要流程如下,以titlebarPart为例。

  1. 使用枚举类型Parts定义了七种类型的Part,其中workbench.parts.titlebar 最后会作为对应DOM元素的id。

    //在src/vs/workbench/services/layout/browser/layoutService.ts
    export const enum Parts {
        TITLEBAR_PART = 'workbench.parts.titlebar',
        BANNER_PART = 'workbench.parts.banner',
        ACTIVITYBAR_PART = 'workbench.parts.activitybar',
        SIDEBAR_PART = 'workbench.parts.sidebar',
        PANEL_PART = 'workbench.parts.panel',
        EDITOR_PART = 'workbench.parts.editor',
        STATUSBAR_PART = 'workbench.parts.statusbar'
    }
    
  2. 每种Part都是Part类的子类。在TitlebarPart类的构造函数中,会调用父类的构造函数,从而执行了registerPart() 方法,该方法的作用是将TitlebarPart实例存入一个名叫parts的map中,以便在将titlebarPart渲染到DOM上时可以获取到该实例。

  3. 对TitlebarPart类进行注入(vscode的依赖注入)。由于各种part服务在整个过程中一个实例,因此使用registerSingleton() 函数进行依赖注入,注意registerSingleton() 并没有实例化TitlebarPart类,只是使用SyncDescriptor记录了它的类型。

  4. 当加载主页面的HTML文件时,会进入到workbench.ts 文件中的startup() 函数中,里面有几个重要的函数:

    // Layout
    this.initLayout(accessor);
    // Render Workbench
    this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService);
    // Workbench Layout
    this.createWorkbenchLayout();
    // Layout
    this.layout();
    

    首先,initLayout() 会在accessor.get() 方法里实例化TitlebarPart类,accessor.get() 最后会进入到_createInstance() 里,最后return <T>new ctor(...[...args, ...serviceArgs]) ,返回创建出来的对象,这里ctor表示里之前用SyncDescriptor类记录下的类型。

    // Parts
    this.editorService = accessor.get(IEditorService);
    this.editorGroupService = accessor.get(IEditorGroupsService);
    this.panelService = accessor.get(IPanelService);
    this.viewletService = accessor.get(IViewletService);
    this.viewDescriptorService = accessor.get(IViewDescriptorService);
    this.titleService = accessor.get(ITitleService);
    this.notificationService = accessor.get(INotificationService);
    this.activityBarService = accessor.get(IActivityBarService);
    this.statusBarService = accessor.get(IStatusbarService);
    accessor.get(IBannerService);
    
  5. renderWorkbench() 会使用createPart() 为titlebarPart生成一个div或者footer元素作为包裹它的容器(即parent,在渲染到DOM节点上用到,使用getContainer() 获取),getPart(id).create() 会创建titleArea和contentArea(titlebarPart里面的内容元素)。

    // Create Parts
            [
                { id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] },
                { id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] },
                { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892
                { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
                { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } },
                { id: Parts.PANEL_PART, role: 'none', classes: ['panel', positionToString(this.state.panel.position)] },
                { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }
            ].forEach(({ id, role, classes, options }) => {
                //createPart()返回了div或者footer
                const partContainer = this.createPart(id, role, classes);
                //getPart(id)在map中找key=id的Part实例,而partContainer是该Part实例的parent
                this.getPart(id).create(partContainer, options);
            });
    
  6. createWorkbenchLayout() 为每个titlebarPart创建布局,并注册onDisVisibilityChange() 事件,用于切换part的可见性。在创建布局时,调用了createGridDescriptor() ,会设置每个Part的宽高以及activitybarPart、editorPart和panelPart这三个Part在不同情况下的位置。

  7. layout() 会设置容器的定位方式(relative)、四个方向上的距离(top、bottom、left、right)以及宽高,同时注册了一些事件。layout() 事件执行后,主界面已经分好了块,但是并没有图标和内容。