set Current Working Directory
前面我们学习到,设置Crash目录,接着我们继续,开始设置:工作目录,即process.cwd
// src/utils/workingDirectory.ts
import { app } from 'electron';
import path from 'path';
import { PROCESS_ENV } from './constant';
const setCurrentWorkingDirectory = () => {
try {
if (process.platform === 'win32') {
process.env[PROCESS_ENV.MY_VSCODE_CWD] = process.cwd(); // remember as environment variable
process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd
} else if (process.env[PROCESS_ENV.MY_VSCODE_CWD]) {
process.chdir(process.env[PROCESS_ENV.MY_VSCODE_CWD] as string);
}
} catch (err) {
console.error(err);
}
}
export {
setCurrentWorkingDirectory
}
// src/main.ts
...
setCurrentWorkingDirectory();
Protocol
接下来,vscode 设置了一些协议,我们去Electron官网看看,这个是什么东西!
protocol.registerSchemesAsPrivileged([
{
scheme: 'vscode-webview',
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
corsEnabled: true,
}
}, {
scheme: 'vscode-webview-resource',
privileges: {
secure: true,
standard: true,
supportFetchAPI: true,
corsEnabled: true,
}
},
]);
这里有点不理解其作用,先不加,后续有需求在增加:TODO
全局监听
到现在,需要注册一些应用上的监听,这些监听好像与拖动文件到应用并打开有关,在这之前,监听文件拖动并存储,后续应用ready后进行打开,是一个小的优化点。
// src/main.ts
...
registerListeners();
// src/utils/globalListeners.ts
import { app } from 'electron';
import { GLOBAL_NAMES } from './constant';
const registerListeners = () => {
/**
* macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
* the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
*
* @type {string[]}
*/
const macOpenFiles: string[] = [];
global['macOpenFiles'] = macOpenFiles;
app.on('open-file', function (event: any, path: string) {
macOpenFiles.push(path);
});
/**
* macOS: react to open-url requests.
*
* @type {string[]}
*/
const openUrls: string[] = [];
const onOpenUrl = function (event, url: string) {
event.preventDefault();
openUrls.push(url);
};
app.on('will-finish-launching', function () {
app.on('open-url', onOpenUrl);
});
global[GLOBAL_NAMES.getOpenUrls] = function () {
app.removeListener('open-url', onOpenUrl);
return openUrls;
};
}
export {
registerListeners
}
nodeCachedDataDir
这个东东比较复杂,暂时没理解其作用,记个 TODO
locale
/**
* Support user defined locale: load it early before app('ready')
* to have more things running in parallel.
*
* @type {Promise<import('./vs/base/node/languagePacks').NLSConfiguration> | undefined}
*/
let nlsConfigurationPromise = undefined;
const metaDataFile = path.join(__dirname, 'nls.metadata.json');
const locale = getUserDefinedLocale(argvConfig);
if (locale) {
nlsConfigurationPromise = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale);
}
暂时不增加,记个 TODO
contentTracing
终于到 Ready 环节了, 这里首先使用了 Electron的contentTracing,我们来了解下是什么,有啥用,以及怎么用。
好像是个内容追踪的模块,我也不懂,看是 dev下调试使用的,那就先记个 TODO,后续搞懂。
startup
最终,我们跟踪到这里,要进行Electron启动了:
require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
perf.mark('didLoadMainBundle');
});
这是vscode使用自己的loader进行的加载,我们不懂作用是什么,记个 TODO 后续搞懂。
我们进入vs/code/electron-main/main.ts 文件,终于可以开始Electron启动了。
// vs/code/electron-main/main.ts
// Main Startup
const code = new CodeMain();
code.main();
首先,我们来解读main 函数,它有一个异常处理的统一的工具类, 并提供注册统一错误处理方法,这个机制,我们可以先统一集成,后续感受其带来的好处。
setUnexpectedErrorHandler(err => console.error(err));
先将工具类复制到我们的项目中src/vs/base/common/errors.ts。
我们新建一个src/main-process.ts。
// src/main-process.ts
import { setUnexpectedErrorHandler } from "./base/common/errors";
class CodeMain {
main(): void {
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
}
}
export const startCodeMain = () => {
const codeMain = new CodeMain();
codeMain.main();
}
// src/main.ts
import { startCodeMain } from './main-process';
import { configureCommandlineSwitchesSync } from './utils/commandLineSwitches';
import { startCrash } from './utils/crash';
import { registerListeners } from './utils/globalListeners';
import { parseCLIArgs } from './utils/utils';
import { setCurrentWorkingDirectory } from './utils/workingDirectory';
const args = parseCLIArgs();
// argv 参数配置
const argvConfig = configureCommandlineSwitchesSync(args);
// 设置 crasher
startCrash(args, argvConfig);
// 设置当前工作目录
setCurrentWorkingDirectory();
// 注册全局监听
registerListeners();
// 启动主进程
startCodeMain();
Parse arguments-01
在运行之前,统一处理一下启动时,带来的一些运行参数,这里我们需要一些辅助的工具类,就不再去解释了,主要是做一些参数的接收处理,便于我们使用,这里我就统一的直接拷贝过来。
src/platform/environment/common/argv.tssrc/platform/environment/node/argvHelper.ts
在拷贝argvHelper的时候,遇到了一些需要集成的东西:
-
localize这个是处理语言的多种翻译。具体的细节后续详解,先集成进来,记个TODO -
platform工具类,用于判断操作系统相关的东西,集成进来,记个 TODO -
fies工具类,处理文件相关内容, 但此事一个大模块,此处先不详解也不集成,记个 TODO- 依赖的
MIN_MAX_MEMORY_SIZE_MB常量先定义到constants.ts中。
- 依赖的
我们在做参数准备工作的时候,却遇到了validatePaths 而这个看起来会是一个比较复杂的模块,咱们一点点来理解。
parse arguments 的事情暂时搁置,后续启动。
Paths
首先,我们在处理环境变量的时候,需要去根据变量的值去验证一些路径的合法性:
args = this.validatePaths(args);
private validatePaths(args: NativeParsedArgs): NativeParsedArgs {
// Track URLs if they're going to be used
// TODO:这个逻辑不理解先放着
if (args['open-url']) {
args._urls = args._;
args._ = [];
}
// Normalize paths and watch out for goto line mode
// 这个是因为操作系统差异性,因此会需要去 normalize 路径,从而确保参数上路径的正确性。
if (!args['remote']) {
// TODO goto 是个 boolean 值,啥意思?
const paths = this.doValidatePaths(args._, args.goto);
args._ = paths;
}
return args;
}
再来看doValidatePaths
export interface IPathWithLineAndColumn {
path: string;
line?: number;
column?: number;
}
private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
// 获取到所有参数
const cwd = process.env['VSCODE_CWD'] || process.cwd();
const result = args.map(arg => {
// 候选路径
let pathCandidate = String(arg);
// 路径包含行号和列号
// 是这个意思: C:\file.txt:<line>:<column> 某个文件的某行某列
let parsedPath: IPathWithLineAndColumn | undefined = undefined;
if (gotoLineMode) {
parsedPath = parseLineAndColumnAware(pathCandidate);
pathCandidate = parsedPath.path;
}
if (pathCandidate) {
pathCandidate = this.preparePath(cwd, pathCandidate);
}
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
const filePathBasename = basename(sanitizedFilePath);
if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) {
return null; // do not allow invalid file names
}
if (gotoLineMode && parsedPath) {
parsedPath.path = sanitizedFilePath;
return this.toPath(parsedPath);
}
return sanitizedFilePath;
});
const caseInsensitive = isWindows || isMacintosh;
const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || ''));
return coalesce(distinctPaths);
}
解读到这个函数parseLineAndColumnAware:我们来写个例子跑下,转换成了啥。
定义parseLineAndColumnAware
function parseLineAndColumnAware(rawPath) {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path = undefined;
let line = undefined;
let column = undefined;
segments.forEach(segment => {
const segmentAsNumber = Number(segment);
if (!isNumber(segmentAsNumber)) {
// 处理路径中带“:”的情况,防止错误识别。比如: "c:\",从而正确的去读取行号。
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
} else if (line === undefined) {
line = segmentAsNumber;
} else if (column === undefined) {
column = segmentAsNumber;
}
});
if (!path) {
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
}
return {
path,
line: line !== undefined ? line : undefined,
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
};
}
定义isNumber
function isNumber(obj) {
return (typeof obj === 'number' && !isNaN(obj));
}
运行:
parseLineAndColumnAware('C:\file.txt:3:3');
result:
{
column: 3
line: 3
path: "C:\file.txt"
}
所以大概理解其作用了。
接下来是preparePath 就是对字符串做一些处理。这里涉及到一个字符串处理工具类,直接复制过来好了。
string.ts字符串处理工具类。charCode.ts又被string.ts依赖,处理一些字符相关的。
后续有工具类的时候,不再解释,直接复制即可。
相关的工具类都复制完毕,这里主要是对参数中的路径做了一些处理,不再详细阐述了,可自行去看。
我们先把所有的路径都处理好。
这里,我们将主进程中,获取运行时的参数,并对其做统一的处理比如:路径处理等,封装成argsPathsUtil.ts
// src/utils/argsPathsUtil.ts
import { coalesce, distinct } from "../base/common/arrays";
import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from "../base/common/extpath";
import { basename, resolve } from "../base/common/path";
import { isMacintosh, isWindows } from "../base/common/platform";
import { rtrim, trim } from "../base/common/strings";
import { isNumber } from "../base/common/types";
import { NativeParsedArgs } from "../platform/environment/common/argv";
import { PROCESS_ENV } from "./constant";
const preparePath = (cwd: string, path: string): string => {
// Trim trailing quotes
if (isWindows) {
path = rtrim(path, '"'); // https://github.com/microsoft/vscode/issues/1498
}
// Trim whitespaces
path = trim(trim(path, ' '), '\t');
if (isWindows) {
// Resolve the path against cwd if it is relative
path = resolve(cwd, path);
// Trim trailing '.' chars on Windows to prevent invalid file names
path = rtrim(path, '.');
}
return path;
}
}
const toPath = (pathWithLineAndCol: IPathWithLineAndColumn): string => {
const segments = [pathWithLineAndCol.path];
if (isNumber(pathWithLineAndCol.line)) {
segments.push(String(pathWithLineAndCol.line));
}
if (isNumber(pathWithLineAndCol.column)) {
segments.push(String(pathWithLineAndCol.column));
}
return segments.join(':');
}
const doValidatePaths = (args: string[], gotoLineMode?: boolean): string[] => {
const cwd = process.env[PROCESS_ENV.MY_VSCODE_CWD] || process.cwd();
const result = args.map(arg => {
let pathCandidate = String(arg);
let parsedPath: IPathWithLineAndColumn | undefined = undefined;
if (gotoLineMode) {
parsedPath = parseLineAndColumnAware(pathCandidate);
pathCandidate = parsedPath.path;
}
if (pathCandidate) {
pathCandidate = preparePath(cwd, pathCandidate);
}
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
const filePathBasename = basename(sanitizedFilePath);
if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) {
return null; // do not allow invalid file names
}
if (gotoLineMode && parsedPath) {
parsedPath.path = sanitizedFilePath;
return toPath(parsedPath);
}
return sanitizedFilePath;
});
const caseInsensitive = isWindows || isMacintosh;
const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || ''));
return coalesce(distinctPaths);
}
export const validatePaths = (args: NativeParsedArgs): NativeParsedArgs => {
// Track URLs if they're going to be used
if (args['open-url']) {
args._urls = args._;
args._ = [];
}
// Normalize paths and watch out for goto line mode
if (!args['remote']) {
const paths = doValidatePaths(args._, args.goto);
args._ = paths;
}
return args;
}
最后回到main.ts函数:
class CodeMain {
main(): void {
// Set the error handler early enough so that we are not getting the
// default electron error dialog popping up
setUnexpectedErrorHandler(err => console.error(err));
// 处理主进程运行时参数 包含路径处理
const args: NativeParsedArgs = parseMainAgrs();
}
}
这样处理后,就清爽很多了,确保做到:每段代码只展示当时读者需要看的内容,不展示细节!
waitMarkerFile
// If we are started with --wait create a random temporary file
// and pass it over to the starting instance. We can use this file
// to wait for it to be deleted to monitor that the edited file
// is closed and then exit the waiting process.
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
if (args.wait && !args.waitMarkerFilePath) {
const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
}
}
这里在说等待waitMarkerFilePath ,先记个TODO 不影响主流程。
当下的解读,很多比较细节的地方会记下TODO, 后续回顾的时候一一了解。
StartUp
接着,我们进入最重要的环节: start up 。需要看懂这里,就需要明白 IOC 机制,这也是我们的重点。