百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

万字详文:微软 VSCode IDE 源码分析揭秘

liuian 2025-02-21 14:17 52 浏览

作者: zanewang,腾讯 CSIG web 前端开发工程师

目录

  • (1)简介
  • (2)技术架构
  • (3)启动主进程
  • (4)实例化服务
  • (5)事件分发
  • (6)进程通信
  • (7)主要窗口
  • (8)开发调试

1.简介

Visual Studio Code(简称 VSCode) 是开源免费的 IDE 编辑器,原本是微软内部使用的云编辑器(Monaco)。

git 仓库地址:
https://github.com/microsoft/vscode

通过 Eletron 集成了桌面应用,可以跨平台使用,开发语言主要采用微软自家的 TypeScript。整个项目结构比较清晰,方便阅读代码理解。成为了最流行跨平台的桌面 IDE 应用

微软希望 VSCode 在保持核心轻量级的基础上,增加项目支持,智能感知,编译调试。

编译安装

下载最新版本,目前我用的是 1.37.1 版本官方的 wiki 中有编译安装的说明 How to Contribute

Linux, Window, MacOS 三个系统编译时有些差别,参考官方文档,在编译安装依赖时如果遇到 connect timeout, 需要进行科学上网。

需要注意的一点 运行环境依赖版本 Nodejs x64 version >= 10.16.0, < 11.0.0, python 2.7(3.0 不能正常执行)

2.技术架构

Electron

Electron 是一个使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生程序的框架,它负责比较难搞的部分,你只需把精力放在你的应用的核心上即可 (Electron = Node.js + Chromium + Native API)

Monaco Editor

Monaco Editor是微软开源项目, 为 VS Code 提供支持的代码编辑器,运行在浏览器环境中。编辑器提供代码提示,智能建议等功能。供开发人员远程更方便的编写代码,可独立运行。

TypeScript

TypeScript是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程

目录结构

├── build # gulp编译构建脚本

├── extensions # 内置插件

├── product.json # App meta信息

├── resources # 平台相关静态资源

├── scripts # 工具脚本,开发/测试

├── src # 源码目录

└── typings # 函数语法补全定义

└── vs

├── base # 通用工具/协议和UI库

│ ├── browser # 基础UI组件,DOM操作

│ ├── common # diff描述,markdown解析器,worker协议,各种工具函数

│ ├── node # Node工具函数

│ ├── parts # IPC协议(Electron、Node),quickopen、tree组件

│ ├── test # base单测用例

│ └── worker # Worker factory和main Worker(运行IDE Core:Monaco)

├── code # VSCode主运行窗口

├── editor # IDE代码编辑器

| ├── browser # 代码编辑器核心

| ├── common # 代码编辑器核心

| ├── contrib # vscode 与独立 IDE共享的代码

| └── standalone # 独立 IDE 独有的代码

├── platform # 支持注入服务和平台相关基础服务(文件、剪切板、窗体、状态栏)

├── workbench # 工作区UI布局,功能主界面

│ ├── api #

│ ├── browser #

│ ├── common #

│ ├── contrib #

│ ├── electron-browser #

│ ├── services #

│ └── test #

├── css.build.js # 用于插件构建的CSS loader

├── css.js # CSS loader

├── editor # 对接IDE Core(读取编辑/交互状态),提供命令、上下文菜单、hover、snippet等支持

├── loader.js # AMD loader(用于异步加载AMD模块)

├── nls.build.js # 用于插件构建的NLS loader

└── nls.js # NLS(National Language Support)多语言loader

核心层

  • base: 提供通用服务和构建用户界面
  • platform: 注入服务和基础服务代码
  • editor: 微软 Monaco 编辑器,也可独立运行使用
  • wrokbench: 配合 Monaco 并且给 viewlets 提供框架:如:浏览器状态栏,菜单栏利用 electron 实现桌面程序

核心环境

整个项目完全使用 typescript 实现,electron 中运行主进程和渲染进程,使用的 api 有所不同,所以在 core 中每个目录组织也是按照使用的 api 来安排,运行的环境分为几类:

  • common: 只使用 javascritp api 的代码,能在任何环境下运行
  • browser: 浏览器 api, 如操作 dom; 可以调用 common
  • node: 需要使用 node 的 api,比如文件 io 操作
  • electron-brower: 渲染进程 api, 可以调用 common, brower, node, 依赖electron renderer-process API
  • electron-main: 主进程 api, 可以调用: common, node 依赖于electron main-process AP

