当前位置: 首页 > news >正文

VSCode源码解析-程序的启动逻辑

VSCode源码解析(一)- 程序的启动逻辑

  • 1. 查找项目入口文件
  • 2. 查看electron的ready事件
  • 3. 创建初始化服务并启动VSCode实例
  • 4. 启动实例
  • 5.打开窗口
  • 6. 创建窗口
  • 7.加载窗体内容
  • 8.加载HTML入口页面
  • 9 构建工作区对象
  • 10 加载工作台内容

学前先了解:
Visual Studio Code / Egret Wing 技术架构:基础
VS Code源码简析
仅为自学梳理流程。

1. 查找项目入口文件

在项目根目录package.json中找到项目入口文件 在这里插入图片描述
在此处可以看到入口文件地址。out文件的内容为编译后出现的,如果没有编译则找不到此文件。

编译参考:VSCode源码环境安装及运行

2. 查看electron的ready事件

追踪out\main.jssrc\main.js文件。
不编译也没关系,不编译就查找src\main.jsout\main.jssrc\main.js的JavaScript编译版。
找到其中的electron的ready事件app.once('ready')
在这里插入图片描述
继续追踪onReady
在这里插入图片描述

继续追踪startUp事件
在这里插入图片描述
其中的加载了主程序文件vs/code/electron-main/main.ts

3. 创建初始化服务并启动VSCode实例

追踪vs/code/electron-main/main.ts
执行了当前类中的main方法在这里插入图片描述
main方法中主要调用了startup方法。
在这里插入图片描述
继续看startup,主要是创建和初始化服务,启动VSCode实例。

private async startup(): Promise<void> {// 尽早设置错误处理程序,以避免弹出 Electron 默认的错误对话框setUnexpectedErrorHandler(err => console.error(err));// Create services// 1. 调用 createServicesconst [instantiationService, instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogger,productService, userDataProfilesMainService] = this.createServices();try {// Init services// 1.1 初始化Service服务//HDtry {await this.initServices(environmentMainService, userDataProfilesMainService, configurationService, stateMainService,productService);} catch (error) {// Show a dialog for errors that can be resolved by the userthis.handleStartupDataDirError(environmentMainService, productService, error);throw error;}// Startup/* HD main process入口文件对应源码  // 1.2 启动实例*/await instantiationService.invokeFunction(async accessor => {const logService = accessor.get(ILogService);const lifecycleMainService = accessor.get(ILifecycleMainService);const fileService = accessor.get(IFileService);const loggerService = accessor.get(ILoggerService);/** 这个方法负责尝试启动一个主 IPC(进程间通信)服务器。* 如果成功,则表示这是第一个运行的 VS Code 实例。* 如果失败,则表示我们不是第一个运行的 VS Code 实例,* 因此将会退出。* 其主要功能是确保同一时间只有一个 VS Code 实例在运行。* 如果检测到另一个实例,它会连接到该实例并发送必要的环境信息,然后终止自身。* 如果没有实例在运行,则启动一个新的 IPC 服务器并声称该实例。*/const mainProcessNodeIpcServer = await this.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, true);// Write a lockfile to indicate an instance is running//写一个锁文件来指示一个实例正在运行// (https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451)FSPromises.writeFile(environmentMainService.mainLockfile, String(process.pid)).catch(err => {logService.warn(`app#startup(): Error writing main lockfile: ${err.stack}`);});// Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906)//延迟spdlog的创建bufferLogger.logger = loggerService.createLogger('main', { name: localize('mainLog', "Main") });// Lifecycle/*负责在程序退出前释放资源并清理锁文件,确保程序能正常关闭。* 使用Event.once监听lifecycleMainService.onWillShutdown一次性事件* 监听到事件后释放资源并清理锁文件* 通过evt.join()异步删除主锁文件(mainLockfile),并忽略可能的删除错误*/Event.once(lifecycleMainService.onWillShutdown)(evt => {fileService.dispose();configurationService.dispose();evt.join('instanceLockfile', promises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ }));});return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();});} catch (error) {instantiationService.invokeFunction(this.quit, error);}}

下列这句代码使用instantiationService(依赖注入容器)创建一个CodeApplication类的实例,创建时传入两个参数:mainProcessNodeIpcServer(主进程IPC通信服务)和instanceEnvironment(实例环境配置),最后调用该实例的startup()方法启动应用。:

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

简而言之就是:创建并启动了一个VS Code类型的应用程序实例,并为其配置了进程通信和环境变量。

instantiationService:InstantiationService 是依赖注入(DI)容器,用于创建和管理类的实例。核心功能:
依赖解析:自动解析构造函数中的依赖项
实例管理:创建并缓存单例实例
接口实现:提供createInstance()方法来实例化类

CodeApplication:地址src\vs\code\electron-main\app.ts
CodeApplication类是 VS Code 桌面版的核心,协调了所有的底层 Electron 功能与 VS Code 自身业务逻辑的集成。负责整个 Electron 应用的初始化和生命周期管理。

核心职责

  • 应用入口:作为 VS Code 主进程的入口点,管理整个应用的启动流程
    注释说明"将始终只有一个实例",即使用户启动了多个实例
  • 服务初始化:初始化各种核心服务(窗口管理、更新、诊断、加密等)
    使用依赖注入系统(IInstantiationService)管理服务实例
  • 安全配置:配置 Electron 会话的安全策略(权限请求、请求过滤等)
    处理 UNC 访问限制(Windows 特有)
    主要组件
  • 窗口系统:IWindowsMainService:管理主窗口
    IAuxiliaryWindowsMainService:管理辅助窗口
  • 协议处理:处理 vscode:// 等自定义协议 URL
    支持打开文件/文件夹/工作区
  • 生命周期管理:使用 ILifecycleMainService 管理应用的不同阶段(Ready, AfterWindowOpen, Eventually)
  • 共享进程:创建和管理共享进程(SharedProcess)
    通过 MessagePort 与共享进程通信
    关键方法
  • startup():
    主启动流程,包括:
    设置应用标识
    初始化共享进程
    初始化服务集合
    打开第一个窗口
    进入不同生命周期阶段
  • openFirstWindow():
    处理首次窗口打开的多种情况:
    从命令行参数打开
    从协议 URL 打开
    从 macOS 的 dock 打开文件
    安全相关
  • configureSession():配置 Electron 会话的权限检查和请求过滤
  • shouldBlockOpenable():检查是否应阻止可疑的文件/文件夹打开请求

4. 启动实例

查看src\vs\code\electron-main\app.tsstartup方法:

...
//初始化服务,IWindowsMainservice接口的实现类由此initServices可得知为WindowsMainService【步骤5中传入的】
const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady);
...
//`openFirstWindow`是启动主窗口的方法
await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, initialProtocolUrls));

