Раскрыта технология VSCode (1)

Visual Studio Code

предисловие

Visual Studio Code (далее VSCode) — это легкий и мощный кроссплатформенный редактор кода с открытым исходным кодом (IDE).VSCode использует Electron, используемый редактор кода называется Monaco, а Monaco — это также Visual Studio Team Service (Visual Studio Online). ) Используемый редактор кода, с точки зрения языка, VSCode использует собственную разработку языка TypeScript.

VSCode предоставляет мощный механизм расширения плагинов и предоставляетРынок плагиновДля разработчиков, чтобы публиковать и загружать плагины. VSCode предоставляет множество моделей возможностей расширений, таких как базовая подсветка синтаксиса/подсказки API, переходы по ссылкам (переход к определениям), поиск файлов, настройка темы, расширенные протоколы отладки и многое другое. Но плагину не разрешен прямой доступ к базовому UI DOM (то есть сложно настроить внешний вид VSCode), потому что команда разработчиков VSCode часто меняет UI Dom по мере оптимизации VSCode, поэтому возможности настройки пользовательского интерфейса ограничены. .

Но когда вы хотите разработать специализированную IDE, не хотите начинать с нуля, а встать на плечи гигантов для вторичной разработки, тогда VSCode будет вашим лучшим выбором, напримерWeex Studio,Крыло белой цапли,Быстрая среда разработки приложенийДругие IDE основаны на расширениях VSCode.

Weex Studio

Egret Wing

Быстрое приложение

Эта серия статей поможет вам понять общую архитектуру и метод настройки исходного кода VSCode, шаг за шагом начать с исходного кода и настроить специальный инструмент разработки.

Введение в технологию

Студенты, которые изучают исходный код VSCode, в основном выполняют работу с интерфейсом, поэтому node.js и javascript являются базовыми навыками, поэтому здесь нет необходимости переоценивать их. Однако, прежде чем читать исходный код VSCode, вам все равно необходимо понять соответствующие технические фреймворки, используемые VSCode.

Электронное введение

Как мы все знаем, VSCode — это приложение для редактирования рабочего стола, но интерфейс не может делать настольные приложения только с помощью js, поэтому мы используемElectronстроить. Electron основан на Chromium и Node.js и использует JavaScript, HTML и CSS для создания кроссплатформенных настольных приложений.Он совместим с Mac, Windows и Linux и может создавать приложения для трех платформ.

С точки зрения реализации Electron = Node.js + Chromium + Native API.

Другими словами, у Electron есть среда выполнения Node, которая дает пользователям возможность взаимодействовать с нижним уровнем системы, он опирается на Chromium для обеспечения поддержки взаимодействия с интерфейсом на основе веб-технологий (HTML, CSS, JS), а также имеет некоторые функции платформы, такие как уведомления на рабочем столе.

С точки зрения дизайна API, приложение Electron обычно имеет один основной процесс и несколько процессов рендеринга:

  • основной процесс: доступ к Node и Native API можно получить в среде основного процесса.
  • Процесс рендерера: API браузера, Node API и некоторые Native API доступны в среде процесса рендеринга.

Основной процесс и процесс рендеринга

В приложениях Electron основной процесс электрона можно запустить, выполнив файл, на который указывает основное поле в package.json. Используйте экземпляр BrowserWindow для создания веб-страниц в основном процессе, а электронное приложение имеет и может иметь только один основной процесс. Основной процесс обычно используется для:

  • Управление несколькими формами (создание/переключение)
  • Управление жизненным циклом приложений
  • В качестве базовой станции технологической связи (IPC-сервер)
  • Панель панели панели инструментов

Поскольку электрон использует Chromium для отображения веб-страниц, также используется многопроцессорная архитектура Chromium. Каждая веб-страница работает в своем собственном процессе, называемом процессом визуализации. Процесс рендеринга обычно отвечает за конкретные бизнес-функции, связанные с взаимодействием с интерфейсом.

На веб-странице вызов базового API системы не разрешен, поскольку очень опасно обрабатывать базовые ресурсы графического интерфейса на веб-странице и легко вызвать утечку ресурсов. Если вы хотите выполнять операции с графическим интерфейсом на веб-странице, процесс рендеринга соответствующей веб-страницы должен взаимодействовать с основным процессом и делать запросы к основному процессу для выполнения этих операций. взаимодействовать с процессом рендеринга, например, с модулями ipcRenderer и ipcMain для отправки сообщений, а также с модулем удаленной связи в стиле RPC. Что касается взаимодействия между процессами Electron, я не буду вводить здесь слишком много.Вы можете прочитать официальный сайт Electron и онлайн-материалы, чтобы узнать о взаимодействии между основным процессом и процессом рендеринга.

Для получения дополнительной информации см.Архитектура электронного приложения

РедакторMonaco Editor

Ранее у Microsoft был проект под названием Monaco Workbench, который позже стал VSCode, иMonaco Editor(далее monaco) — это веб-редактор, выросший из этого проекта.Большая часть их кода (monaco-editor-core) является общей, поэтому monaco и VSCode практически одинаковы в редактировании кода, взаимодействии и пользовательском интерфейсе. Точно так же, разница в том, что платформы у них разные, monaco основан на браузерах, а VSCode основан на электронах, поэтому VSCode функциональнее и мощнее.