3.启动主进程

Electron 通过 package.json 中的 main 字段来定义应用入口。

main.js 是 vscode 的入口。

  • src/main.js
  • _ vs/code/electron-main/main.ts
  • _ vs/code/electron-main/app.ts
  • _ vs/code/electron-main/windows.ts
  • _ vs/workbench/electron-browser/desktop.main.ts * vs/workbench/browser/workbench.ts

app.once('ready', function () {

//启动追踪,后面会讲到,跟性能检测优化相关。

if (args['trace']) {

// @ts-ignore

const contentTracing = require('electron').contentTracing;

const traceOptions = {

categoryFilter: args['trace-category-filter'] || '*',

traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'

};

contentTracing.startRecording(traceOptions, () => onReady());

} else {

onReady();

}

});

function onReady() {

perf.mark('main:appReady');

Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {

//1. 这里尝试获取本地配置信息,如果有的话会传递到startup

if (locale && !nlsConfiguration) {

nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);

}

if (!nlsConfiguration) {

nlsConfiguration = Promise.resolve(undefined);

}

nlsConfiguration.then(nlsConfig => {

//4. 首先会检查用户语言环境配置,如果没有设置默认使用英语

const startup = nlsConfig => {

nlsConfig._languagePackSupport = true;

process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);

process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';

perf.mark('willLoadMainBundle');

//使用微软的loader组件加载electron-main/main文件

require('./bootstrap-amd').load('vs/code/electron-main/main', () => {

perf.mark('didLoadMainBundle');

});

};

// 2. 接收到有效的配置传入是其生效,调用startup

if (nlsConfig) {

startup(nlsConfig);

}

// 3. 这里尝试使用本地的应用程序

// 应用程序设置区域在ready事件后才有效

else {

let appLocale = app.getLocale();

if (!appLocale) {

startup({ locale: 'en', availableLanguages: {} });

} else {

// 配置兼容大小写敏感,所以统一转换成小写

appLocale = appLocale.toLowerCase();

// 这里就会调用config服务,把本地配置加载进来再调用startup

lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale).then(nlsConfig => {

if (!nlsConfig) {

nlsConfig = { locale: appLocale, availableLanguages: {} };

}

startup(nlsConfig);

});

}

}

});

}, console.error);

}

vs/code/electron-main/main.ts

electron-main/main 是程序真正启动的入口,进入 main process 初始化流程.

这里主要做了两件事情:

  1. 初始化 Service
  2. 启动主实例

直接看 startup 方法的实现,基础服务初始化完成后会加载 CodeApplication, mainIpcServer, instanceEnvironment,调用 startup 方法启动 APP

private async startup(args: ParsedArgs): Promise {

//spdlog 日志服务

const bufferLogService = new BufferLogService();

// 1. 调用 createServices

const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService);

try {

// 1.1 初始化Service服务

await instantiationService.invokeFunction(async accessor => {

// 基础服务,包括一些用户数据,缓存目录

const environmentService = accessor.get(IEnvironmentService);

// 配置服务

const configurationService = accessor.get(IConfigurationService);

// 持久化数据

const stateService = accessor.get(IStateService);

try {

await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);

} catch (error) {

// 抛出错误对话框

this.handleStartupDataDirError(environmentService, error);

throw error;

}

});

// 1.2 启动实例

await instantiationService.invokeFunction(async accessor => {

const environmentService = accessor.get(IEnvironmentService);

const logService = accessor.get(ILogService);

const lifecycleService = accessor.get(ILifecycleService);

const configurationService = accessor.get(IConfigurationService);

const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);

bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());

once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());

return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();

});

} catch (error) {

instantiationService.invokeFunction(this.quit, error);

}

}

Service

这里通过 createService 创建一些基础的 Service

运行环境服务 EnvironmentService

src/vs/platform/environment/node/environmentService.ts

通过这个服务获取当前启动目录,日志目录,操作系统信息,配置文件目录,用户目录等。

日志服务 MultiplexLogService