initServices方法:

//由此句IWindowsMainservice接口的实现类由此initServices可得知为WindowsMainService
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, sqmId, devDeviceId, this.userEnv], false));

SyncDescriptor 是 ‌VS Code 依赖注入系统‌ 中的核心工具类,用于延迟实例化服务并管理依赖关系。存储服务类的构造函数和依赖项,当通过 InstantiationService#createInstance 请求服务时,才会执行实际构造
openFirstWindow方法主要逻辑分为以下几点:
协议链接处理:优先检查是否有通过协议链接(如vscode://)打开的请求,若有则直接打开对应窗>口或特殊处理windowId=_blank的情况。
无参数启动:若启动时无文件/文件夹参数,根据条件(如–new-window或macOS的open-file事件)决定是否打开空窗口或处理macOS文件打开请求。
默认处理:若无特殊条件,则根据命令行参数(如–diff、–merge等)打开对应窗口。

openFirstWindow方法:

//获取窗口管理服务实例
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
....
return windowsMainService.open({context,cli: args,forceNewWindow: args['new-window'],diffMode: args.diff, // 差异对比模式mergeMode: args.merge,// 合并模式noRecentEntry,waitMarkerFileURI,gotoLineMode: args.goto,// 行跳转模式initialStartup: true,remoteAuthority,forceProfile,forceTempProfile
});

通过accessor.get(IWindowsMainService)获取窗口管理服务实例。
然后通过windowsMainService.open方法统一处理不同场景的窗口打开逻辑,并考虑了远程连接、临时配置等特殊情况。

5.打开窗口

找到src\vs\platform\windows\electron-main\windowsMainService.ts方法,查看其中的open方法。
open是VS Code窗口管理器的核心功能,主要处理打开窗口/文件/工作区的逻辑。

主要功能分点如下:

  • 参数校验:检查addMode/removeMode是否在有效场景下使用
  • 路径分类:将待打开的路径分为文件夹、工作区、文件、备份窗口等不同类型
  • 特殊模式处理:处理–diff/–merge/–wait等命令行参数的特殊逻辑
  • 窗口恢复:在初始启动时恢复未保存的工作区和空窗口
  • 窗口聚焦:多窗口打开时智能选择聚焦窗口
  • 历史记录:将打开项添加到最近使用列表(排除开发模式等特殊情况)

其中doOpen方法在处理根据配置打开窗口。

const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, maybeOpenEmptyWindow, filesToOpen, foldersToAdd, foldersToRemove);