TypeScript

TypeScript— это расширенный набор типов JavaScript, который компилируется в обычный JavaScript. TypeScript работает в любом браузере, на любом компьютере и в любой операционной системе и имеет открытый исходный код. TypeScript имеет следующие характеристики:

  • Аннотации типов и проверка типов во время компиляции
  • строго типизированный язык
  • объектно-ориентированный
  • сорт
  • интерфейс
  • лямбда-функция
  • Дженерики

Исходный код VSCode в основном написан на TypeScript, поэтому при изучении исходного кода VSCode вы должны сначала понять основы использования TypeScript.

Приведенный выше контент является базовым для изучения исходного кода VSCode.Вы можете сначала изучить Electron, чтобы создать простое настольное приложение, а затем изучить базовый синтаксис TypeScript, а затем приступить к изучению исходного кода VSCode.

VSCode Архитектура

VSCode включает в себя основной процесс и процесс рендеринга.В то же время, поскольку VSCode предоставляет возможность расширения плагинов, и ради безопасности и стабильности, на рисунке есть еще один Extension Host.На самом деле, это Extension Host также является независимым процессом для запуска нашего кода плагина. И, как и процесс рендеринга, они независимы друг от друга. Хост-процесс расширения предоставляет некоторые API-интерфейсы VSCode для использования разработчиками плагинов.

Структура процесса VSCode

VSCode использует многопроцессорную архитектуру, которая в основном состоит из следующих процессов после запуска:

  • закулисный процесс
  • Окно редактора — запускается фоновым процессом, также многопроцессорная архитектура
    • Пользовательский интерфейс написан на HTML
      • ActivityBar
      • SideBar
      • Panel
      • Editor
      • StatusBar
    • Асинхронный ввод-вывод Nodejs
      • FileService
      • ConfigurationService
    • Хост-процесс плагина
      • Экземпляр плагина
        • Подпроцесс плагина — например, языковая служба TS
      • Экземпляр плагина
      • Экземпляр плагина
    • Процесс отладки
    • Процесс поиска

закулисный процесс

Фоновый процесс — это точка входа VSCode, которая в основном отвечает за управление жизненным циклом редактора, взаимодействие между процессами, автоматическое обновление, управление меню и т. д.

Когда мы запускаем VSCode, сначала запускается фоновый процесс, который считывает различную информацию о конфигурации и историю, а затем интегрирует эту информацию с путем к основному файлу HTML пользовательского интерфейса главного окна в URL-адрес и запускает окно браузера для отображения пользовательского интерфейса редактора. Фоновый процесс всегда будет обращать внимание на состояние процесса пользовательского интерфейса.Когда все процессы пользовательского интерфейса закрыты, весь редактор закрывается.

Кроме того, фоновый процесс также откроет локальный сокет.При запуске нового процесса VSCode он попытается подключиться к сокету и передать ему информацию о параметрах запуска, а существующий VSCode выполнит соответствующие действия, которые может обеспечить уникальность VSCode и избежать проблем, вызванных открытием нескольких папок.

Окно редактора

Процесс окна редактора отвечает за представление всего пользовательского интерфейса. Это та часть, которую мы видели. Пользовательский интерфейс полностью написан на HTML, и вводить особо нечего.

Асинхронный ввод-вывод Nodejs

Чтение и сохранение файлов проекта выполняются NodeJS API основного процесса, потому что все это асинхронные операции, даже если есть относительно большие файлы, это не будет блокировать пользовательский интерфейс. IO и UI находятся в одном процессе, а асинхронные операции используются для обеспечения скорости отклика UI на основе обеспечения производительности IO.

процесс плагина

Каждое окно пользовательского интерфейса запускает дочерний процесс NodeJS в качестве хост-процесса плагина. Все плагины будут работать вместе в этом процессе. Основная цель этого дизайна — избежать сложной системы подключаемых модулей, блокирующей ответ пользовательского интерфейса. Однако размещение плагина в отдельном процессе также имеет очевидные недостатки.Поскольку это отдельный процесс, а не процесс пользовательского интерфейса, нет прямого доступа к дереву DOM. Становится сложно эффективно изменять пользовательский интерфейс в режиме реального времени. расширение в VSCode API для расширения UI в системе почти нет.

Процесс отладки

Подключаемый модуль Debugger немного отличается от обычных подключаемых модулей.Он не запускается в процессе подключаемого модуля, но каждый раз при отладке пользовательским интерфейсом открывается новый процесс.

процесс поиска

Поиск — очень трудоемкая задача, и VSCode также использует отдельный процесс для реализации этой функции, чтобы обеспечить эффективность главного окна. Разделение трудоемких задач на несколько процессов эффективно обеспечивает скорость отклика основного процесса.

Исходный код VSCode работает

Установка среды

  • Git
  • Node.JS, версия >= 10.16.0,
  • Yarn
  • Python версии 2.7 или выше не поддерживает версию 3.0 и выше (компьютер Mac поставляется с Python, загружать его не нужно)

