vscode把主界面分成了各种part,它们都是Part类的子类,比如editorPart、statusbarPart、titlebarPart等。
各个Part渲染到页面上的主要流程如下,以titlebarPart为例。
-
使用枚举类型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' }
-
每种Part都是Part类的子类。在TitlebarPart类的构造函数中,会调用父类的构造函数,从而执行了
registerPart()
方法,该方法的作用是将TitlebarPart实例存入一个名叫parts的map中,以便在将titlebarPart渲染到DOM上时可以获取到该实例。 -
对TitlebarPart类进行注入(vscode的依赖注入)。由于各种part服务在整个过程中一个实例,因此使用
registerSingleton()
函数进行依赖注入,注意registerSingleton()
并没有实例化TitlebarPart类,只是使用SyncDescriptor记录了它的类型。 -
当加载主页面的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);
-
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); });
-
createWorkbenchLayout()
为每个titlebarPart创建布局,并注册onDisVisibilityChange()
事件,用于切换part的可见性。在创建布局时,调用了createGridDescriptor()
,会设置每个Part的宽高以及activitybarPart、editorPart和panelPart这三个Part在不同情况下的位置。 -
layout()
会设置容器的定位方式(relative)、四个方向上的距离(top、bottom、left、right)以及宽高,同时注册了一些事件。layout()
事件执行后,主界面已经分好了块,但是并没有图标和内容。