跟踪doOpen方法
doOpen主要功能是处理各种打开场景(工作区、文件夹、文件等)的窗口分配策略。

主要逻辑分5点:

  • 跟踪已使用的窗口和文件打开状态(usedWindows和filesOpenedInWindow)
  • 处理文件夹的添加/移除操作(通过doAddRemoveFoldersInExistingWindow)
  • 优先在现有匹配窗口中打开文件(通过findWindowOnFile查找)
  • 按优先级处理三种打开类型:
    - 工作区(workspacesToOpen)
    - 单文件夹(foldersToOpen)
    - 空窗口恢复(emptyToRestore)
  • 最后处理剩余未打开的文件或强制空窗口情况
    所有操作都遵循"是否在新窗口打开"的配置规则,并确保远程连接权限匹配。
addUsedWindow(await this.openInBrowserWindow({userEnv: openConfig.userEnv,cli: openConfig.cli,initialStartup: openConfig.initialStartup,filesToOpen,forceNewWindow: true,remoteAuthority: filesToOpen.remoteAuthority,forceNewTabbedWindow: openConfig.forceNewTabbedWindow,forceProfile: openConfig.forceProfile,forceTempProfile: openConfig.forceTempProfile}), true);

addUsedWindow:该函数用于管理窗口使用状态,并在文件打开时更新相关标记。

将传入的窗口对象(window)添加到usedWindows数组中,用于记录已使用的窗口。
如果openedFiles参数为true,则:
将当前窗口设置为filesOpenedInWindow(标记为已打开文件的窗口)
清空filesToOpen变量(表示文件已打开,无需再处理待打开文件)

6. 创建窗口

src\vs\platform\windows\electron-main\windowsMainService.ts跟踪openInBrowserWindow
openInBrowserWindow的核心功能是管理VS Code窗口的生命周期和配置,打开一个新的浏览器窗口或和重用现有窗口。

主要逻辑如下:
配置获取:从服务中获取窗口配置和用户数据配置
窗口选择:根据选项决定是重用现有窗口还是创建新窗口
配置构建:组合环境变量、CLI参数等构建完整的窗口配置
窗口创建:如需新窗口,创建并设置事件监听
窗口重用:如重用现有窗口,继承部分开发相关配置
窗口加载:根据窗口状态决定直接加载或先卸载再加载。

const createdWindow = window = this.instantiationService.createInstance(CodeWindow, {state,extensionDevelopmentPath: configuration.extensionDevelopmentPath,isExtensionTestHost: !!configuration.extensionTestsPath});

this.instantiationService.createInstance创建了一个CodeWindow的实例。
这个类型src\vs\platform\windows\electron-main\windowImpl.ts中定义
在这个方法里完成了窗口的创建:

this._win = new electron.BrowserWindow(options);

VSCode窗口创建出来了

7.加载窗体内容

src\vs\platform\windows\electron-main\windowImpl.tsopenInBrowserWindow方法继续往下读。

// If the window was already loaded, make sure to unload it// first and only load the new configuration if that was// not vetoed//如果窗口已经加载,确保卸载它。只加载新的配置if (window.isReady) {this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(async veto => {if (!veto) {await this.doOpenInBrowserWindow(window, configuration, options, defaultProfile);}});} else {await this.doOpenInBrowserWindow(window, configuration, options, defaultProfile);}