Я полагаю, что все знакомы с установкой вышеуказанной среды. Поскольку на моем компьютере используется Mac, все соответствующие примеры работают в системе Mac, а примеры в Windows аналогичны. Подробнее см.Официальный сайт Wiki-документ.

Загрузка исходного кода

Каждый раз, когда исходный код VSCode обновляется, часть пользовательского интерфейса будет оптимизирована, но общая структура останется прежней.Возможно, старая версия VSCode использовалась в руководстве по исходному коду о VSCode в Интернете, здесь я использую последнюю версию - версия v1.39.2, чтобы объяснить.

Загрузка исходного кода:VSCode Releases

После скачивания распакуйте его и откройте редактором VSCode, введите в командной строкеyarnКоманда для установки зависимостей, которая займет много времени в середине.

Многие пакеты зависимостей будут установлены посередине.Если вы обнаружите, что сеть заблокирована, загрузка не удалась и т. д., вам сначала нужно проверить правильность указанной выше версии среды разработки, и, если необходимо, вам нужно выйти в Интернет. научно.

запуск исходного кода

После завершения установки зависимостей войдите в проект и выполнитеyarn watchВыполните задание сборки:

пока не увидишьFinished compilation with 0 errors after 108726 msВывод показывает, что сборка прошла успешно!

В это время не закрывайте текущую командную строку. Команда сборки не завершается. Она будет отслеживать изменения файла исходного кода vscode. Если есть изменение, она немедленно выполнит инкрементную сборку и отразит результат сборки. изменение исходного кода в режиме реального времени.

Запустите новую командную строку и выполните./scripts/code.sh, выполненный под окнами\scripts\code.bat, Электрон скачан.

После завершения загрузки он готов к запуску. Рабочий интерфейс выглядит следующим образом:

Структура исходного кода VSCode

Общая структура файловых каталогов выглядит следующим образом:

├── build         # gulp编译构建脚本
├── extensions    # 内置插件
├── gulpfile.js   # gulp task
├── out           # 编译输出目录
├── resources     # 平台相关静态资源,图标等
├── scripts       # 工具脚本,开发/测试
├── src           # 源码目录
├── test          # 测试套件
└── product.json  # App meta信息

Структура каталогов файлов в src, как показано ниже:

├── bootstrap-amd.js    # 子进程实际入口
├── bootstrap-fork.js   #
├── bootstrap-window.js #
├── bootstrap.js        # 子进程环境初始化
├── buildfile.js        # 构建config
├── cli.js              # CLI入口
├── main.js             # 主进程入口
├── paths.js            # AppDataPath与DefaultUserDataPath
├── typings
│   └── xxx.d.ts        # ts类型声明
└── vs
    ├── base            # 定义基础的工具方法和基础的 DOM UI 控件
    │   ├── browser     # 基础UI组件,DOM操作、交互事件、DnD等
    │   ├── common      # diff描述,markdown解析器,worker协议,各种工具函数
    │   ├── node        # Node工具函数
    │   ├── parts       # IPC协议(Electron、Node),quickopen、tree组件
    │   ├── test        # base单测用例
    │   └── worker      # Worker factory 和 main Worker(运行IDE Core:Monaco)
    ├── code            # VSCode Electron 应用的入口,包括 Electron 的主进程脚本入口
    │   ├── electron-browser # 需要 Electron 渲染器处理API的源代码(可以使用 common, browser, node)
    │   ├── electron-main    # 需要Electron主进程API的源代码(可以使用 common, node)
    │   ├── node        # 需要Electron主进程API的源代码(可以使用 common, node)
    │   ├── test
    │   └── code.main.ts
    ├── editor          # Monaco Editor 代码编辑器:其中包含单独打包发布的 Monaco Editor 和只能在 VSCode 的使用的部分
    │   ├── browser     # 代码编辑器核心
    │   ├── common      # 代码编辑器核心
    │   ├── contrib     # vscode 与独立 IDE共享的代码
    │   ├── standalone  # 独立 IDE 独有的代码
    │   ├── test
    │   ├── editor.all.ts
    │   ├── editor.api.ts
    │   ├── editor.main.ts
    │   └── editor.worker.ts
    ├── platform        # 依赖注入的实现和 VSCode 使用的基础服务 Services
    ├── workbench       # VSCode 桌面应用程序工作台的实现
    ├── buildunit.json
    ├── css.build.js    # 用于插件构建的CSS loader
    ├── css.js          # CSS loader
    ├── loader.js       # AMD loader(用于异步加载AMD模块,类似于require.js)
    ├── nls.build.js    # 用于插件构建的 NLS loader
    └── nls.js          # NLS(National Language Support)多语言loader

Прежде всего, VSCode состоит из основного ядра и встроенных расширений.Ядро реализует базовый редактор кода и настольное приложение VSCode, то есть рабочую среду VSCode.Он также предоставляет API-интерфейсы расширений, позволяющие использовать встроенные расширения и расширения, разработанные третьим лицам для расширения возможностей VSCode Core.