src/vs/platform/log/common/log.ts

默认使用控制台日志 ConsoleLogMainService其中包含性能追踪和释放信息,日志输出级别

配置服务 ConfigurationService

src/vs/platform/configuration/node/configurationService.ts

从运行环境服务获取内容

生命周期服务 LifecycleService

src/vs/platform/lifecycle/common/lifecycleService.ts

监听事件,electron app 模块 比如:ready, window-all-closed,before-quit

可以参考官方electron app 文档

状态服务 StateService

src/vs/platform/state/node/stateService.ts

通过 FileStorage 读写 storage.json 存储,里记录一些与程序运行状态有关的键值对

请求服务 RequestService

src/vs/platform/request/browser/requestService.ts

这里使用的是原生 ajax 请求,实现了 request 方法

主题服务 ThemeMainService

src/vs/platform/theme/electron-main/themeMainService.ts

这里只设置背景颜色,通过 getBackgroundColor 方法 IStateService 存储

签名服务 SignService

src/vs/platform/sign/node/signService.ts

private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] {

//服务注册容器

const services = new ServiceCollection();

const environmentService = new EnvironmentService(args, process.execPath);

const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment

services.set(IEnvironmentService, environmentService);

const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);

process.once('exit', () => logService.dispose());

//日志服务

services.set(ILogService, logService);

//配置服务

services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource));

//生命周期

services.set(ILifecycleService, new SyncDescriptor(LifecycleService));

//状态存储

services.set(IStateService, new SyncDescriptor(StateService));

//网络请求

services.set(IRequestService, new SyncDescriptor(RequestService));

//主题设定

services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));

//签名服务

services.set(ISignService, new SyncDescriptor(SignService));

return [new InstantiationService(services, true), instanceEnvironment];

}

4.实例化服务

SyncDescriptor 负责注册这些服务,当用到该服务时进程实例化使用

src/vs/platform/instantiation/common/descriptors.ts

export class SyncDescriptor {

readonly ctor: any;

readonly staticArguments: any[];

readonly supportsDelayedInstantiation: boolean;

constructor(ctor: new (...args: any[]) => T, staticArguments: any[] = [], supportsDelayedInstantiation: boolean = false) {

this.ctor = ctor;

this.staticArguments = staticArguments;

this.supportsDelayedInstantiation = supportsDelayedInstantiation;

}

}

main.ts 中 startup 方法调用 invokeFunction.get 实例化服务

await instantiationService.invokeFunction(async accessor => {

const environmentService = accessor.get(IEnvironmentService);

const configurationService = accessor.get(IConfigurationService);

const stateService = accessor.get(IStateService);

try {

await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService);

} catch (error) {

// Show a dialog for errors that can be resolved by the user

this.handleStartupDataDirError(environmentService, error);

throw error;

}

});

get 方法调用
_getOrCreateServiceInstance,这里第一次创建会存入缓存中下次实例化对象时会优先从缓存中获取对象。

src/vs/platform/instantiation/common/instantiationService.ts

invokeFunction(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R {

let _trace = Trace.traceInvocation(fn);

let _done = false;

try {

const accessor: ServicesAccessor = {

get: (id: ServiceIdentifier, isOptional?: typeof optional) => {

if (_done) {

throw illegalState('service accessor is only valid during the invocation of its target method');

}

const result = this._getOrCreateServiceInstance(id, _trace);

if (!result && isOptional !== optional) {

throw new Error(`[invokeFunction] unknown service '${id}'`);

}

return result;

}

};

return fn.apply(undefined, [accessor, ...args]);

} finally {

_done = true;

_trace.stop();

}

}

private _getOrCreateServiceInstance(id: ServiceIdentifier, _trace: Trace): T {

let thing = this._getServiceInstanceOrDescriptor(id);

if (thing instanceof SyncDescriptor) {

return this._createAndCacheServiceInstance(id, thing, _trace.branch(id, true));

} else {

_trace.branch(id, false);

return thing;

}

}

vs/code/electron-main/app.ts

这里首先触发 CodeApplication.startup()方法, 在第一个窗口打开 3 秒后成为共享进程,

async startup(): Promise {

...

// 1. 第一个窗口创建共享进程

const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);

const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));