这段代码的功能是根据窗口状态决定如何打开浏览器窗口:
如果窗口已准备就绪(window.isReady),先尝试卸载窗口(通过lifecycleMainService.unload),如果卸载未被阻止(!veto),则调用doOpenInBrowserWindow打开新窗口
如果窗口未就绪,直接调用doOpenInBrowserWindow打开新窗口
核心逻辑:窗口准备状态不同时采用不同的打开策略,已就绪窗口需要先安全卸载再打开

追踪doOpenInBrowserWindow方法。

这段TypeScript代码的功能是:在浏览器窗口中打开VS Code时处理备份和用户配置。主要逻辑分三点:

  1. 备份注册:根据工作区类型(普通工作区/单文件夹/空窗口)向备份服务注册备份路径,排除扩展开发路径的情况。
  2. 用户配置处理:解析并设置窗口的用户配置文件,如果是普通工作区(非扩展开发)则持久化配置关联。
  3. 窗口加载:最终将配置加载到目标窗口。
    代码涉及工作区类型判断、异步配置解析和备份服务协作,核心是为窗口启动提供完整的初始化配置。
// Load it
window.load(configuration);

其中load方法根据配置进行加载窗体内容。

8.加载HTML入口页面

跟踪load放,上文6提到过在src\vs\platform\windows\electron-main\windowsMainService.tsthis.instantiationService.createInstance创建了一个CodeWindow的实例。
所以此处的_win对应的实现类为src\vs\platform\windows\electron-main\windowImpl.ts
找到其中的load