Во-вторых, поскольку VSCode опирается на Electron, а у Electron есть основной процесс и процесс рендеринга, API, которые они могут использовать, недостаточно, поэтому организация каждого каталога в VSCode Core также организована в соответствии с API, которые они могут использовать. В каждом подкаталоге Core целевая среда, в которой выполняется код, делится на следующие категории:

  • общий: используйте только исходный код JavaScript API, может работать в любой среде
  • браузер: исходный код, который должен использовать API, предоставляемый браузером, например, манипулирование DOM и т. д.
  • узел: исходный код, необходимый для использования API, предоставляемого Node.js.
  • электронный браузер: исходный код, необходимый для использования API-интерфейса рендеринга Electron.
  • electronic-main: исходный код, необходимый для использования API основного процесса Electron.

Согласно приведенным выше правилам, исходный код в src/vs/workbench/browser может использовать только базовый API JavaScript и API, предоставляемый браузером, в то время как исходный код в src/vs/workbench/electron-browser может использовать JavaScript. API, API, предоставляемый браузером, API, предоставляемый Node.js, и API в процессе рендеринга Electron.

В репозитории кода VSCode, помимо упомянутого выше src/vs Core, также есть большой кусок встроенных расширений VSCode, а их исходный код находится в extensions.

В качестве редактора кода VSCode дополнен различными функциями редактирования кода, такими как подсветка синтаксиса, подсказки о завершении и проверка. Поэтому во встроенных расширениях VSCode большинство из них являются поддержкой расширений различных языков программирования, таких как: extensions\html, extensions\javascript, extensions\cpp и т. д., и будут появляться большинство языковых расширений, таких как .tmTheme , .tmLanguage и т. д. Грамматическое определение TextMate. Другим типом встроенного расширения является расширение тела VSCode, такое как расширения тела/темы по умолчанию VSCode и т. д.

Процесс запуска VSCode

Поскольку VSCode разработан на основе Electron, запись запуска Electron находится в package.json, а сценарий, представленный полем main, является сценарием запуска приложения, который будет выполняться в основном процессе.

./out/main.js Очевидно это вход в основной процесс программы, но main.js под папкой out, судя по всему вне скомпилированного вывода, потом находим под src файл tsconfig.json такой конфигурации:

"outDir": "../out",

Таким образом, очевидно, что код в src компилируется и выводится в папку out. Таким образом, реальная запись находится в main.js в разделе src, и тогда вам нужно только проанализировать ее из файла main.js.

В main.js мы видим следующую строку, импортированную

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

electron.app отвечает за управление жизненным циклом приложений Electron, запускается в основном процессе, а затем находитreadyпрослушать событие

// Load our code once ready
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();
	}
});

Этот монитор готовности указывает, что Electron будет инициализирован и подготовлен, а некоторые API-интерфейсы можно будет использовать только после запуска события готовности. Создание окна также необходимо создать после того, как оно будет готово. Наконец, эта функция называетсяonReady()функция.

function onReady() {
	perf.mark('main:appReady');

	Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => {
		if (locale && !nlsConfiguration) {
			nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
		}

		if (!nlsConfiguration) {
			nlsConfiguration = Promise.resolve(undefined);
		}

		// First, we need to test a user defined locale. If it fails we try the app locale.
		// If that fails we fall back to English.
		nlsConfiguration.then(nlsConfig => {

			const startup = nlsConfig => {
				nlsConfig._languagePackSupport = true;
				process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
				process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';

				// Load main in AMD
				perf.mark('willLoadMainBundle');
				require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
					perf.mark('didLoadMainBundle');
				});
			};

			// We received a valid nlsConfig from a user defined locale
			if (nlsConfig) {
				startup(nlsConfig);
			}

			// Try to use the app locale. Please note that the app locale is only
			// valid after we have received the app ready event. This is why the
			// code is here.
			else {
				let appLocale = app.getLocale();
				if (!appLocale) {
					startup({ locale: 'en', availableLanguages: {} });
				} else {

					// See above the comment about the loader and case sensitiviness
					appLocale = appLocale.toLowerCase();

					lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale).then(nlsConfig => {
						if (!nlsConfig) {
							nlsConfig = { locale: appLocale, availableLanguages: {} };
						}

						startup(nlsConfig);
					});
				}
			}
		});
	}, console.error);
}

Вся функция считывает настройки языка пользователя, а затем в конечном итоге вызываютstartup().

const startup = nlsConfig => {
    nlsConfig._languagePackSupport = true;
    process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
    process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';

    // Load main in AMD
    perf.mark('willLoadMainBundle');
    require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
        perf.mark('didLoadMainBundle');
    });
};

Стартап в основном представленboostrap-amd, этот bootstrap-amd представляет /vs/loader и создает загрузчик.

const loader = require('./vs/loader');

Загрузчик — это собственный модуль Microsoft AMD, загружающий проект с открытым исходным кодом:GitHub.com/Microsoft/V…

Потом загрузить через загрузчикvs/code/electron-main/mainмодуль, это реальная запись VSCode, а затем вvs/code/electron-main/main.tsВидно, что определенныйCodeMainкласс, затем инициализируйте класс CodeMain и вызовитеmainфункция.

// src/vs/code/electron-main/main
class CodeMain {
	main(): void {
		...
		// Launch
		this.startup(args);
	}