this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {

this._register(new RunOnceScheduler(async () => {

const userEnv = await getShellEnvironment(this.logService, this.environmentService);

sharedProcess.spawn(userEnv);

}, 3000)).schedule();

});

// 2. 创建app实例

const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient);

// 3. 打开一个窗口 调用

const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));

// 4. 窗口打开后执行生命周期和授权操作

this.afterWindowOpen();

...

//vscode结束了性能问题的追踪

if (this.environmentService.args.trace) {

this.stopTracingEventually(windows);

}

}

openFirstWindow 主要实现
CodeApplication.openFirstWindow 首次开启窗口时,创建 Electron 的 IPC,使主进程和渲染进程间通信。window 会被注册到 sharedProcessClient,主进程和共享进程通信根据 environmentService 提供的参数(path,uri)调用 windowsMainService.open 方法打开窗口

private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] {

...

// 1. 注入Electron IPC Service, windows窗口管理,菜单栏等服务

// 2. 根据environmentService进行参数配置

const macOpenFiles: string[] = (global).macOpenFiles;

const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;

const hasCliArgs = hasArgs(args._);

const hasFolderURIs = hasArgs(args['folder-uri']);

const hasFileURIs = hasArgs(args['file-uri']);

const noRecentEntry = args['skip-add-to-recently-opened'] === true;

const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;

...

// 打开主窗口,默认从执行命令行中读取参数

return windowsMainService.open({

context,

cli: args,

forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),

diffMode: args.diff,

noRecentEntry,

waitMarkerFileURI,

gotoLineMode: args.goto,

initialStartup: true

});

}

vs/code/electron-main/windows.ts

接下来到了 electron 的 windows 窗口,open 方法在 doOpen 中执行窗口配置初始化,最终调用 openInBrowserWindow -> 执行 doOpenInBrowserWindow 使其打开 window,主要步骤如下:

private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {

...

// New window

if (!window) {

//1.判断是否全屏创建窗口

...

// 2. 创建实例窗口

window = this.instantiationService.createInstance(CodeWindow, {

state,

extensionDevelopmentPath: configuration.extensionDevelopmentPath,

isExtensionTestHost: !!configuration.extensionTestsPath

});

// 3.添加到当前窗口控制器

WindowsManager.WINDOWS.push(window);

// 4.窗口监听器

window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own

window.win.webContents.on('devtools-reload-page', () => this.reload(window!));

window.win.webContents.on('crashed', () => this.onWindowError(window!, WindowError.CRASHED));

window.win.on('unresponsive', () => this.onWindowError(window!, WindowError.UNRESPONSIVE));

window.win.on('closed', () => this.onWindowClosed(window!));

// 5.注册窗口生命周期

(this.lifecycleService as LifecycleService).registerWindow(window);

}

...

return window;

}

doOpenInBrowserWindow 会调用 window.load 方法 在 window.ts 中实现

load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {

...

// Load URL

perf.mark('main:loadWindow');

this._win.loadURL(this.getUrl(configuration));

...

}

private getUrl(windowConfiguration: IWindowConfiguration): string {

...

//加载欢迎屏幕的html

let configUrl = this.doGetUrl(config);

...

return configUrl;

}

//默认加载
vs/code/electron-browser/workbench/workbench.html

private doGetUrl(config: object): string {

return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;

}

main process 的使命完成, 主界面进行构建布局。

在 workbench.html 中加载了 workbench.js,这里调用 return require('
vs/workbench/electron-browser/desktop.main').main(configuration);实现对主界面的展示

vs/workbench/electron-browser/desktop.main.ts

创建工作区,调用 workbench.startup()方法,构建主界面展示布局

...

async open(): Promise {

const services = await this.initServices();

await domContentLoaded();

mark('willStartWorkbench');

// 1.创建工作区

const workbench = new Workbench(document.body, services.serviceCollection, services.logService);

// 2.监听窗口变化

this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench)));

// 3.工作台生命周期

this._register(workbench.onShutdown(() => this.dispose()));

this._register(workbench.onWillShutdown(event => event.join(services.storageService.close())));

// 3.启动工作区

const instantiationService = workbench.startup();

...

}

...

vs/workbench/browser/workbench.ts