this._win.loadURL(FileAccess.asBrowserUri(`vs/code/electron-sandbox/workbench/workbench${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true));

loadURL在加载HTML入口文件。

9 构建工作区对象

src\vs\code\electron-sandbox\workbench\workbench.ts中加载了<script src="./workbench.js" type="module"></script>,对应的ts便是src\vs\code\electron-sandbox\workbench\workbench.ts
查看这个`workbench.ts``的逻辑
在这里插入图片描述

通过load方法异步加载了vs/workbench/workbench.desktop.main模块,result返回的就是vs/workbench/workbench.desktop.main模块。
接下来看result.main(configuration)
在这里插入图片描述
src\vs\workbench\electron-sandbox\desktop.main.tsmain函数中打开了工作区。
继续追踪open方法

/* HD624  打开工作区 */async open(): Promise<void> {// Init services and wait for DOM to be ready in parallel//初始化服务并等待DOM准备好const [services] = await Promise.all([this.initServices(), domContentLoaded(mainWindow)]);//一旦我们有了配置服务,就尽早应用缩放级别//在工作台创建之前,以防止闪烁。//我们还需要尊重缩放级别可以配置//工作空间,所以我们需要解析的配置服务。//最后,窗口可以有一个自定义//不是从设置中派生的缩放级别。this.applyWindowZoomLevel(services.configurationService);// Create Workbenchconst workbench = new Workbench(mainWindow.document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService);// Listenersthis.registerListeners(workbench, services.storageService);// Startupconst instantiationService = workbench.startup();// Windowthis._register(instantiationService.createInstance(NativeWindow));}

此处的const workbench = new Workbench(mainWindow.document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService)构建了工作区对象。
继续查看workbench.startup()方法做了什么事情。

workbench.startup()是VS Code工作台启动的核心逻辑,主要功能如下:
初始化服务:配置事件监听泄漏阈值(175),创建依赖注入容器(instantiationService)
关键服务注册:获取生命周期、存储、配置等核心服务,设置默认悬浮框行为
工作台构建:初始化布局、注册编辑器工厂、添加上下文键处理器
渲染流程:注册监听器→渲染工作台→创建布局→恢复上次状态
异常时会触发全局错误处理,若启动失败会重新抛出致命错误。整个过程通过依赖注入协调各模块初始化顺序。

// Render Workbench 呈现工作台
this.renderWorkbench(instantiationService, notificationService, storageService, configurationService);

10 加载工作台内容

src\vs\code\electron-sandbox\workbench\workbench.ts中通过renderWorkbench函数渲染工作台。

/**HD624*这段TypeScript代码是用于渲染工作台(Workbench)的主要逻辑,主要功能包括:*无障碍设置:配置ARIA容器和进度条信号调度器*平台样式设置:根据操作系统和浏览器添加对应的CSS类名*字体处理:更新字体抗锯齿设置并恢复字体缓存*创建工作台组件:生成标题栏、活动栏、编辑器等各个UI部件*通知处理:创建通知处理器*DOM插入:将工作台添加到父容器中*代码通过instantiationService等依赖注入服务来创建和管理各个组件,并考虑了不同平台和浏览器的兼容性问题。*/private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void {// ARIA & Signals/*HD625* 创建并配置一个ARIA(无障碍访问)容器,主要包含以下部分:* 创建一个div作为ARIA容器,并添加到指定的父元素中* 创建两个警报容器(alert),设置role = "alert"和aria - atomic="true"属性,用于紧急通知* 创建两个状态容器(status),设置aria - live="polite"和aria - atomic="true"属性,用于非紧急状态更新*/setARIAContainer(this.mainContainer);//设置一个进度条的无障碍信号调度器setProgressAcccessibilitySignalScheduler((msDelayTime: number, msLoopTime?: number) => instantiationService.createInstance(AccessibilityProgressSignalScheduler, msDelayTime, msLoopTime));// State specific classesconst platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';//根据运行平台、浏览器类型和额外配置类动态生成一组CSS类名const workbenchClasses = coalesce(['monaco-workbench',platformClass,isWeb ? 'web' : undefined,isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined,...this.getLayoutClasses(),...(this.options?.extraClasses ? this.options.extraClasses : [])]);this.mainContainer.classList.add(...workbenchClasses);// Apply font aliasing,更新字体抗锯齿设置this.updateFontAliasing(undefined, configurationService);// Warm up font cache information before building up too many dom elements//预热字体缓存信息,以避免在创建大量DOM元素时因字体加载导致性能问题this.restoreFontInfo(storageService, configurationService);// Create Parts 创建并初始化多个UI部件(parts)for (const { id, role, classes, options } of [{ id: Parts.TITLEBAR_PART, role: 'none', classes: ['titlebar'] },{ id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] },{ id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === 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.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] },{ id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } },{ id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] },{ id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] },{ id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }]) {const partContainer = this.createPart(id, role, classes);mark(`code/willCreatePart/${id}`);//从管理器中获取指定部件,并在目标容器中初始化该部件this.getPart(id).create(partContainer, options);mark(`code/didCreatePart/${id}`);}// Notification Handlers,初始化通知相关的处理器this.createNotificationsHandlers(instantiationService, notificationService);// Add Workbench to DOMthis.parent.appendChild(this.mainContainer);}

回到src\vs\workbench\browser\workbench.ts,其中的this.createWorkbenchLayout();在构建工作区内部内容
this.layout();调整工作台布局。

参考资料:
vscode源码分析【二】程序的启动逻辑,第一个窗口是如何创建的
VS Code源码简析

相关文章:

  • 企业建网站需要什么八八网
  • 企业网站营销如何建设软文营销广告
  • 网页网络优化seo网络推广技术员招聘
  • 淘宝做轮播广告哪个网站好南平网站seo
  • 做首饰网站网上电商平台开发
  • 广东的网站备案湖北短视频搜索seo
  • 彻底拆解 Vue scoped 指令:从编译原理到工程实践的全链路解析
  • 【Spring底层分析】AOP的cligb代理和jdk代理
  • 逆向入门(7)汇编篇-mul指令的学习
  • 《C++》命名空间简述
  • 云电脑,“死”于AI时代前夕 | 数智化观察
  • JVM(12)——详解G1垃圾回收器
  • AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月24日第118弹
  • Python移除链表元素-虚拟节点
  • 植物小知识
  • [密码学实战]商密TLCP协议抓包解析与深度分析(二十九)
  • 云原生周刊:Argo CD v3.1 正式发布
  • git学习资源
  • 大模型时代的创业机遇
  • Linux 网络命名空间的奥秘:深入解析struct net与内核模块编译陷阱
  • Redis 分布式锁原理与实战-学习篇
  • DeepSeek智能总结 | 邓紫棋音乐版权纠纷核心梳理
  • Vue3+Spring boot 前后端防抖增强方案
  • 3.0 compose学习:MVVM框架+Hilt注解调用登录接口
  • 领域驱动设计(DDD)【9】之代码初始部分实现和问题解决
  • 仓颉语言语法特点、使用范围、编译及环境搭建:从零开始第一个cangjie程序