	private async startup(args: ParsedArgs): Promise<void> {

		// We need to buffer the spdlog logs until we are sure
		// we are the only instance running, otherwise we'll have concurrent
		// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
		const bufferLogService = new BufferLogService();

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

			// Init services
			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;
				}
			});

			// Startup
			await instantiationService.invokeFunction(async accessor => {
				const environmentService = accessor.get(IEnvironmentService);
				const logService = accessor.get(ILogService);
				const lifecycleMainService = accessor.get(ILifecycleMainService);
				const configurationService = accessor.get(IConfigurationService);

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

				bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel());
				once(lifecycleMainService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose());

				return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
			});
		} catch (error) {
			instantiationService.invokeFunction(this.quit, error);
		}
	}

	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(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
		services.set(IStateService, new SyncDescriptor(StateService));
		services.set(IRequestService, new SyncDescriptor(RequestMainService));
		services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
		services.set(ISignService, new SyncDescriptor(SignService));

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

// Main Startup
const code = new CodeMain();
code.main();

можно увидетьmain()функция, наконец, вызванаstartup()функция.

существуетstartup()функция, вызовите сначалаthis.createServices()функция для создания зависимых служб. Сервисы — это ряд публичных модулей в VSCode, которые можно инжектить, эти Сервисы отвечают за разные функции, и здесь создается несколько базовых сервисов. В дополнение к этим базовым службам VSCode также содержит большое количество служб, таких как IModeService, ICodeEditorService, IPanelService и т. д. Режим «внедрения зависимостей», реализованный VSCode, может использоваться в качестве параметра конструктора в форме Decorator, где эти службы Объявление зависимостей будет автоматически внедрено в класс. Что касается внедрения зависимостей сервисов, следующие главы будут посвящены объяснению.

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
		// environmentService 一些基本配置,包括运行目录、用户数据目录、工作区缓存目录等
		services.set(IEnvironmentService, environmentService);

		const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
		process.once('exit', () => logService.dispose());
		// logService 日志服务
		services.set(ILogService, logService);

		// ConfigurationService 配置项
		services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource));
		// LifecycleService 生命周期相关的一些方法
		services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService));
		// StateService 持久化数据
		services.set(IStateService, new SyncDescriptor(StateService));
		// RequestService 请求服务
		services.set(IRequestService, new SyncDescriptor(RequestMainService));
		services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
		services.set(ISignService, new SyncDescriptor(SignService));

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

видно в кодеcreateServices()Наконец, создайте экземплярInstantiationServiceэкземпляр и вернуть его, затем вstartup()Вызовите метод createInstance службы InstantiationService и передайте параметр CodeApplication, что означает инициализацию экземпляра CodeApplication, а затемstartup()метод.

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

Далее мы рассмотрим метод запуска в CodeApplication.

//src/vs/code/electron-main/app.ts
export class CodeApplication extends Disposable {
	...
	async startup(): Promise<void> {
		this.logService.debug('Starting VS Code');
		this.logService.debug(`from: ${this.environmentService.appRoot}`);
		this.logService.debug('args:', this.environmentService.args);

		// Make sure we associate the program with the app user model id
		// This will help Windows to associate the running program with
		// any shortcut that is pinned to the taskbar and prevent showing
		// two icons in the taskbar for the same app.
		const win32AppUserModelId = product.win32AppUserModelId;
		if (isWindows && win32AppUserModelId) {
			app.setAppUserModelId(win32AppUserModelId);
		}

		// Fix native tabs on macOS 10.13
		// macOS enables a compatibility patch for any bundle ID beginning with
		// "com.microsoft.", which breaks native tabs for VS Code when using this
		// identifier (from the official build).
		// Explicitly opt out of the patch here before creating any windows.
		// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
		try {
			if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
				systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
			}
		} catch (error) {
			this.logService.error(error);
		}

		// Create Electron IPC Server
		const electronIpcServer = new ElectronIPCServer();

