微软 VSCode IDE 源码分析揭秘
作者:zanewang,騰訊 CSIG 工程師
目錄
(1)簡(jiǎn)介
(2)技術(shù)架構(gòu)
(3)啟動(dòng)主進(jìn)程
(4)實(shí)例化服務(wù)
(5)事件分發(fā)
(6)進(jìn)程通信
(7)主要窗口
(8)開發(fā)調(diào)試
1.簡(jiǎn)介
Visual Studio Code(簡(jiǎn)稱 VSCode) 是開源免費(fèi)的 IDE 編輯器,原本是微軟內(nèi)部使用的云編輯器(Monaco)。
git 倉庫地址:https://github.com/microsoft/vscode
通過 Eletron 集成了桌面應(yīng)用,可以跨平臺(tái)使用,開發(fā)語言主要采用微軟自家的 TypeScript。
整個(gè)項(xiàng)目結(jié)構(gòu)比較清晰,方便閱讀代碼理解。成為了最流行跨平臺(tái)的桌面 IDE 應(yīng)用
微軟希望 VSCode 在保持核心輕量級(jí)的基礎(chǔ)上,增加項(xiàng)目支持,智能感知,編譯調(diào)試。
編譯安裝
下載最新版本,目前我用的是 1.37.1 版本
官方的 wiki 中有編譯安裝的說明 How to Contribute
Linux, Window, MacOS 三個(gè)系統(tǒng)編譯時(shí)有些差別,參考官方文檔,
在編譯安裝依賴時(shí)如果遇到 connect timeout, 需要進(jìn)行科學(xué)上網(wǎng)。
需要注意的一點(diǎn) 運(yùn)行環(huán)境依賴版本 Nodejs x64 version >= 10.16.0, < 11.0.0, python 2.7(3.0 不能正常執(zhí)行)
2.技術(shù)架構(gòu)
Electron
Electron 是一個(gè)使用 JavaScript, HTML 和 CSS 等 Web 技術(shù)創(chuàng)建原生程序的框架,它負(fù)責(zé)比較難搞的部分,你只需把精力放在你的應(yīng)用的核心上即可 (Electron = Node.js + Chromium + Native API)
Monaco Editor
Monaco Editor是微軟開源項(xiàng)目, 為 VS Code 提供支持的代碼編輯器,運(yùn)行在瀏覽器環(huán)境中。編輯器提供代碼提示,智能建議等功能。供開發(fā)人員遠(yuǎn)程更方便的編寫代碼,可獨(dú)立運(yùn)行。
TypeScript
TypeScript是一種由微軟開發(fā)的自由和開源的編程語言。它是 JavaScript 的一個(gè)超集,而且本質(zhì)上向這個(gè)語言添加了可選的靜態(tài)類型和基于類的面向?qū)ο缶幊?/p>
目錄結(jié)構(gòu)
├──?build?????????#?gulp編譯構(gòu)建腳本 ├──?extensions????#?內(nèi)置插件 ├──?product.json??#?App?meta信息 ├──?resources?????#?平臺(tái)相關(guān)靜態(tài)資源 ├──?scripts???????#?工具腳本,開發(fā)/測(cè)試 ├──?src???????????#?源碼目錄 └──?typings???????#?函數(shù)語法補(bǔ)全定義 └──?vs├──?base????????#?通用工具/協(xié)議和UI庫│???├──?browser?#?基礎(chǔ)UI組件,DOM操作│???├──?common??#?diff描述,markdown解析器,worker協(xié)議,各種工具函數(shù)│???├──?node????#?Node工具函數(shù)│???├──?parts???#?IPC協(xié)議(Electron、Node),quickopen、tree組件│???├──?test????#?base單測(cè)用例│???└──?worker??# Worker factory和main Worker(運(yùn)行IDE Core:Monaco)├──?code????????#?VSCode主運(yùn)行窗口├──?editor????????#?IDE代碼編輯器|???├──?browser?????#?代碼編輯器核心|???├──?common??????#?代碼編輯器核心|???├──?contrib?????#?vscode?與獨(dú)立?IDE共享的代碼|???└──?standalone??#?獨(dú)立?IDE?獨(dú)有的代碼├──?platform??????#?支持注入服務(wù)和平臺(tái)相關(guān)基礎(chǔ)服務(wù)(文件、剪切板、窗體、狀態(tài)欄)├──?workbench?????#?工作區(qū)UI布局,功能主界面│???├──?api??????????????#│???├──?browser??????????#│???├──?common???????????#│???├──?contrib??????????#│???├──?electron-browser?#│???├──?services?????????#│???└──?test?????????????#├──?css.build.js??#?用于插件構(gòu)建的CSS?loader├──?css.js????????#?CSS?loader├──?editor????????#?對(duì)接IDE?Core(讀取編輯/交互狀態(tài)),提供命令、上下文菜單、hover、snippet等支持├──?loader.js?????#?AMD?loader(用于異步加載AMD模塊)├──?nls.build.js??#?用于插件構(gòu)建的NLS?loader└──?nls.js????????#?NLS(National?Language?Support)多語言loader核心層
base: 提供通用服務(wù)和構(gòu)建用戶界面
platform: 注入服務(wù)和基礎(chǔ)服務(wù)代碼
editor: 微軟 Monaco 編輯器,也可獨(dú)立運(yùn)行使用
wrokbench: 配合 Monaco 并且給 viewlets 提供框架:如:瀏覽器狀態(tài)欄,菜單欄利用 electron 實(shí)現(xiàn)桌面程序
核心環(huán)境
整個(gè)項(xiàng)目完全使用 typescript 實(shí)現(xiàn),electron 中運(yùn)行主進(jìn)程和渲染進(jìn)程,使用的 api 有所不同,所以在 core 中每個(gè)目錄組織也是按照使用的 api 來安排,
運(yùn)行的環(huán)境分為幾類:
common: 只使用 javascritp api 的代碼,能在任何環(huán)境下運(yùn)行
browser: 瀏覽器 api, 如操作 dom; 可以調(diào)用 common
node: 需要使用 node 的 api,比如文件 io 操作
electron-brower: 渲染進(jìn)程 api, 可以調(diào)用 common, brower, node, 依賴electron renderer-process API
electron-main: 主進(jìn)程 api, 可以調(diào)用: common, node 依賴于electron main-process AP
3.啟動(dòng)主進(jìn)程
Electron 通過 package.json 中的 main 字段來定義應(yīng)用入口。
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
vs/code/electron-main/main.ts
electron-main/main 是程序真正啟動(dòng)的入口,進(jìn)入 main process 初始化流程.
這里主要做了兩件事情:
初始化 Service
啟動(dòng)主實(shí)例
直接看 startup 方法的實(shí)現(xiàn),基礎(chǔ)服務(wù)初始化完成后會(huì)加載 CodeApplication, mainIpcServer, instanceEnvironment,調(diào)用 startup 方法啟動(dòng) APP
private?async?startup(args:?ParsedArgs):?Promise<void>?{//spdlog?日志服務(wù)const?bufferLogService?=?new?BufferLogService();//?1.?調(diào)用?createServicesconst?[instantiationService,?instanceEnvironment]?=?this.createServices(args,?bufferLogService);try?{//?1.1?初始化Service服務(wù)await?instantiationService.invokeFunction(async?accessor?=>?{//?基礎(chǔ)服務(wù),包括一些用戶數(shù)據(jù),緩存目錄const?environmentService?=?accessor.get(IEnvironmentService);//?配置服務(wù)const?configurationService?=?accessor.get(IConfigurationService);//?持久化數(shù)據(jù)const?stateService?=?accessor.get(IStateService);try?{await?this.initServices(environmentService,?configurationService?as?ConfigurationService,?stateService?as?StateService);}?catch?(error)?{//?拋出錯(cuò)誤對(duì)話框this.handleStartupDataDirError(environmentService,?error);throw?error;}});//?1.2?啟動(dòng)實(shí)例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 創(chuàng)建一些基礎(chǔ)的 Service
運(yùn)行環(huán)境服務(wù) EnvironmentService
src/vs/platform/environment/node/environmentService.ts
通過這個(gè)服務(wù)獲取當(dāng)前啟動(dòng)目錄,日志目錄,操作系統(tǒng)信息,配置文件目錄,用戶目錄等。
日志服務(wù) MultiplexLogService
src/vs/platform/log/common/log.ts
默認(rèn)使用控制臺(tái)日志 ConsoleLogMainService
其中包含性能追蹤和釋放信息,日志輸出級(jí)別
配置服務(wù) ConfigurationService
src/vs/platform/configuration/node/configurationService.ts
從運(yùn)行環(huán)境服務(wù)獲取內(nèi)容
生命周期服務(wù) LifecycleService
src/vs/platform/lifecycle/common/lifecycleService.ts
監(jiān)聽事件,electron app 模塊 比如:ready, window-all-closed,before-quit
可以參考官方electron app 文檔
狀態(tài)服務(wù) StateService
src/vs/platform/state/node/stateService.ts
通過 FileStorage 讀寫 storage.json 存儲(chǔ),里記錄一些與程序運(yùn)行狀態(tài)有關(guān)的鍵值對(duì)
請(qǐng)求服務(wù) RequestService
src/vs/platform/request/browser/requestService.ts
這里使用的是原生 ajax 請(qǐng)求,實(shí)現(xiàn)了 request 方法
主題服務(wù) ThemeMainService
src/vs/platform/theme/electron-main/themeMainService.ts
這里只設(shè)置背景顏色,通過 getBackgroundColor 方法 IStateService 存儲(chǔ)
簽名服務(wù) SignService
src/vs/platform/sign/node/signService.ts
private?createServices(args:?ParsedArgs,?bufferLogService:?BufferLogService):?[IInstantiationService,?typeof?process.env]?{//服務(wù)注冊(cè)容器const?services?=?new?ServiceCollection();const?environmentService?=?new?EnvironmentService(args,?process.execPath);const?instanceEnvironment?=?this.patchEnvironment(environmentService);?//?Patch?`process.env`?with?the?instance's?environmentservices.set(IEnvironmentService,?environmentService);const?logService?=?new?MultiplexLogService([new?ConsoleLogMainService(getLogLevel(environmentService)),?bufferLogService]);process.once('exit',?()?=>?logService.dispose());//日志服務(wù)services.set(ILogService,?logService);//配置服務(wù)services.set(IConfigurationService,?new?ConfigurationService(environmentService.settingsResource));//生命周期services.set(ILifecycleService,?new?SyncDescriptor(LifecycleService));//狀態(tài)存儲(chǔ)services.set(IStateService,?new?SyncDescriptor(StateService));//網(wǎng)絡(luò)請(qǐng)求services.set(IRequestService,?new?SyncDescriptor(RequestService));//主題設(shè)定services.set(IThemeMainService,?new?SyncDescriptor(ThemeMainService));//簽名服務(wù)services.set(ISignService,?new?SyncDescriptor(SignService));return?[new?InstantiationService(services,?true),?instanceEnvironment]; }4.實(shí)例化服務(wù)
SyncDescriptor 負(fù)責(zé)注冊(cè)這些服務(wù),當(dāng)用到該服務(wù)時(shí)進(jìn)程實(shí)例化使用
src/vs/platform/instantiation/common/descriptors.ts
export?class?SyncDescriptor<T>?{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 方法調(diào)用 invokeFunction.get 實(shí)例化服務(wù)
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?userthis.handleStartupDataDirError(environmentService,?error);throw?error;} });get 方法調(diào)用_getOrCreateServiceInstance,這里第一次創(chuàng)建會(huì)存入緩存中
下次實(shí)例化對(duì)象時(shí)會(huì)優(yōu)先從緩存中獲取對(duì)象。
src/vs/platform/instantiation/common/instantiationService.ts
invokeFunction<R,?TS?extends?any[]?=?[]>(fn:?(accessor:?ServicesAccessor,?...args:?TS)?=>?R,?...args:?TS):?R?{let?_trace?=?Trace.traceInvocation(fn);let?_done?=?false;try?{const?accessor:?ServicesAccessor?=?{get:?<T>(id:?ServiceIdentifier<T>,?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<T>(id:?ServiceIdentifier<T>,?_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
這里首先觸發(fā) CodeApplication.startup()方法, 在第一個(gè)窗口打開 3 秒后成為共享進(jìn)程,
async?startup():?Promise<void>?{...//?1.?第一個(gè)窗口創(chuàng)建共享進(jìn)程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.?創(chuàng)建app實(shí)例const?appInstantiationService?=?await?this.createServices(machineId,?trueMachineId,?sharedProcess,?sharedProcessClient);//?3.?打開一個(gè)窗口?調(diào)用const?windows?=?appInstantiationService.invokeFunction(accessor?=>?this.openFirstWindow(accessor,?electronIpcServer,?sharedProcessClient));//?4.?窗口打開后執(zhí)行生命周期和授權(quán)操作this.afterWindowOpen();...//vscode結(jié)束了性能問題的追蹤if?(this.environmentService.args.trace)?{this.stopTracingEventually(windows);} }openFirstWindow 主要實(shí)現(xiàn)
CodeApplication.openFirstWindow 首次開啟窗口時(shí),創(chuàng)建 Electron 的 IPC,使主進(jìn)程和渲染進(jìn)程間通信。
window 會(huì)被注冊(cè)到 sharedProcessClient,主進(jìn)程和共享進(jìn)程通信
根據(jù) environmentService 提供的參數(shù)(path,uri)調(diào)用 windowsMainService.open 方法打開窗口
vs/code/electron-main/windows.ts
接下來到了 electron 的 windows 窗口,open 方法在 doOpen 中執(zhí)行窗口配置初始化,最終調(diào)用 openInBrowserWindow -> 執(zhí)行 doOpenInBrowserWindow 是其打開 window,主要步驟如下:
private?openInBrowserWindow(options:?IOpenBrowserWindowOptions):?ICodeWindow?{...//?New?windowif?(!window)?{//1.判斷是否全屏創(chuàng)建窗口...//?2.?創(chuàng)建實(shí)例窗口window?=?this.instantiationService.createInstance(CodeWindow,?{state,extensionDevelopmentPath:?configuration.extensionDevelopmentPath,isExtensionTestHost:?!!configuration.extensionTestsPath});//?3.添加到當(dāng)前窗口控制器WindowsManager.WINDOWS.push(window);//?4.窗口監(jiān)聽器window.win.webContents.removeAllListeners('devtools-reload-page');?//?remove?built?in?listener?so?we?can?handle?this?on?our?ownwindow.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.注冊(cè)窗口生命周期(this.lifecycleService?as?LifecycleService).registerWindow(window);}...return?window; }doOpenInBrowserWindow 會(huì)調(diào)用 window.load 方法 在 window.ts 中實(shí)現(xiàn)
load(config:?IWindowConfiguration,?isReload?:?boolean,?disableExtensions?:?boolean):?void?{...//?Load?URLperf.mark('main:loadWindow');this._win.loadURL(this.getUrl(configuration));... }private?getUrl(windowConfiguration:?IWindowConfiguration):?string?{...//加載歡迎屏幕的htmllet?configUrl?=?this.doGetUrl(config);...return?configUrl; }//默認(rèn)加載?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 的使命完成, 主界面進(jìn)行構(gòu)建布局。
在 workbench.html 中加載了 workbench.js,
這里調(diào)用 return require('vs/workbench/electron-browser/desktop.main').main(configuration);實(shí)現(xiàn)對(duì)主界面的展示
vs/workbench/electron-browser/desktop.main.ts
創(chuàng)建工作區(qū),調(diào)用 workbench.startup()方法,構(gòu)建主界面展示布局
... async?open():?Promise<void>?{const?services?=?await?this.initServices();await?domContentLoaded();mark('willStartWorkbench');//?1.創(chuàng)建工作區(qū)const?workbench?=?new?Workbench(document.body,?services.serviceCollection,?services.logService);//?2.監(jiān)聽窗口變化this._register(addDisposableListener(window,?EventType.RESIZE,?e?=>?this.onWindowResize(e,?true,?workbench)));//?3.工作臺(tái)生命周期this._register(workbench.onShutdown(()?=>?this.dispose()));this._register(workbench.onWillShutdown(event?=>?event.join(services.storageService.close())));//?3.啟動(dòng)工作區(qū)const?instantiationService?=?workbench.startup();... } ...vs/workbench/browser/workbench.ts
工作區(qū)繼承自 layout 類,主要作用是構(gòu)建工作區(qū),創(chuàng)建界面布局。
export?class?Workbench?extends?Layout?{...startup():?IInstantiationService?{try?{...//?Servicesconst?instantiationService?=?this.initServices(this.serviceCollection);instantiationService.invokeFunction(async?accessor?=>?{const?lifecycleService?=?accessor.get(ILifecycleService);const?storageService?=?accessor.get(IStorageService);const?configurationService?=?accessor.get(IConfigurationService);//?Layoutthis.initLayout(accessor);//?Registriesthis.startRegistries(accessor);//?Context?Keysthis._register(instantiationService.createInstance(WorkbenchContextKeysHandler));//?注冊(cè)監(jiān)聽事件this.registerListeners(lifecycleService,?storageService,?configurationService);//?渲染工作區(qū)this.renderWorkbench(instantiationService,?accessor.get(INotificationService)?as?NotificationService,?storageService,?configurationService);//?創(chuàng)建工作區(qū)布局this.createWorkbenchLayout(instantiationService);//?布局構(gòu)建this.layout();//?Restoretry?{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.事件分發(fā)
event
src/vs/base/common/event.ts
程序中常見使用 once 方法進(jìn)行事件綁定, 給定一個(gè)事件,返回一個(gè)只觸發(fā)一次的事件,放在匿名函數(shù)返回
export?function?once<T>(event:?Event<T>):?Event<T>?{return?(listener,?thisArgs?=?null,?disposables?)?=>?{//?設(shè)置次變量,防止事件重復(fù)觸發(fā)造成事件污染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;}; }循環(huán)派發(fā)了所有注冊(cè)的事件, 事件會(huì)存儲(chǔ)到一個(gè)事件隊(duì)列,通過 fire 方法觸發(fā)事件
private _deliveryQueue?: LinkedList<[Listener, T]>;//事件存儲(chǔ)隊(duì)列
fire(event:?T):?void?{if?(this._listeners)?{//?將所有事件傳入?delivery?queue//?內(nèi)部/嵌套方式通過emit發(fā)出.//?this調(diào)用事件驅(qū)動(dòng)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.進(jìn)程通信
主進(jìn)程
src/vs/code/electron-main/main.ts
main.ts 在啟動(dòng)應(yīng)用后就創(chuàng)建了一個(gè)主進(jìn)程 main process,它可以通過 electron 中的一些模塊直接與原生 GUI 交互。
server?=?await?serve(environmentService.mainIPCHandle); once(lifecycleService.onWillShutdown)(()?=>?server.dispose());渲染進(jìn)程
僅啟動(dòng)主進(jìn)程并不能給你的應(yīng)用創(chuàng)建應(yīng)用窗口。窗口是通過 main 文件里的主進(jìn)程調(diào)用叫 BrowserWindow 的模塊創(chuàng)建的。
主進(jìn)程與渲染進(jìn)程之間的通信
在 electron 中,主進(jìn)程與渲染進(jìn)程有很多通信的方法。比如 ipcRenderer 和 ipcMain,還可以在渲染進(jìn)程使用 remote 模塊。
ipcMain & ipcRenderer
主進(jìn)程:ipcMain
渲染進(jìn)程:ipcRenderer
ipcMain 模塊和 ipcRenderer 是類 EventEmitter 的實(shí)例。
在主進(jìn)程中使用 ipcMain 接收渲染線程發(fā)送過來的異步或同步消息,發(fā)送過來的消息將觸發(fā)事件。
在渲染進(jìn)程中使用 ipcRenderer 向主進(jìn)程發(fā)送同步或異步消息,也可以接收到主進(jìn)程的消息。
發(fā)送消息,事件名為 channel .
回應(yīng)同步消息, 你可以設(shè)置 event.returnValue .
回應(yīng)異步消息, 你可以使用 event.sender.send(…)
創(chuàng)建 IPC 服務(wù)
src/vs/base/parts/ipc/node/ipc.net.ts
這里返回一個(gè) promise 對(duì)象,成功則 createServer
export?function?serve(hook:?any):?Promise<Server>?{return?new?Promise<Server>((c,?e)?=>?{const?server?=?createServer();server.on('error',?e);server.listen(hook,?()?=>?{server.removeListener('error',?e);c(new?Server(server));});}); }創(chuàng)建信道
src/vs/code/electron-main/app.ts
mainIpcServer * launchChannel
electronIpcServer
_ updateChannel
_ issueChannel
_ workspacesChannel
_ windowsChannel
_ menubarChannel
_ urlChannel
_ storageChannel
_ logLevelChannel
每一個(gè)信道,內(nèi)部實(shí)現(xiàn)兩個(gè)方法 listen 和 call
例如:src/vs/platform/localizations/node/localizationsIpc.ts
構(gòu)造函數(shù)綁定事件
export?class?LocalizationsChannel?implements?IServerChannel?{onDidLanguagesChange:?Event<void>;constructor(private?service:?ILocalizationsService)?{this.onDidLanguagesChange?=?Event.buffer(service.onDidLanguagesChange,?true);}listen(_:?unknown,?event:?string):?Event<any>?{switch?(event)?{case?'onDidLanguagesChange':?return?this.onDidLanguagesChange;}throw?new?Error(`Event?not?found:?${event}`);}call(_:?unknown,?command:?string,?arg?:?any):?Promise<any>?{switch?(command)?{case?'getLanguageIds':?return?this.service.getLanguageIds(arg);}throw?new?Error(`Call?not?found:?${command}`);} }7.主要窗口
workbench.ts 中 startup 里面 Workbench 負(fù)責(zé)創(chuàng)建主界面
src/vs/workbench/browser/workbench.ts
渲染主工作臺(tái),渲染完之后加入到 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?最左側(cè)菜單選項(xiàng)卡//SIDEBAR_PART?左側(cè)邊欄,顯示文件,結(jié)果展示等//EDITOR_PART?右側(cè)窗口,代碼編寫,歡迎界面等//STATUSBAR_PART?底部狀態(tài)欄[{?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-regionthis.container.insertBefore(partContainer,?this.container.lastChild);}this.getPart(id).create(partContainer,?options);});//?將工作臺(tái)添加至container?dom渲染this.parent.appendChild(this.container);}workbench 最后調(diào)用 this.layout()方法,將窗口占據(jù)整個(gè)界面,渲染完成
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?widgetthis.workbenchGrid.layout(this._dimension.width,?this._dimension.height);}?else?{this.workbenchGrid.layout(options);}//?Emit?as?eventthis._onLayout.fire(this._dimension);}}8.開發(fā)調(diào)試
app.once('ready',?function?()?{//啟動(dòng)追蹤if?(args['trace'])?{//?@ts-ignoreconst?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();} });啟動(dòng)追蹤
這里如果傳入 trace 參數(shù),在 onReady 啟動(dòng)之前會(huì)調(diào)用 chromium 的收集跟蹤數(shù)據(jù),
提供的底層的追蹤工具允許我們深度了解 V8 的解析以及其他時(shí)間消耗情況,
一旦收到可以開始記錄的請(qǐng)求,記錄將會(huì)立馬啟動(dòng)并且在子進(jìn)程是異步記錄聽的. 當(dāng)所有的子進(jìn)程都收到 startRecording 請(qǐng)求的時(shí)候,callback 將會(huì)被調(diào)用.
categoryFilter 是一個(gè)過濾器,它用來控制那些分類組應(yīng)該被用來查找.過濾器應(yīng)當(dāng)有一個(gè)可選的 - 前綴來排除匹配的分類組.不允許同一個(gè)列表既是包含又是排斥.
contentTracing.startRecording(options, callback)
options Object
_ categoryFilter String
_ traceOptions Stringcallback Function
關(guān)于 trace 的詳細(xì)介紹
結(jié)束追蹤
contentTracing.stopRecording(resultFilePath, callback)
resultFilePath String
callback Function
在成功啟動(dòng)窗口后,程序結(jié)束性能追蹤,停止對(duì)所有子進(jìn)程的記錄.
子進(jìn)程通常緩存查找數(shù)據(jù),并且僅僅將數(shù)據(jù)截取和發(fā)送給主進(jìn)程.這有利于在通過 IPC 發(fā)送查找數(shù)據(jù)之前減小查找時(shí)的運(yùn)行開銷,這樣做很有價(jià)值.因此,發(fā)送查找數(shù)據(jù),我們應(yīng)當(dāng)異步通知所有子進(jìn)程來截取任何待查找的數(shù)據(jù).
一旦所有子進(jìn)程接收到了 stopRecording 請(qǐng)求,將調(diào)用 callback ,并且返回一個(gè)包含查找數(shù)據(jù)的文件.
如果 resultFilePath 不為空,那么將把查找數(shù)據(jù)寫入其中,否則寫入一個(gè)臨時(shí)文件.實(shí)際文件路徑如果不為空,則將調(diào)用 callback .
debug
調(diào)試界面在菜單欄找到 Help->Toggle Developers Tools
調(diào)出 Chrome 開發(fā)者調(diào)試工具進(jìn)行調(diào)試
參考
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/
推薦閱讀:
下一代 TGW 從13Mpps到50Mpps性能優(yōu)化之旅
寫給前端工程師的 Flutter 詳細(xì)教程
現(xiàn)代化 C++ 開發(fā)工具 CLion 從入門到精通
總結(jié)
以上是生活随笔為你收集整理的微软 VSCode IDE 源码分析揭秘的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TEG《选择》乘风破浪 · 披荆斩棘
- 下一篇: 深入浅出理解 Spark:环境部署与工作