日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

微软 VSCode IDE 源码分析揭秘

發(fā)布時(shí)間:2024/2/28 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 微软 VSCode IDE 源码分析揭秘 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

作者: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

app.once('ready',?function?()?{//啟動(dòng)追蹤,后面會(huì)講到,跟性能檢測(cè)優(yōu)化相關(guān)。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();} }); function?onReady()?{perf.mark('main:appReady');Promise.all([nodeCachedDataDir.ensureExists(),?userDefinedLocale]).then(([cachedDataDir,?locale])?=>?{//1.?這里嘗試獲取本地配置信息,如果有的話會(huì)傳遞到startupif?(locale?&&?!nlsConfiguration)?{nlsConfiguration?=?lp.getNLSConfiguration(product.commit,?userDataPath,?metaDataFile,?locale);}if?(!nlsConfiguration)?{nlsConfiguration?=?Promise.resolve(undefined);}nlsConfiguration.then(nlsConfig?=>?{//4.?首先會(huì)檢查用戶語言環(huán)境配置,如果沒有設(shè)置默認(rèn)使用英語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.?接收到有效的配置傳入是其生效,調(diào)用startupif?(nlsConfig)?{startup(nlsConfig);}//?3.?這里嘗試使用本地的應(yīng)用程序//?應(yīng)用程序設(shè)置區(qū)域在ready事件后才有效else?{let?appLocale?=?app.getLocale();if?(!appLocale)?{startup({?locale:?'en',?availableLanguages:?{}?});}?else?{//?配置兼容大小寫敏感,所以統(tǒng)一轉(zhuǎn)換成小寫appLocale?=?appLocale.toLowerCase();//?這里就會(huì)調(diào)用config服務(wù),把本地配置加載進(jìn)來再調(diào)用startuplp.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 是程序真正啟動(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 方法打開窗口

    private?openFirstWindow(accessor:?ServicesAccessor,?electronIpcServer:?ElectronIPCServer,?sharedProcessClient:?Promise<Client<string>>):?ICodeWindow[]?{...//?1.?注入Electron?IPC?Service,?windows窗口管理,菜單欄等服務(wù)//?2.?根據(jù)environmentService進(jìn)行參數(shù)配置const?macOpenFiles:?string[]?=?(<any>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;...//?打開主窗口,默認(rèn)從執(zhí)行命令行中讀取參數(shù)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 中執(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

    private?openFirstWindow(accessor:?ServicesAccessor,?electronIpcServer:?ElectronIPCServer,?sharedProcessClient:?Promise<Client<string>>):?ICodeWindow[]?{//?Register?more?Main?IPC?servicesconst?launchService?=?accessor.get(ILaunchService);const?launchChannel?=?new?LaunchChannel(launchService);this.mainIpcServer.registerChannel('launch',?launchChannel);//?Register?more?Electron?IPC?servicesconst?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?managementconst?logLevelChannel?=?new?LogLevelSetterChannel(accessor.get(ILogService));electronIpcServer.registerChannel('loglevel',?logLevelChannel);sharedProcessClient.then(client?=>?client.registerChannel('loglevel',?logLevelChannel));...//?default:?read?paths?from?clireturn?windowsMainService.open({context,cli:?args,forceNewWindow:?args['new-window']?||?(!hasCliArgs?&&?args['unity-launch']),diffMode:?args.diff,noRecentEntry,waitMarkerFileURI,gotoLineMode:?args.goto,initialStartup:?true});}

    每一個(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

    startup():?IInstantiationService?{try?{...instantiationService.invokeFunction(async?accessor?=>?{//?渲染主工作界面this.renderWorkbench(instantiationService,?accessor.get(INotificationService)?as?NotificationService,?storageService,?configurationService);//?界面布局this.createWorkbenchLayout(instantiationService);//?Layoutthis.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} }

    渲染主工作臺(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 String

    • callback 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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。