		// Resolve unique machine ID
		this.logService.trace('Resolving machine identifier...');
		const { machineId, trueMachineId } = await this.resolveMachineId();
		this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`);

		// Spawn shared process after the first window has opened and 3s have passed
		const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
		const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
		this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
			this._register(new RunOnceScheduler(async () => {
				const userEnv = await getShellEnvironment(this.logService, this.environmentService);

				sharedProcess.spawn(userEnv);
			}, 3000)).schedule();
		});

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

		// Create driver
		if (this.environmentService.driverHandle) {
			const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService);

			this.logService.info('Driver started at:', this.environmentService.driverHandle);
			this._register(server);
		}

		// Setup Auth Handler
		this._register(new ProxyAuthHandler());

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

		// Post Open Windows Tasks
		this.afterWindowOpen();

		// Tracing: Stop tracing after windows are ready if enabled
		if (this.environmentService.args.trace) {
			this.stopTracingEventually(windows);
		}
	}

	...

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

		// Register more Main IPC services
		const launchMainService = accessor.get(ILaunchMainService);
		const launchChannel = createChannelReceiver(launchMainService, { disableMarshalling: true });
		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 = createChannelReceiver(issueService);
		electronIpcServer.registerChannel('issue', issueChannel);

		const electronService = accessor.get(IElectronService);
		const electronChannel = createChannelReceiver(electronService);
		electronIpcServer.registerChannel('electron', electronChannel);
		sharedProcessClient.then(client => client.registerChannel('electron', electronChannel));

		const sharedProcessMainService = accessor.get(ISharedProcessMainService);
		const sharedProcessChannel = createChannelReceiver(sharedProcessMainService);
		electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel);

		const workspacesService = accessor.get(IWorkspacesService);
		const workspacesChannel = createChannelReceiver(workspacesService);
		electronIpcServer.registerChannel('workspaces', workspacesChannel);

		const menubarService = accessor.get(IMenubarService);
		const menubarChannel = createChannelReceiver(menubarService);
		electronIpcServer.registerChannel('menubar', menubarChannel);

		const urlService = accessor.get(IURLService);
		const urlChannel = createChannelReceiver(urlService);
		electronIpcServer.registerChannel('url', urlChannel);

		const storageMainService = accessor.get(IStorageMainService);
		const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
		electronIpcServer.registerChannel('storage', storageChannel);

		const loggerChannel = new LoggerChannel(accessor.get(ILogService));
		electronIpcServer.registerChannel('logger', loggerChannel);
		sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));

		// ExtensionHost Debug broadcast service
		electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());

		// Signal phase: ready (services set)
		this.lifecycleMainService.phase = LifecycleMainPhase.Ready;

		// Propagate to clients
		const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
		this.dialogMainService = accessor.get(IDialogMainService);

		// Create a URL handler to open file URIs in the active window
		const environmentService = accessor.get(IEnvironmentService);
		urlService.registerHandler({
			async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {

				// Catch file URLs
				if (uri.authority === Schemas.file && !!uri.path) {
					const cli = assign(Object.create(null), environmentService.args);
					const urisToOpen = [{ fileUri: URI.file(uri.fsPath) }];

					windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true });

					return true;
				}

				return false;
			}
		});

		// Create a URL handler which forwards to the last active window
		const activeWindowManager = new ActiveWindowManager(electronService);
		const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
		const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter);
		const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter);
		const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);

		// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
		// if there is none
		if (isMacintosh) {
			urlService.registerHandler({
				async handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
					if (windowsMainService.getWindowCount() === 0) {
						const cli = { ...environmentService.args };
						const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true, gotoLineMode: true });

						await window.ready();

						return urlService.open(uri);
					}

					return false;
				}
			});
		}

		// Register the multiple URL handler
		urlService.registerHandler(multiplexURLHandler);

		// Watch Electron URLs and forward them to the UrlService
		const args = this.environmentService.args;
		const urls = args['open-url'] ? args._urls : [];
		const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService);
		this._register(urlListener);

		// Open our first window
		const macOpenFiles: string[] = (<any>global).macOpenFiles;
		const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
		const hasCliArgs = args._.length;
		const hasFolderURIs = !!args['folder-uri'];
		const hasFileURIs = !!args['file-uri'];
		const noRecentEntry = args['skip-add-to-recently-opened'] === true;
		const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;

		// new window if "-n" was used without paths
		if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
			return windowsMainService.open({
				context,
				cli: args,
				forceNewWindow: true,
				forceEmpty: true,
				noRecentEntry,
				waitMarkerFileURI,
				initialStartup: true
			});
		}

		// mac: open-file event received on startup
		if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
			return windowsMainService.open({
				context: OpenContext.DOCK,
				cli: args,
				urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
				noRecentEntry,
				waitMarkerFileURI,
				gotoLineMode: false,
				initialStartup: true
			});
		}

		// 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
		});
	}
	...
}

В CodeApplication.startup сначала запускается общий процесс SharedProcess, а также создаются некоторые службы, связанные с окнами, в том числе WindowsManager, WindowsService, MenubarService и т. д., которые отвечают за такие функции, как управление окном, многооконность и меню. тогда позвониopenFirstWindowметод открытия окна.

В openFirstWindow сначала создайте ряд каналов Electron IPC для связи между основным процессом и процессом рендеринга.Каналы window и logLevel также будут зарегистрированы в sharedProcessClient, который является клиентом, с которым основной процесс взаимодействует с общим процессом (SharedProcess). ). Затем вызывается метод windowsMainService.open в соответствии с соответствующими параметрами (file_uri, folder_uri), предоставленными environmentService.

windowsMainService — это служба, созданная WindowsManager, а WindowsManager — это многофункциональный класс управления (src/vs/code/electron-main/windows.ts). Далее мы смотрим на метод windowsMainService.open и видим, что он вызывает метод doOpen.

open(openConfig: IOpenConfiguration): ICodeWindow[] {
		...

		// Open based on config
		const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, 
		...
	}

Наконец, метод doOpen вызывает метод openInBrowserWindow.

private doOpen(
		openConfig: IOpenConfiguration,
		workspacesToOpen: IWorkspacePathToOpen[],
		foldersToOpen: IFolderPathToOpen[],
		emptyToRestore: IEmptyWindowBackupInfo[],
		emptyToOpen: number,
		fileInputs: IFileInputs | undefined,
		foldersToAdd: IFolderPathToOpen[]
	) {
		const usedWindows: ICodeWindow[] = [];

		...

		// Handle empty to open (only if no other window opened)
		if (usedWindows.length === 0 || fileInputs) {
			if (fileInputs && !emptyToOpen) {
				emptyToOpen++;
			}

			const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);

			for (let i = 0; i < emptyToOpen; i++) {
				usedWindows.push(this.openInBrowserWindow({
					userEnv: openConfig.userEnv,
					cli: openConfig.cli,
					initialStartup: openConfig.initialStartup,
					remoteAuthority,
					forceNewWindow: openFolderInNewWindow,
					forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
					fileInputs
				}));

				// Reset these because we handled them
				fileInputs = undefined;
				openFolderInNewWindow = true; // any other window to open must open in new window then
			}
		}

		return arrays.distinct(usedWindows);
	}

В openInBrowserWindow создается и возвращается экземпляр CodeWindow, а также вызывается метод doOpenInBrowserWindow, который описан ниже.

private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {

		...

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

		// 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(veto => {
				if (!veto) {
					this.doOpenInBrowserWindow(window!, configuration, options);
				}
			});
		} else {
			this.doOpenInBrowserWindow(window, configuration, options);
		}

		return window;
	}

Затем мы обнаруживаем, что CodeWindow определен в src/vs/code/electron-main/window.ts, вызываем метод createBrowserWindow в конструкторе CodeWindow, а затем видим экземпляр в методе createBrowserWindow.BrowserWindow, который является определением окна браузера в Electron.

//src/vs/code/electron-main/window.ts
export class CodeWindow extends Disposable implements ICodeWindow {

	...

	constructor(
		config: IWindowCreationOptions,
		@ILogService private readonly logService: ILogService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@IFileService private readonly fileService: IFileService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IThemeMainService private readonly themeMainService: IThemeMainService,
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
	) {
		super();

		this.touchBarGroups = [];
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
		this.whenReadyCallbacks = [];

		// create browser window
		this.createBrowserWindow(config);

		// respect configured menu bar visibility
		this.onConfigurationUpdated();

		// macOS: touch bar support
		this.createTouchBar();

		// Request handling
		this.handleMarketplaceRequests();

		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

		...
		// Create the browser window.
		this._win = new BrowserWindow(options);
		...
	}
}

Теперь, когда окно есть, когда загружается страница? Мы только что упомянули выше, что в openInBrowserWindow создайте экземпляр CodeWindow и верните его, а также вызовите метод doOpenInBrowserWindow, тогда давайте взглянем на определение этого метода.

private doOpenInBrowserWindow(window: ICodeWindow, configuration: IWindowConfiguration, options: IOpenBrowserWindowOptions): void {
		...
		// Load it
		window.load(configuration);
		...
	}

Этот метод должен вызвать метод загрузки CodeWindow, а затем просмотреть определение метода загрузки. Вы увидите, что вызывается this._win.loadURL, this._win — это окно BrowserWindow, созданное CodeWindow, которое находит время загрузки окна URL.

load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
	...
	// Load URL
	perf.mark('main:loadWindow');
	this._win.loadURL(this.getUrl(configuration));
	...
}

Затем посмотрите на определение метода getUrl.Окончательный возвращаемый configUrl получается при вызове doGetUrl.

private getUrl(windowConfiguration: IWindowConfiguration): string {

		...
		let configUrl = this.doGetUrl(config);
		...

		return configUrl;
	}

Затем посмотрите на метод doGetUrl, вы увидите, что возвращаемый URL-адрес — это 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))}`;
}