工作区继承自 layout 类,主要作用是构建工作区,创建界面布局。

export class Workbench extends Layout {

...

startup(): IInstantiationService {

try {

...

// Services

const instantiationService = this.initServices(this.serviceCollection);

instantiationService.invokeFunction(async accessor => {

const lifecycleService = accessor.get(ILifecycleService);

const storageService = accessor.get(IStorageService);

const configurationService = accessor.get(IConfigurationService);

// Layout

this.initLayout(accessor);

// Registries

this.startRegistries(accessor);

// Context Keys

this._register(instantiationService.createInstance(WorkbenchContextKeysHandler));

// 注册监听事件

this.registerListeners(lifecycleService, storageService, configurationService);

// 渲染工作区

this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService);

// 创建工作区布局

this.createWorkbenchLayout(instantiationService);

// 布局构建

this.layout();

// Restore

try {

await this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService);

} catch (error) {

onUnexpectedError(error);

}

});

return instantiationService;

} catch (error) {

onUnexpectedError(error);

throw error; // rethrow because this is a critical issue we cannot handle properly here

}

}

...

}

5.事件分发

event

src/vs/base/common/event.ts

程序中常见使用 once 方法进行事件绑定, 给定一个事件,返回一个只触发一次的事件,放在匿名函数返回

export function once(event: Event): Event {

return (listener, thisArgs = null, disposables?) => {

// 设置次变量,防止事件重复触发造成事件污染

let didFire = false;

let result: IDisposable;

result = event(e => {

if (didFire) {

return;

} else if (result) {

result.dispose();

} else {

didFire = true;

}

return listener.call(thisArgs, e);

}, null, disposables);

if (didFire) {

result.dispose();

}

return result;

};

}

循环派发了所有注册的事件, 事件会存储到一个事件队列,通过 fire 方法触发事件

private _deliveryQueue?: LinkedList<[Listener, T]>;//事件存储队列

fire(event: T): void {

if (this._listeners) {

// 将所有事件传入 delivery queue

// 内部/嵌套方式通过emit发出.

// this调用事件驱动

if (!this._deliveryQueue) {

this._deliveryQueue = new LinkedList();

}

for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {

this._deliveryQueue.push([e.value, event]);

}

while (this._deliveryQueue.size > 0) {

const [listener, event] = this._deliveryQueue.shift()!;

try {

if (typeof listener === 'function') {

listener.call(undefined, event);

} else {

listener[0].call(listener[1], event);

}

} catch (e) {

onUnexpectedError(e);

}

}

}

}

6.进程通信

主进程

src/vs/code/electron-main/main.ts

main.ts 在启动应用后就创建了一个主进程 main process,它可以通过 electron 中的一些模块直接与原生 GUI 交互。

server = await serve(environmentService.mainIPCHandle);

once(lifecycleService.onWillShutdown)(() => server.dispose());

渲染进程

仅启动主进程并不能给你的应用创建应用窗口。窗口是通过 main 文件里的主进程调用叫 BrowserWindow 的模块创建的。

主进程与渲染进程之间的通信

在 electron 中,主进程与渲染进程有很多通信的方法。比如 ipcRenderer 和 ipcMain,还可以在渲染进程使用 remote 模块。

ipcMain & ipcRenderer

  • 主进程:ipcMain
  • 渲染进程:ipcRenderer

ipcMain 模块和 ipcRenderer 是类 EventEmitter 的实例。

在主进程中使用 ipcMain 接收渲染线程发送过来的异步或同步消息,发送过来的消息将触发事件。

在渲染进程中使用 ipcRenderer 向主进程发送同步或异步消息,也可以接收到主进程的消息。

  • 发送消息,事件名为 channel .
  • 回应同步消息, 你可以设置 event.returnValue .
  • 回应异步消息, 你可以使用 event.sender.send(…)

创建 IPC 服务
src/vs/base/parts/ipc/node/ipc.net.ts

这里返回一个 promise 对象,成功则 createServer

export function serve(hook: any): Promise {

return new Promise((c, e) => {

const server = createServer();

server.on('error', e);

server.listen(hook, () => {

server.removeListener('error', e);

c(new Server(server));

});

});

}

创建信道