Это вход всего Workbench, появляется HTML, выполняется миссия основного процесса, появляется процесс рендеринга.

//src/vs/code/electron-browser/workbench/workbench.html
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' https://*.vscode-webview-test.com; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
	</head>
	<body class="vs-dark" aria-label="">
	</body>

	<!-- Startup via workbench.js -->
	<script src="workbench.js"></script>
</html>

Файл workbench.js загружается в workbench.html, который отвечает за загрузку реального модуля Workbench и вызов его основного метода для инициализации основного интерфейса.

// src/vs/code/electron-browser/workbench/workbench.js
const bootstrapWindow = require('../../../../bootstrap-window');

// Setup shell environment
process['lazyEnv'] = getLazyEnv();

// Load workbench main JS, CSS and NLS all in parallel. This is an
// optimization to prevent a waterfall of loading to happen, because
// we know for a fact that workbench.desktop.main will depend on
// the related CSS and NLS counterparts.
bootstrapWindow.load([
	'vs/workbench/workbench.desktop.main',
	'vs/nls!vs/workbench/workbench.desktop.main',
	'vs/css!vs/workbench/workbench.desktop.main'
],
	function (workbench, configuration) {
		perf.mark('didLoadWorkbenchMain');

		return process['lazyEnv'].then(function () {
			perf.mark('main/startup');

			// @ts-ignore
			//加载 Workbench 并初始化主界面
			return require('vs/workbench/electron-browser/desktop.main').main(configuration);
		});
	}, {
		removeDeveloperKeybindingsAfterLoad: true,
		canModifyDOM: function (windowConfig) {
			showPartsSplash(windowConfig);
		},
		beforeLoaderConfig: function (windowConfig, loaderConfig) {
			loaderConfig.recordStats = true;
		},
		beforeRequire: function () {
			perf.mark('willLoadWorkbenchMain');
		}
	});

Мы видим, что модуль vs/workbench/electron-browser/desktop.main загружен и вызывается основной метод модуля. DesktopMain создается в основном методе, и вызывается открытый метод DesktopMain.

class DesktopMain extends Disposable {

	async open(): Promise<void> {
		...

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

		// Listeners
		this.registerListeners(workbench, services.storageService);

		// Startup
		const instantiationService = workbench.startup();
		...
	}
	...
}

export function main(configuration: IWindowConfiguration): Promise<void> {
	const renderer = new DesktopMain(configuration);
	return renderer.open();
}

Мы видим, что класс Workbench создается в методе open DesktopMain и вызывается метод запуска Workbench. Далее мы рассмотрим класс Workbench.

export class Workbench extends Layout {

	...
	startup(): IInstantiationService {
		try {

			// Configure emitter leak warning threshold
			setGlobalLeakWarningThreshold(175);

			// ARIA
			setARIAContainer(document.body);

			// 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));

				// Register Listeners
				this.registerListeners(lifecycleService, storageService, configurationService);

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

				// Workbench Layout
				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
		}
	}
	...
}

Мы видим, что Workbench наследует класс макета Layout, строит макет основного интерфейса, создает глобальные прослушиватели событий и инстанцирует некоторые зависимые сервисы в методе workbench.startup, ведь ранее открытый редактор будет восстановлен, а весь Workbench будет быть загружен.

Поэтому большой объем кода в предыдущей статье только для того, чтобы подготовить здесь почву для окончательного создания основного интерфейса.Основной код модуля Workbench находится в каталоге vs/workbench, который в основном отвечает за создание элементы интерфейса и реализация конкретных бизнес-функций.

На данный момент, от запуска до загрузки в html, до построения макета основного интерфейса, весь процесс предельно ясен.

Настройка информации о приложении

В корневом каталоге исходного кода VSCode есть файл product.json, который используется для настройки информации о приложении.

{
	"nameShort": "Code - OSS",
	"nameLong": "Code - OSS",
	"applicationName": "code-oss",
	"dataFolderName": ".vscode-oss",
	"win32MutexName": "vscodeoss",
	"licenseName": "MIT",
	"licenseUrl": "https://github.com/Microsoft/vscode/blob/master/LICENSE.txt",
	"win32DirName": "Microsoft Code OSS",
	"win32NameVersion": "Microsoft Code OSS",
	"win32RegValueName": "CodeOSS",
	"win32AppId": "{{E34003BB-9E10-4501-8C11-BE3FAA83F23F}",
	"win32x64AppId": "{{D77B7E06-80BA-4137-BCF4-654B95CCEBC5}",
	"win32UserAppId": "{{C6065F05-9603-4FC4-8101-B9781A25D88E}",
	"win32x64UserAppId": "{{C6065F05-9603-4FC4-8101-B9781A25D88E}",
	"win32AppUserModelId": "Microsoft.CodeOSS",
	"win32ShellNameShort": "C&ode - OSS",
	"darwinBundleIdentifier": "com.visualstudio.code.oss",
	"linuxIconName": "com.visualstudio.code.oss",
	"licenseFileName": "LICENSE.txt",
	"reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new",
	"urlProtocol": "code-oss",
	"extensionAllowedProposedApi": [
		"ms-vscode.references-view"
	]
}

Вы можете изменить информацию в product.json, чтобы обновить такую ​​информацию, как имя пользовательского VSCode. Если вы измените информацию product.json после выполнения ./scripts/code.sh, например, измените конфигурацию nameLong, то повторный запуск ./scripts/code.sh сообщит об ошибке.

Сообщение об ошибке: ./scripts/code.sh: строка 53: /Users/jiangshuaijie/Desktop/vscode-1.39.2/.build/electron/test.app/Contents/MacOS/Electron: нет такого файла или каталога, вы можно увидеть. Об ошибке сообщается в code.sh, посмотрите содержимое в code.sh.

...
function code() {
	cd "$ROOT"
	if [[ "$OSTYPE" == "darwin"* ]]; then
		NAME=`node -p "require('./product.json').nameLong"`
		CODE="./.build/electron/$NAME.app/Contents/MacOS/Electron"
	else
		NAME=`node -p "require('./product.json').applicationName"`
		CODE=".build/electron/$NAME"
	fi
	...
	# Launch Code
	exec "$CODE" . "$@"
}
...

Наконец, запустите приложение, сгенерированное в .build/electron/, в корневом каталоге в соответствии с именемLong в product.json, Приложение в это время было сгенерировано ранее, поэтому будет сообщено об ошибке. Нам просто нужно удалить папку .build в корневом каталоге и повторно выполнить ./scripts/code.sh.

Настройка значка приложения

Папка ресурсов в корневом каталоге исходного кода VSCode в основном используется для хранения статических ресурсов платформы VSCode, таких как значки приложений.

Среди них darwin, linux и win32 соответствуют трем разным платформам, а ресурсы изображений можно заменять в папках разных платформ.

Больше захватывающих, пожалуйста, следитетинейджер co.GitHub.IO/vs code-anal…, больше контента будет обновлено позже.