src/vs/code/electron-main/app.ts

  • mainIpcServer * launchChannel
  • electronIpcServer
  • _ updateChannel
  • _ issueChannel
  • _ workspacesChannel
  • _ windowsChannel
  • _ menubarChannel
  • _ urlChannel
  • _ storageChannel
  • _ logLevelChannel

private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] {

// Register more Main IPC services

const launchService = accessor.get(ILaunchService);

const launchChannel = new LaunchChannel(launchService);

this.mainIpcServer.registerChannel('launch', launchChannel);

// Register more Electron IPC services

const updateService = accessor.get(IUpdateService);

const updateChannel = new UpdateChannel(updateService);

electronIpcServer.registerChannel('update', updateChannel);

const issueService = accessor.get(IIssueService);

const issueChannel = new IssueChannel(issueService);

electronIpcServer.registerChannel('issue', issueChannel);

const workspacesService = accessor.get(IWorkspacesMainService);

const workspacesChannel = new WorkspacesChannel(workspacesService);

electronIpcServer.registerChannel('workspaces', workspacesChannel);

const windowsService = accessor.get(IWindowsService);

const windowsChannel = new WindowsChannel(windowsService);

electronIpcServer.registerChannel('windows', windowsChannel);

sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel));

const menubarService = accessor.get(IMenubarService);

const menubarChannel = new MenubarChannel(menubarService);

electronIpcServer.registerChannel('menubar', menubarChannel);

const urlService = accessor.get(IURLService);

const urlChannel = new URLServiceChannel(urlService);

electronIpcServer.registerChannel('url', urlChannel);

const storageMainService = accessor.get(IStorageMainService);

const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));

electronIpcServer.registerChannel('storage', storageChannel);

// Log level management

const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));

electronIpcServer.registerChannel('loglevel', logLevelChannel);

sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));

...

// default: read paths from cli

return windowsMainService.open({

context,

cli: args,

forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),

diffMode: args.diff,

noRecentEntry,

waitMarkerFileURI,

gotoLineMode: args.goto,

initialStartup: true

});

}

每一个信道,内部实现两个方法 listen 和 call

例如:
src/vs/platform/localizations/node/localizationsIpc.ts

构造函数绑定事件

export class LocalizationsChannel implements IServerChannel {

onDidLanguagesChange: Event;

constructor(private service: ILocalizationsService) {

this.onDidLanguagesChange = Event.buffer(service.onDidLanguagesChange, true);

}

listen(_: unknown, event: string): Event {

switch (event) {

case 'onDidLanguagesChange': return this.onDidLanguagesChange;

}

throw new Error(`Event not found: ${event}`);

}

call(_: unknown, command: string, arg?: any): Promise {

switch (command) {

case 'getLanguageIds': return this.service.getLanguageIds(arg);

}

throw new Error(`Call not found: ${command}`);

}

}

7.主要窗口

workbench.ts 中 startup 里面 Workbench 负责创建主界面
src/vs/workbench/browser/workbench.ts

startup(): IInstantiationService {

try {

...

instantiationService.invokeFunction(async accessor => {

// 渲染主工作界面

this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService);

// 界面布局

this.createWorkbenchLayout(instantiationService);

// Layout

this.layout();

// Restore

try {

await this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService);

} catch (error) {

onUnexpectedError(error);

}

});

return instantiationService;

} catch (error) {

onUnexpectedError(error);

throw error; // rethrow because this is a critical issue we cannot handle properly here

}

}

渲染主工作台,渲染完之后加入到 container 中,container 加入到 parent, parent 就是 body 了。

this.parent.appendChild(this.container);

private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void {

...

//TITLEBAR_PART 顶部操作栏

//ACTIVITYBAR_PART 最左侧菜单选项卡

//SIDEBAR_PART 左侧边栏,显示文件,结果展示等

//EDITOR_PART 右侧窗口,代码编写,欢迎界面等

//STATUSBAR_PART 底部状态栏

[

{ id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] },

{ id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },

{ id: Parts.SIDEBAR_PART, role: 'complementary', 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: 'complementary', classes: ['panel', this.state.panel.position === Position.BOTTOM ? 'bottom' : 'right'] },

{ id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] }

].forEach(({ id, role, classes, options }) => {

const partContainer = this.createPart(id, role, classes);

if (!configurationService.getValue('workbench.useExperimentalGridLayout')) {

// TODO@Ben cleanup once moved to grid

// Insert all workbench parts at the beginning. Issue #52531

// This is primarily for the title bar to allow overriding -webkit-app-region

this.container.insertBefore(partContainer, this.container.lastChild);

}

this.getPart(id).create(partContainer, options);

});

// 将工作台添加至container dom渲染

this.parent.appendChild(this.container);

}

workbench 最后调用 this.layout()方法,将窗口占据整个界面,渲染完成

layout(options?: ILayoutOptions): void {

if (!this.disposed) {

this._dimension = getClientArea(this.parent);

if (this.workbenchGrid instanceof Grid) {

position(this.container, 0, 0, 0, 0, 'relative');

size(this.container, this._dimension.width, this._dimension.height);

// Layout the grid widget

this.workbenchGrid.layout(this._dimension.width, this._dimension.height);

} else {

this.workbenchGrid.layout(options);

}

// Emit as event

this._onLayout.fire(this._dimension);

}

}

8.开发调试

app.once('ready', function () {

//启动追踪

if (args['trace']) {

// @ts-ignore

const contentTracing = require('electron').contentTracing;

const traceOptions = {

categoryFilter: args['trace-category-filter'] || '*',

traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'

};

contentTracing.startRecording(traceOptions, () => onReady());

} else {

onReady();

}

});

启动追踪

这里如果传入 trace 参数,在 onReady 启动之前会调用 chromium 的收集跟踪数据,提供的底层的追踪工具允许我们深度了解 V8 的解析以及其他时间消耗情况,

一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 startRecording 请求的时候,callback 将会被调用.

categoryFilter 是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 - 前缀来排除匹配的分类组.不允许同一个列表既是包含又是排斥.

contentTracing.startRecording(options, callback)

  • options Object
  • _ categoryFilter String
  • _ traceOptions String
  • callback Function

[关于 trace 的详细介绍](
https://www.w3cschool.cn/electronmanual/electronmanual-content-tracing.html)

结束追踪

contentTracing.stopRecording(resultFilePath, callback)

  • resultFilePath String
  • callback Function
  • 在成功启动窗口后,程序结束性能追踪,停止对所有子进程的记录.

子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据.

一旦所有子进程接收到了 stopRecording 请求,将调用 callback ,并且返回一个包含查找数据的文件.

如果 resultFilePath 不为空,那么将把查找数据写入其中,否则写入一个临时文件.实际文件路径如果不为空,则将调用 callback .

debug

调试界面在菜单栏找到 Help->Toggle Developers Tools

调出 Chrome 开发者调试工具进行调试

参考

https://electronjs.org/docs

https://github.com/microsoft/vscode/wiki/How-to-Contribute

https://github.com/Microsoft/vscode/wiki/Code-Organization

http://xzper.com/2016/04/17/vscode%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/

目录
http://www.ayqy.net/blog/vs-code%E6%BA%90%E7%A0%81%E7%AE%80%E6%9E%90/

相关推荐

驱动网卡(怎么从新驱动网卡)
驱动网卡(怎么从新驱动网卡)

网卡一般是指为电脑主机提供有线无线网络功能的适配器。而网卡驱动指的就是电脑连接识别这些网卡型号的桥梁。网卡只有打上了网卡驱动才能正常使用。并不是说所有的网卡一插到电脑上面就能进行数据传输了,他都需要里面芯片组的驱动文件才能支持他进行数据传输...

2026-01-30 00:37 liuian

win10更新助手装系统(微软win10更新助手)

1、点击首页“系统升级”的按钮,给出弹框,告诉用户需要上传IMEI码才能使用升级服务。同时给出同意和取消按钮。华为手机助手2、点击同意,则进入到“系统升级”功能华为手机助手华为手机助手3、在检测界面,...

windows11专业版密钥最新(windows11专业版激活码永久)

 Windows11专业版的正版密钥,我们是对windows的激活所必备的工具。该密钥我们可以通过微软商城或者通过计算机的硬件供应商去购买获得。获得了windows11专业版的正版密钥后,我...

手机删过的软件恢复(手机删除过的软件怎么恢复)
手机删过的软件恢复(手机删除过的软件怎么恢复)

操作步骤:1、首先,我们需要先打开手机。然后在许多图标中找到带有[文件管理]文本的图标,然后单击“文件管理”进入页面。2、进入页面后,我们将在顶部看到一行文本:手机,最新信息,文档,视频,图片,音乐,收藏,最后是我们正在寻找的[更多],单击...

2026-01-29 23:55 liuian

一键ghost手动备份系统步骤(一键ghost 备份)

  步骤1、首先把装有一键GHOST装系统的U盘插在电脑上,然后打开电脑马上按F2或DEL键入BIOS界面,然后就选择BOOT打USDHDD模式选择好,然后按F10键保存,电脑就会马上重启。  步骤...

怎么创建局域网(怎么创建局域网打游戏)

  1、购买路由器一台。进入路由器把dhcp功能打开  2、购买一台交换机。从路由器lan端口拉出一条网线查到交换机的任意一个端口上。  3、两台以上电脑。从交换机任意端口拉出网线插到电脑上(电脑设置...

精灵驱动器官方下载(精灵驱动手机版下载)

是的。驱动精灵是一款集驱动管理和硬件检测于一体的、专业级的驱动管理和维护工具。驱动精灵为用户提供驱动备份、恢复、安装、删除、在线更新等实用功能。1、全新驱动精灵2012引擎,大幅提升硬件和驱动辨识能力...

一键还原系统步骤(一键还原系统有哪些)

1、首先需要下载安装一下Windows一键还原程序,在安装程序窗口中,点击“下一步”,弹出“用户许可协议”窗口,选择“我同意该许可协议的条款”,并点击“下一步”。  2、在弹出的“准备安装”窗口中,可...

电脑加速器哪个好(电脑加速器哪款好)

我认为pp加速器最好用,飞速土豆太懒,急速酷六根本不工作。pp加速器什么网页都加速,太任劳任怨了!以上是个人观点,具体性能请自己试。ps:我家电脑性能很好。迅游加速盒子是可以加速电脑的。因为有过之...

任何u盘都可以做启动盘吗(u盘必须做成启动盘才能装系统吗)

是的,需要注意,U盘的大小要在4G以上,最好是8G以上,因为启动盘里面需要装系统,内存小的话,不能用来安装系统。内存卡或者U盘或者移动硬盘都可以用来做启动盘安装系统。普通的U盘就可以,不过最好U盘...

u盘怎么恢复文件(u盘文件恢复的方法)

开360安全卫士,点击上面的“功能大全”。点击文件恢复然后点击“数据”下的“文件恢复”功能。选择驱动接着选择需要恢复的驱动,选择接入的U盘。点击开始扫描选好就点击中间的“开始扫描”,开始扫描U盘数据。...

系统虚拟内存太低怎么办(系统虚拟内存占用过高什么原因)

1.检查系统虚拟内存使用情况,如果发现有大量的空闲内存,可以尝试释放一些不必要的进程,以释放内存空间。2.如果系统虚拟内存使用率较高,可以尝试增加系统虚拟内存的大小,以便更多的应用程序可以使用更多...

剪贴板权限设置方法(剪贴板访问权限)
剪贴板权限设置方法(剪贴板访问权限)

1、首先打开iphone手机,触碰并按住单词或图像直到显示选择选项。2、其次,然后选取“拷贝”或“剪贴板”。3、勾选需要的“权限”,最后选择开启,即可完成苹果剪贴板权限设置。仅参考1.打开苹果手机设置按钮,点击【通用】。2.点击【键盘】,再...

2026-01-29 21:37 liuian

平板系统重装大师(平板重装win系统)

如果你的平板开不了机,但可以连接上电脑,那就能好办,楼主下载安装个平板刷机王到你的个人电脑上,然后连接你的平板,平板刷机王会自动识别你的平板,平板刷机王上有你平板的我刷机包,楼主点击下载一个,下载完成...

联想官网售后服务网点(联想官网售后服务热线)

联想3c服务中心是联想旗下的官方售后,是基于互联网O2O模式开发的全新服务平台。可以为终端用户提供多品牌手机、电脑以及其他3C类产品的维修、保养和保险服务。根据客户需求层次,联想服务针对个人及家庭客户...