|
|
|
@ -239,268 +239,514 @@ async function findConfigs(uri: vscode.Uri, quiet: boolean): Promise<FileSystemC
|
|
|
|
|
* @returns 一个包含解析后的文件系统配置数组和一个布尔值的数组,表示位置是否是绝对路径。
|
|
|
|
|
*/
|
|
|
|
|
async function findConfigFiles(location: string | vscode.Uri, quiet = false): Promise<[configs: FileSystemConfig[] | undefined, isAbsolute: boolean]> {
|
|
|
|
|
// 如果 location 是 vscode.Uri 类型,则直接使用该 URI 查找配置文件,并返回结果和 true 表示 location 是绝对路径
|
|
|
|
|
if (location instanceof vscode.Uri) {
|
|
|
|
|
return [await findConfigs(location, quiet), true];
|
|
|
|
|
} else if (location.match(/^([a-zA-Z0-9+.-]+):/)) {
|
|
|
|
|
}
|
|
|
|
|
// 如果 location 匹配正则表达式 /^([a-zA-Z0-9+.-]+):/,则将其解析为 URI 并查找配置文件,返回结果和 true 表示 location 是绝对路径
|
|
|
|
|
else if (location.match(/^([a-zA-Z0-9+.-]+):/)) {
|
|
|
|
|
return [await findConfigs(vscode.Uri.parse(location), quiet), true];
|
|
|
|
|
} else if (path.isAbsolute(location)) {
|
|
|
|
|
}
|
|
|
|
|
// 如果 location 是绝对路径,则将其转换为文件 URI 并查找配置文件,返回结果和 true 表示 location 是绝对路径
|
|
|
|
|
else if (path.isAbsolute(location)) {
|
|
|
|
|
return [await findConfigs(vscode.Uri.file(location), quiet), true];
|
|
|
|
|
}
|
|
|
|
|
// 如果以上条件都不满足,则返回空数组和 false 表示 location 不是绝对路径
|
|
|
|
|
return [[], false];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 尝试从指定的位置查找配置文件。
|
|
|
|
|
* 如果找到了配置文件,则返回这些文件的解析结果;如果没有找到,则记录一条错误或信息日志,并返回一个空数组。
|
|
|
|
|
* @param location - 要查找的配置文件的位置,可以是字符串或 vscode.Uri。
|
|
|
|
|
* @param source - 提供配置文件位置的源,通常是调用此函数的模块或函数的名称。
|
|
|
|
|
* @returns 一个包含解析后的文件系统配置数组的 Promise,如果没有找到配置文件,则返回一个空数组。
|
|
|
|
|
*/
|
|
|
|
|
async function tryFindConfigFiles(location: string | vscode.Uri, source: string): Promise<FileSystemConfig[]> {
|
|
|
|
|
// 尝试从指定的位置查找配置文件。
|
|
|
|
|
const [found, isAbsolute] = await findConfigFiles(location, true);
|
|
|
|
|
// 如果找到了配置文件,则返回这些文件的解析结果。
|
|
|
|
|
if (found) return found;
|
|
|
|
|
// 如果没有找到配置文件,则记录一条错误或信息日志,并返回一个空数组。
|
|
|
|
|
logging[isAbsolute ? 'error' : 'info']`No configs found in '${location}' provided by ${source}`;
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取特定作用域下的配置路径。
|
|
|
|
|
* @param scope - 可选的工作区文件夹对象,用于指定特定的工作区文件夹。如果未提供,则默认为全局作用域。
|
|
|
|
|
* @returns 一个对象,包含全局、工作区和文件夹级别的配置路径数组。
|
|
|
|
|
*/
|
|
|
|
|
function getConfigPaths(scope?: vscode.WorkspaceFolder): Record<'global' | 'workspace' | 'folder', string[]> {
|
|
|
|
|
// 从 VSCode 工作区获取名为 'sshfs' 的配置对象,如果指定了 scope,则仅获取该 scope 下的配置
|
|
|
|
|
const config = vscode.workspace.getConfiguration('sshfs', scope);
|
|
|
|
|
// 检查 'sshfs' 配置对象中的 'configpaths' 属性,获取其全局、工作区和文件夹级别的值
|
|
|
|
|
const inspect = config.inspect<string[]>('configpaths')!;
|
|
|
|
|
// 返回一个包含全局、工作区和文件夹级别的配置路径数组的对象
|
|
|
|
|
return {
|
|
|
|
|
// 如果 inspect.globalValue 存在,则返回其值,否则返回空数组
|
|
|
|
|
global: inspect.globalValue || [],
|
|
|
|
|
// 如果 inspect.workspaceValue 存在,则返回其值,否则返回空数组
|
|
|
|
|
workspace: inspect.workspaceValue || [],
|
|
|
|
|
// 如果 inspect.workspaceFolderValue 存在,则返回其值,否则返回空数组
|
|
|
|
|
folder: inspect.workspaceFolderValue || [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义一个名为 configLayers 的对象,用于存储不同层级的配置文件
|
|
|
|
|
let configLayers: {
|
|
|
|
|
// 存储全局配置文件的数组
|
|
|
|
|
global: FileSystemConfig[];
|
|
|
|
|
// 存储工作区配置文件的数组
|
|
|
|
|
workspace: FileSystemConfig[];
|
|
|
|
|
// 存储文件夹级别的配置文件的 Map,其中键为文件夹的 URI,值为配置文件数组
|
|
|
|
|
folder: Map<string, FileSystemConfig[]>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** Only loads `sshfs.configs` into `configLayers`, ignoring `sshfs.configpaths` */
|
|
|
|
|
/**
|
|
|
|
|
* 加载全局和工作区的配置文件,并将它们存储在 configLayers 对象中。
|
|
|
|
|
* 这个函数会从 VSCode 的工作区配置中获取 'sshfs.configs' 的值,并将其解析为 FileSystemConfig 数组。
|
|
|
|
|
* 然后,它会将这些配置文件分别存储在 configLayers.global 和 configLayers.workspace 中。
|
|
|
|
|
* 每个配置文件都会被标记为其来源(全局或工作区),并存储在 _locations 和 _location 属性中。
|
|
|
|
|
* @returns 一个 Promise,当所有配置文件都被加载并存储后,它会被解决。
|
|
|
|
|
*/
|
|
|
|
|
async function loadGlobalOrWorkspaceConfigs(): Promise<void> {
|
|
|
|
|
// 从 VSCode 工作区获取名为 'sshfs' 的配置对象
|
|
|
|
|
const config = vscode.workspace.getConfiguration('sshfs');
|
|
|
|
|
// 检查 'sshfs' 配置对象中的 'configs' 属性,获取其全局和工作区的值
|
|
|
|
|
const inspect = config.inspect<FileSystemConfig[]>('configs')!;
|
|
|
|
|
// 将全局配置文件存储在 configLayers.global 中,如果没有全局配置,则存储一个空数组
|
|
|
|
|
configLayers.global = inspect.globalValue || [];
|
|
|
|
|
// 将工作区配置文件存储在 configLayers.workspace 中,如果没有工作区配置,则存储一个空数组
|
|
|
|
|
configLayers.workspace = inspect.workspaceValue || [];
|
|
|
|
|
// 遍历全局配置文件,将每个配置文件的 _locations 属性设置为包含其来源(全局)的数组,并将 _location 属性设置为其来源
|
|
|
|
|
configLayers.global.forEach(c => c._locations = [c._location = vscode.ConfigurationTarget.Global]);
|
|
|
|
|
// 遍历工作区配置文件,将每个配置文件的 _locations 属性设置为包含其来源(工作区)的数组,并将 _location 属性设置为其来源
|
|
|
|
|
configLayers.workspace.forEach(c => c._locations = [c._location = vscode.ConfigurationTarget.Workspace]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Loads `sshfs.configs` and (including global/workspace-provided) relative `sshfs.configpaths` into `configLayers` */
|
|
|
|
|
/**
|
|
|
|
|
* 加载特定工作区文件夹中的配置文件,并将它们存储在 configLayers 对象中。
|
|
|
|
|
* 这个函数会从 VSCode 的工作区配置中获取 'sshfs.configs' 的值,并将其解析为 FileSystemConfig 数组。
|
|
|
|
|
* 然后,它会将这些配置文件存储在 configLayers.folder 中,键为工作区文件夹的 URI。
|
|
|
|
|
* 此外,它还会检查 'sshfs.configpaths' 配置,以查找其他可能的配置文件路径,并尝试加载这些文件。
|
|
|
|
|
* 如果工作区文件夹的 URI 是断开连接的,函数将跳过加载,并记录一条信息日志。
|
|
|
|
|
* @param folder - 要加载配置文件的工作区文件夹对象。
|
|
|
|
|
* @returns 一个包含解析后的文件系统配置数组的 Promise,如果没有找到配置文件,则返回一个空数组。
|
|
|
|
|
*/
|
|
|
|
|
async function loadWorkspaceFolderConfigs(folder: vscode.WorkspaceFolder): Promise<FileSystemConfig[]> {
|
|
|
|
|
// 如果工作区文件夹的 URI 是断开连接的,则跳过加载,并记录一条信息日志
|
|
|
|
|
if (skipDisconnectedUri(folder.uri)) {
|
|
|
|
|
// 将空数组设置为工作区文件夹的配置文件
|
|
|
|
|
configLayers.folder.set(folder.uri.toString(), []);
|
|
|
|
|
// 返回空数组
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
// 从 VSCode 工作区获取名为 'sshfs' 的配置对象,并检查 'configs' 属性
|
|
|
|
|
const config = vscode.workspace.getConfiguration('sshfs', folder).inspect<FileSystemConfig[]>('configs');
|
|
|
|
|
// 获取工作区文件夹级别的配置文件,如果没有,则返回空数组
|
|
|
|
|
const configs = config && config.workspaceFolderValue || [];
|
|
|
|
|
// 如果找到了配置文件,则记录一条调试日志
|
|
|
|
|
if (configs.length) {
|
|
|
|
|
logging.debug`Read ${configs.length} configs from workspace folder ${folder.uri}`;
|
|
|
|
|
// 为每个配置文件设置 _locations 和 _location 属性,以标识其来源
|
|
|
|
|
configs.forEach(c => c._locations = [c._location = `WorkspaceFolder ${folder.uri}`]);
|
|
|
|
|
}
|
|
|
|
|
// 获取特定工作区文件夹的配置路径
|
|
|
|
|
const configPaths = getConfigPaths(folder);
|
|
|
|
|
// 遍历所有配置路径,包括全局、工作区和文件夹级别的路径
|
|
|
|
|
for (const location of [...configPaths.global, ...configPaths.workspace, ...configPaths.folder]) {
|
|
|
|
|
// 如果路径是绝对路径,则跳过
|
|
|
|
|
if (path.isAbsolute(location)) continue;
|
|
|
|
|
// 将工作区文件夹的 URI 与相对路径拼接,得到绝对路径
|
|
|
|
|
const uri = vscode.Uri.joinPath(folder.uri, location);
|
|
|
|
|
// 尝试从绝对路径查找配置文件,并记录其来源
|
|
|
|
|
const found = await tryFindConfigFiles(uri, `WorkspaceFolder '${folder.uri}'`);
|
|
|
|
|
// 如果找到了配置文件,则将其添加到配置文件数组中
|
|
|
|
|
if (found) configs.push(...found);
|
|
|
|
|
}
|
|
|
|
|
// 将最终的配置文件数组存储在 configLayers.folder 中,键为工作区文件夹的 URI
|
|
|
|
|
configLayers.folder.set(folder.uri.toString(), configs);
|
|
|
|
|
// 返回配置文件数组
|
|
|
|
|
return configs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用配置层,合并和处理多个配置层,最终生成一个有效的配置列表。
|
|
|
|
|
* 这个函数会将所有的配置层合并到一个数组中,然后进行去重、合并和扩展操作,最终生成一个有效的配置列表。
|
|
|
|
|
* 如果配置名称无效或存在循环引用,函数会记录错误并跳过相应的配置。
|
|
|
|
|
* @returns 一个包含所有有效配置的数组。
|
|
|
|
|
*/
|
|
|
|
|
function applyConfigLayers(): void {
|
|
|
|
|
// Merge all layers into a single array of configs, in order of importance
|
|
|
|
|
// 将所有的配置文件合并到一个数组中,按照重要性排序
|
|
|
|
|
const all: FileSystemConfig[] = [
|
|
|
|
|
// 遍历所有的工作区文件夹,获取每个文件夹的配置文件
|
|
|
|
|
...(vscode.workspace.workspaceFolders || []).flatMap(ws => configLayers.folder.get(ws.uri.toString()) || []),
|
|
|
|
|
// 获取工作区的配置文件
|
|
|
|
|
...configLayers.workspace,
|
|
|
|
|
// 获取全局的配置文件
|
|
|
|
|
...configLayers.global,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 确保每个配置对象的名称属性都被转换为小写形式,以避免大小写敏感问题
|
|
|
|
|
all.forEach(c => c.name = (c.name || '').toLowerCase()); // It being undefined shouldn't happen, but better be safe
|
|
|
|
|
// Let the user do some cleaning with the raw configs
|
|
|
|
|
// 遍历所有的配置文件
|
|
|
|
|
for (const conf of all) {
|
|
|
|
|
// 如果配置文件没有名称
|
|
|
|
|
if (!conf.name) {
|
|
|
|
|
// 记录错误信息
|
|
|
|
|
logging.error`Skipped an invalid SSH FS config (missing a name field):\n${conf}`;
|
|
|
|
|
// 显示错误信息给用户
|
|
|
|
|
vscode.window.showErrorMessage(`Skipped an invalid SSH FS config (missing a name field)`);
|
|
|
|
|
// 如果配置文件的名称无效
|
|
|
|
|
} else if (invalidConfigName(conf.name)) {
|
|
|
|
|
// 记录警告信息
|
|
|
|
|
logging.warning(`Found a SSH FS config with the invalid name "${conf.name}", prompting user how to handle`);
|
|
|
|
|
// 显示警告信息给用户,并提供处理选项
|
|
|
|
|
vscode.window.showErrorMessage(`Invalid SSH FS config name: ${conf.name}`, 'Rename', 'Delete', 'Skip').then(async (answer) => {
|
|
|
|
|
// 如果用户选择重命名
|
|
|
|
|
if (answer === 'Rename') {
|
|
|
|
|
// 提示用户输入新的名称
|
|
|
|
|
const name = await vscode.window.showInputBox({ prompt: `New name for: ${conf.name}`, validateInput: invalidConfigName, placeHolder: 'New name' });
|
|
|
|
|
// 如果用户输入了新的名称
|
|
|
|
|
if (name) {
|
|
|
|
|
// 记录重命名的信息
|
|
|
|
|
const oldName = conf.name;
|
|
|
|
|
logging.info`Renaming config "${oldName}" to "${name}"`;
|
|
|
|
|
// 更新配置文件的名称
|
|
|
|
|
conf.name = name;
|
|
|
|
|
// 更新配置文件
|
|
|
|
|
return updateConfig(conf, oldName);
|
|
|
|
|
}
|
|
|
|
|
// 如果用户选择删除
|
|
|
|
|
} else if (answer === 'Delete') {
|
|
|
|
|
// 删除配置文件
|
|
|
|
|
return deleteConfig(conf);
|
|
|
|
|
}
|
|
|
|
|
// 如果用户选择跳过
|
|
|
|
|
logging.warning`Skipped SSH FS config '${conf.name}'`;
|
|
|
|
|
// 显示跳过的信息给用户
|
|
|
|
|
vscode.window.showWarningMessage(`Skipped SSH FS config '${conf.name}'`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Remove duplicates, merging those where the more specific config has `merge` set (in the order from above)
|
|
|
|
|
// 初始化一个空数组,用于存储最终的配置文件
|
|
|
|
|
loadedConfigs = [];
|
|
|
|
|
for (const conf of all.filter(c => !invalidConfigName(c.name))) {
|
|
|
|
|
// 遍历所有过滤后的配置文件,确保它们的名称是有效的
|
|
|
|
|
for (const conf of all.filter(c =>!invalidConfigName(c.name))) {
|
|
|
|
|
// 在 loadedConfigs 数组中查找是否已经存在具有相同名称的配置文件
|
|
|
|
|
const dup = loadedConfigs.find(d => d.name === conf.name);
|
|
|
|
|
// 如果找到了重复的配置文件
|
|
|
|
|
if (dup) {
|
|
|
|
|
// 如果重复的配置文件设置了 merge 属性为 true
|
|
|
|
|
if (dup.merge) {
|
|
|
|
|
// 记录合并操作的日志信息
|
|
|
|
|
logging.debug`\tMerging duplicate ${conf.name} from ${conf._locations}`;
|
|
|
|
|
// 将当前配置文件的 _locations 属性添加到重复配置文件的 _locations 属性中
|
|
|
|
|
dup._locations = [...dup._locations, ...conf._locations];
|
|
|
|
|
// 使用 Object.assign 方法合并当前配置文件和重复配置文件的属性
|
|
|
|
|
Object.assign(dup, { ...conf, ...dup });
|
|
|
|
|
// 如果重复的配置文件没有设置 merge 属性为 true
|
|
|
|
|
} else {
|
|
|
|
|
// 记录忽略操作的日志信息
|
|
|
|
|
logging.debug`\tIgnoring duplicate ${conf.name} from ${conf._locations}`;
|
|
|
|
|
}
|
|
|
|
|
// 如果没有找到重复的配置文件
|
|
|
|
|
} else {
|
|
|
|
|
// 记录添加操作的日志信息
|
|
|
|
|
logging.debug`\tAdded configuration ${conf.name} from ${conf._locations}`;
|
|
|
|
|
// 将当前配置文件添加到 loadedConfigs 数组中
|
|
|
|
|
loadedConfigs.push(conf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Handle configs extending other configs
|
|
|
|
|
// 定义一个 BuildData 类型,包含一个 FileSystemConfig 类型的 source 属性,表示原始配置
|
|
|
|
|
// 以及一个可选的 result 属性,表示构建后的配置,和一个可选的 skipped 属性,表示是否跳过构建
|
|
|
|
|
type BuildData = { source: FileSystemConfig; result?: FileSystemConfig; skipped?: boolean };
|
|
|
|
|
// 创建一个名为 buildData 的 Map 对象,用于存储 BuildData 类型的数据,键为配置的名称,值为 BuildData 对象
|
|
|
|
|
const buildData = new Map<string, BuildData>();
|
|
|
|
|
// 创建一个名为 building 的数组,用于存储当前正在构建的 BuildData 对象
|
|
|
|
|
let building: BuildData[] = [];
|
|
|
|
|
// 遍历 loadedConfigs 数组,将每个配置的名称和原始配置对象添加到 buildData 中
|
|
|
|
|
loadedConfigs.forEach(c => buildData.set(c.name, { source: c }));
|
|
|
|
|
/**
|
|
|
|
|
* 获取或构建一个配置对象。
|
|
|
|
|
* 这个函数会根据给定的名称从 buildData 中获取一个 BuildData 对象。如果对象不存在,它会尝试构建一个新的配置对象。
|
|
|
|
|
* 在构建过程中,它会处理配置对象的扩展属性,确保所有依赖的配置对象都被正确地获取或构建。
|
|
|
|
|
* 如果在构建过程中遇到循环引用或其他错误,函数会记录错误信息并返回相应的 BuildData 对象。
|
|
|
|
|
* @param name - 要获取或构建的配置对象的名称。
|
|
|
|
|
* @returns 一个 BuildData 对象,包含原始配置和可能的构建结果。如果配置对象无法构建,返回 undefined。
|
|
|
|
|
*/
|
|
|
|
|
function getOrBuild(name: string): BuildData | undefined {
|
|
|
|
|
// 从 buildData 中获取指定名称的配置数据
|
|
|
|
|
const data = buildData.get(name);
|
|
|
|
|
// Handle special cases (missing, built, skipped or looping)
|
|
|
|
|
// 处理特殊情况(缺失、已构建、跳过或循环)
|
|
|
|
|
if (!data || data.result || data.skipped || building.includes(data)) return data;
|
|
|
|
|
// Start building the resulting config
|
|
|
|
|
// 开始构建结果配置
|
|
|
|
|
building.push(data);
|
|
|
|
|
// 创建一个新的对象,包含源配置的所有属性
|
|
|
|
|
const result = { ...data.source };
|
|
|
|
|
// Handle extending
|
|
|
|
|
// 处理扩展
|
|
|
|
|
let extend = result.extend;
|
|
|
|
|
// 如果 extend 是字符串,则将其转换为数组
|
|
|
|
|
if (typeof extend === 'string') extend = [extend];
|
|
|
|
|
// 遍历扩展配置名称数组
|
|
|
|
|
for (const depName of extend || []) {
|
|
|
|
|
// 获取或构建依赖配置数据
|
|
|
|
|
const depData = getOrBuild(depName);
|
|
|
|
|
// 如果依赖配置数据不存在
|
|
|
|
|
if (!depData) {
|
|
|
|
|
// 记录错误信息,跳过当前配置的构建
|
|
|
|
|
logging.error`\tSkipping "${name}" because it extends unknown config "${depName}"`;
|
|
|
|
|
// 从 building 数组中弹出当前配置数据,并标记为已跳过
|
|
|
|
|
building.pop()!.skipped = true;
|
|
|
|
|
// 返回当前配置数据
|
|
|
|
|
return data;
|
|
|
|
|
} else if (depData.skipped && !data.skipped) {
|
|
|
|
|
}
|
|
|
|
|
// 如果依赖配置数据已被跳过,且当前配置数据未被跳过
|
|
|
|
|
else if (depData.skipped && !data.skipped) {
|
|
|
|
|
// 记录错误信息,跳过当前配置的构建
|
|
|
|
|
logging.error`\tSkipping "${name}" because it extends skipped config "${depName}"`;
|
|
|
|
|
// 从 building 数组中弹出当前配置数据,并标记为已跳过
|
|
|
|
|
building.pop()!.skipped = true;
|
|
|
|
|
// 返回当前配置数据
|
|
|
|
|
return data;
|
|
|
|
|
} else if (data.skipped || building.includes(depData)) {
|
|
|
|
|
}
|
|
|
|
|
// 如果当前配置数据已被跳过,或 building 数组中包含依赖配置数据
|
|
|
|
|
else if (data.skipped || building.includes(depData)) {
|
|
|
|
|
// 记录错误信息,跳过当前配置的构建
|
|
|
|
|
logging.error`\tSkipping "${name}" because it extends config "${depName}" which (indirectly) extends "${name}"`;
|
|
|
|
|
// 如果 building 数组长度大于 0,记录检测到的循环
|
|
|
|
|
if (building.length) logging.debug`\t\tdetected cycle: ${building.map(b => b.source.name).join(' -> ')} -> ${depName}`;
|
|
|
|
|
// 从 building 数组中移除依赖配置数据,并标记为已跳过
|
|
|
|
|
building.splice(building.indexOf(depData)).forEach(d => d.skipped = true);
|
|
|
|
|
// 返回当前配置数据
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
// 记录扩展信息
|
|
|
|
|
logging.debug`\tExtending "${name}" with "${depName}"`;
|
|
|
|
|
// 将依赖配置数据的结果合并到当前配置的结果中
|
|
|
|
|
Object.assign(result, depData.result);
|
|
|
|
|
}
|
|
|
|
|
// 从 building 数组中移除当前正在处理的配置数据
|
|
|
|
|
building.pop();
|
|
|
|
|
// 将源配置数据与处理后的结果合并,生成最终的配置对象
|
|
|
|
|
data.result = Object.assign(result, data.source);
|
|
|
|
|
// 返回处理后的配置数据
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
// 使用 getOrBuild 函数处理 loadedConfigs 中的每个配置,获取其结果,并过滤掉非 FileSystemConfig 类型的结果
|
|
|
|
|
loadedConfigs = loadedConfigs.map(c => getOrBuild(c.name)?.result).filter(isFileSystemConfig);
|
|
|
|
|
|
|
|
|
|
// 如果处理后的配置数量少于 buildData 中的配置数量,说明存在一些配置被跳过
|
|
|
|
|
if (loadedConfigs.length < buildData.size) {
|
|
|
|
|
// 显示错误信息,提示用户有些 SSH FS 配置由于 "extend" 选项不正确而被跳过,并提供查看日志的选项
|
|
|
|
|
vscode.window.showErrorMessage(`Skipped some SSH FS configs due to incorrect "extend" options`, 'See logs').then(answer => {
|
|
|
|
|
if (answer === 'See logs') OUTPUT_CHANNEL.show(true);
|
|
|
|
|
// 如果用户选择查看日志
|
|
|
|
|
if (answer === 'See logs') {
|
|
|
|
|
// 显示输出通道
|
|
|
|
|
OUTPUT_CHANNEL.show(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// And we're done
|
|
|
|
|
// 记录日志,显示已应用的配置层数以及最终得到的配置数量
|
|
|
|
|
logging.info`Applied config layers resulting in ${loadedConfigs.length} configurations`;
|
|
|
|
|
// 遍历 UPDATE_LISTENERS 数组,通知每个监听者配置已更新
|
|
|
|
|
UPDATE_LISTENERS.forEach(listener => listener(loadedConfigs));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义一个名为 LOADING_CONFIGS 的 Promise 类型的变量,用于存储加载文件系统配置的 Promise 对象
|
|
|
|
|
export let LOADING_CONFIGS: Promise<FileSystemConfig[]>;
|
|
|
|
|
/**
|
|
|
|
|
* 加载文件系统配置。
|
|
|
|
|
* 这个函数会从 VSCode 的工作区配置中获取 'sshfs.configs' 的值,并将其解析为 FileSystemConfig 数组。
|
|
|
|
|
* 然后,它会将这些配置文件分别存储在 configLayers.global 和 configLayers.workspace 中。
|
|
|
|
|
* 此外,它还会检查 'sshfs.configpaths' 配置,以查找其他可能的配置文件路径,并尝试加载这些文件。
|
|
|
|
|
* 如果工作区文件夹的 URI 是断开连接的,函数将跳过加载,并记录一条信息日志。
|
|
|
|
|
* @returns 一个包含解析后的文件系统配置数组的 Promise,如果没有找到配置文件,则返回一个空数组。
|
|
|
|
|
*/
|
|
|
|
|
export async function loadConfigs(): Promise<FileSystemConfig[]> {
|
|
|
|
|
// 返回 LOADING_CONFIGS 的 Promise 类型的变量,用于存储加载文件系统配置的 Promise 对象
|
|
|
|
|
return LOADING_CONFIGS = catchingPromise(async loaded => {
|
|
|
|
|
// 记录日志,显示正在加载配置
|
|
|
|
|
logging.info('Loading configurations...');
|
|
|
|
|
// 等待重命名无名称的配置
|
|
|
|
|
await renameNameless();
|
|
|
|
|
// Keep all found configs "ordened" by layer, for proper deduplication/merging
|
|
|
|
|
// while also allowing partially refreshing (workspaceFolder configs) without having to reload *everything*
|
|
|
|
|
// 初始化 configLayers 对象,用于存储不同层级的配置
|
|
|
|
|
configLayers = { global: [], workspace: [], folder: new Map() };
|
|
|
|
|
// Fetch global/workspace configs from vscode settings
|
|
|
|
|
// 从 VSCode 设置中获取全局和工作区的配置
|
|
|
|
|
loadGlobalOrWorkspaceConfigs();
|
|
|
|
|
// Fetch configs from config files defined in global/workspace settings
|
|
|
|
|
// 获取配置文件路径
|
|
|
|
|
const configpaths = getConfigPaths();
|
|
|
|
|
// 遍历全局配置文件路径,尝试查找并加载配置文件
|
|
|
|
|
for (const location of configpaths.global) {
|
|
|
|
|
configLayers.global.push(...await tryFindConfigFiles(location, 'Global Settings'));
|
|
|
|
|
}
|
|
|
|
|
// 遍历工作区配置文件路径,尝试查找并加载配置文件
|
|
|
|
|
for (const location of configpaths.workspace) {
|
|
|
|
|
configLayers.workspace.push(...await tryFindConfigFiles(location, 'Workspace Settings'));
|
|
|
|
|
}
|
|
|
|
|
// Fetch configs from opened folders
|
|
|
|
|
// 遍历已打开的工作区文件夹,加载每个文件夹的配置
|
|
|
|
|
for (const folder of vscode.workspace.workspaceFolders || []) {
|
|
|
|
|
await loadWorkspaceFolderConfigs(folder);
|
|
|
|
|
}
|
|
|
|
|
// 应用配置层,合并和处理多个配置层
|
|
|
|
|
applyConfigLayers();
|
|
|
|
|
// 通知加载完成
|
|
|
|
|
loaded(loadedConfigs);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 调用 loadConfigs 函数加载文件系统配置
|
|
|
|
|
loadConfigs();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重新加载特定 authority 的工作区文件夹配置。
|
|
|
|
|
* 这个函数会遍历所有的工作区文件夹,找到与给定 authority 匹配的文件夹,然后重新加载它们的配置。
|
|
|
|
|
* 如果没有找到匹配的文件夹,函数会直接返回。
|
|
|
|
|
* @param authority - 要重新加载配置的 authority。
|
|
|
|
|
* @returns 一个 Promise,当所有匹配的工作区文件夹的配置都被重新加载后,它会被解决。
|
|
|
|
|
*/
|
|
|
|
|
export async function reloadWorkspaceFolderConfigs(authority: string): Promise<void> {
|
|
|
|
|
// 将 authority 转换为小写,以便进行不区分大小写的比较
|
|
|
|
|
authority = authority.toLowerCase();
|
|
|
|
|
// 使用 Promise.all 并发处理所有匹配的工作区文件夹的配置加载
|
|
|
|
|
const promises = (vscode.workspace.workspaceFolders || []).map(workspaceFolder => {
|
|
|
|
|
if (workspaceFolder.uri.authority.toLowerCase() !== authority) return;
|
|
|
|
|
// 如果工作区文件夹的 authority 与给定的 authority 不匹配,则忽略该文件夹
|
|
|
|
|
if (workspaceFolder.uri.authority.toLowerCase()!== authority) return;
|
|
|
|
|
// 记录日志,显示正在重新加载特定 authority 的工作区文件夹配置
|
|
|
|
|
logging.info`Reloading workspace folder configs for '${authority}' connection`;
|
|
|
|
|
// 返回加载工作区文件夹配置的 Promise
|
|
|
|
|
return loadWorkspaceFolderConfigs(workspaceFolder);
|
|
|
|
|
});
|
|
|
|
|
// 如果没有找到匹配的工作区文件夹,则直接返回
|
|
|
|
|
if (!promises.length) return;
|
|
|
|
|
// 等待所有 Promise 完成
|
|
|
|
|
await Promise.all(promises);
|
|
|
|
|
// 应用配置层,合并和处理多个配置层
|
|
|
|
|
applyConfigLayers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 监听 VSCode 工作区配置的变化
|
|
|
|
|
vscode.workspace.onDidChangeConfiguration(async (e) => {
|
|
|
|
|
// 如果配置变化影响到 'sshfs.configpaths',则重新加载所有配置
|
|
|
|
|
if (e.affectsConfiguration('sshfs.configpaths')) {
|
|
|
|
|
logging.info('Config paths changed for global/workspace, reloading configs...');
|
|
|
|
|
return loadConfigs();
|
|
|
|
|
}
|
|
|
|
|
// 检查是否全局配置 'sshfs.configs' 发生变化
|
|
|
|
|
let updatedGlobal = e.affectsConfiguration('sshfs.configs');
|
|
|
|
|
if (updatedGlobal) {
|
|
|
|
|
logging.info('Config paths changed for global/workspace, updating layers...');
|
|
|
|
|
// 如果全局配置发生变化,重新加载全局和工作区配置
|
|
|
|
|
await loadGlobalOrWorkspaceConfigs();
|
|
|
|
|
}
|
|
|
|
|
// 初始化变量,用于标记是否有任何配置更新
|
|
|
|
|
let updatedAtAll = updatedGlobal;
|
|
|
|
|
// 遍历所有工作区文件夹
|
|
|
|
|
for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
|
|
|
|
|
// 检查是否全局配置、工作区文件夹特定配置或配置路径发生变化
|
|
|
|
|
if (updatedGlobal
|
|
|
|
|
|| e.affectsConfiguration('sshfs.configs', workspaceFolder)
|
|
|
|
|
|| e.affectsConfiguration('sshfs.configpaths', workspaceFolder)) {
|
|
|
|
|
logging.info(`Configs and/or config paths changed for workspace folder ${workspaceFolder.uri}, updating layers...`);
|
|
|
|
|
// 如果有变化,重新加载工作区文件夹的配置
|
|
|
|
|
await loadWorkspaceFolderConfigs(workspaceFolder);
|
|
|
|
|
// 标记有配置更新
|
|
|
|
|
updatedAtAll = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 如果有任何配置更新,应用新的配置层
|
|
|
|
|
if (updatedAtAll) applyConfigLayers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 监听 VSCode 工作区文件夹的变化事件。
|
|
|
|
|
* 当工作区文件夹发生变化时,此事件会被触发。
|
|
|
|
|
* 事件对象包含了被添加和移除的文件夹信息。
|
|
|
|
|
* 当有文件夹被添加或移除时,会重新计算配置,并更新配置层。
|
|
|
|
|
* 如果在重新加载配置过程中发生错误,会记录错误信息,并返回已加载的配置。
|
|
|
|
|
*/
|
|
|
|
|
vscode.workspace.onDidChangeWorkspaceFolders(event => {
|
|
|
|
|
// 使用 catchingPromise 函数来处理异步操作,确保即使发生错误也能正确处理
|
|
|
|
|
LOADING_CONFIGS = catchingPromise<FileSystemConfig[]>(async loaded => {
|
|
|
|
|
// 记录日志,显示工作区文件夹发生变化,正在重新计算配置
|
|
|
|
|
logging.info('Workspace folders changed, recalculating configs with updated workspaceFolder configs...');
|
|
|
|
|
// 遍历被移除的文件夹,从 configLayers.folder 中删除相应的配置
|
|
|
|
|
event.removed.forEach(folder => configLayers.folder.delete(folder.uri.toString()));
|
|
|
|
|
// 遍历被添加的文件夹,重新加载它们的配置
|
|
|
|
|
for (const folder of event.added) await loadWorkspaceFolderConfigs(folder);
|
|
|
|
|
// 应用新的配置层
|
|
|
|
|
applyConfigLayers();
|
|
|
|
|
// 通知配置加载完成
|
|
|
|
|
loaded(loadedConfigs);
|
|
|
|
|
// 捕获任何可能发生的错误
|
|
|
|
|
}).catch(e => {
|
|
|
|
|
// 记录错误信息
|
|
|
|
|
logging.error`Error while reloading configs in onDidChangeWorkspaceFolders: ${e}`;
|
|
|
|
|
// 返回已加载的配置
|
|
|
|
|
return loadedConfigs;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 定义一个函数类型 ConfigAlterer,用于修改文件系统配置数组。
|
|
|
|
|
* @param configs - 要修改的文件系统配置数组。
|
|
|
|
|
* @returns 修改后的文件系统配置数组,或者 null 或 false 表示不进行修改。
|
|
|
|
|
*/
|
|
|
|
|
export type ConfigAlterer = (configs: FileSystemConfig[]) => FileSystemConfig[] | null | false;
|
|
|
|
|
/**
|
|
|
|
|
* 修改指定位置的文件系统配置。
|
|
|
|
|
* 这个函数会根据给定的位置和修改器函数来更新配置。
|
|
|
|
|
* 如果位置是工作区文件夹,它会使用 VSCode 的配置 API 来更新配置。
|
|
|
|
|
* 如果位置是一个文件路径,它会直接读取并修改该文件。
|
|
|
|
|
* @param location - 要修改的配置的位置,可以是工作区文件夹的 URI 或者是文件路径。
|
|
|
|
|
* @param alterer - 一个函数,用于修改配置数组。
|
|
|
|
|
* @returns 如果修改成功,返回修改后的配置数组;如果没有修改,返回 undefined。
|
|
|
|
|
* @throws 如果在修改过程中发生错误,会抛出相应的错误。
|
|
|
|
|
*/
|
|
|
|
|
export async function alterConfigs(location: ConfigLocation, alterer: ConfigAlterer) {
|
|
|
|
|
// 定义一个 URI 类型的变量 uri,用于存储 URI 地址,初始值为 undefined
|
|
|
|
|
let uri!: vscode.Uri | undefined;
|
|
|
|
|
// 定义一个字符串类型的变量 prettyLocation,用于存储格式化后的位置信息,初始值为 undefined
|
|
|
|
|
let prettyLocation: string | undefined;
|
|
|
|
|
// 如果 location 是字符串且以 'WorkspaceFolder ' 开头,则提取 URI 并设置 prettyLocation
|
|
|
|
|
if (typeof location === 'string' && location.startsWith('WorkspaceFolder ')) {
|
|
|
|
|
prettyLocation = location;
|
|
|
|
|
uri = vscode.Uri.parse(location.substring(16));
|
|
|
|
|
location = vscode.ConfigurationTarget.WorkspaceFolder;
|
|
|
|
|
}
|
|
|
|
|
// 根据 location 的类型进行不同的处理
|
|
|
|
|
switch (location) {
|
|
|
|
|
case vscode.ConfigurationTarget.WorkspaceFolder:
|
|
|
|
|
// 抛出错误,因为不允许使用 WorkspaceFolder URI 更新 WorkspaceFolder 设置
|
|
|
|
|
throw new Error(`Trying to update WorkspaceFolder settings with WorkspaceFolder Uri`);
|
|
|
|
|
case vscode.ConfigurationTarget.Global:
|
|
|
|
|
// 如果 prettyLocation 未设置,则默认为 'Global'
|
|
|
|
|
prettyLocation ||= 'Global';
|
|
|
|
|
case vscode.ConfigurationTarget.Workspace:
|
|
|
|
|
// 如果 prettyLocation 未设置,则默认为 'Workspace'
|
|
|
|
|
prettyLocation ||= 'Workspace';
|
|
|
|
|
// 获取 sshfs 配置对象
|
|
|
|
|
const conf = vscode.workspace.getConfiguration('sshfs', uri);
|
|
|
|
|
// 检查 configs 配置项
|
|
|
|
|
const inspect = conf.inspect<FileSystemConfig[]>('configs')!;
|
|
|
|
|
// If the array doesn't exist, create a new empty one
|
|
|
|
|
// 如果数组不存在,则创建一个新的空数组
|
|
|
|
|
const array = inspect[[, 'globalValue', 'workspaceValue', 'workspaceFolderValue'][location]!] || [];
|
|
|
|
|
// 使用 alterer 函数修改配置数组
|
|
|
|
|
let modified = alterer(array);
|
|
|
|
|
// 如果没有修改,则直接返回
|
|
|
|
|
if (!modified) return;
|
|
|
|
|
// 遍历修改后的配置数组,删除以 '_' 开头的键
|
|
|
|
|
modified = modified.map((config) => {
|
|
|
|
|
const newConfig = { ...config };
|
|
|
|
|
for (const key in config) {
|
|
|
|
@ -508,19 +754,28 @@ export async function alterConfigs(location: ConfigLocation, alterer: ConfigAlte
|
|
|
|
|
}
|
|
|
|
|
return newConfig;
|
|
|
|
|
});
|
|
|
|
|
// 更新配置
|
|
|
|
|
await conf.update('configs', modified, location);
|
|
|
|
|
// 记录日志,显示已更新配置
|
|
|
|
|
logging.debug`\tUpdated configs in ${prettyLocation} Settings`;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 如果 location 不是字符串,则抛出错误
|
|
|
|
|
if (typeof location !== 'string') throw new Error(`Invalid _location field: ${location}`);
|
|
|
|
|
// 解析 location 为 URI
|
|
|
|
|
uri = vscode.Uri.parse(location, true);
|
|
|
|
|
// 读取配置文件
|
|
|
|
|
const configs = await readConfigFile(uri, true);
|
|
|
|
|
// 如果没有找到配置文件,则记录错误并抛出错误
|
|
|
|
|
if (!configs) {
|
|
|
|
|
logging.error`Config file '${uri}' not found while altering configs'`;
|
|
|
|
|
throw new Error(`Config file '${uri}' not found while altering configs'`);
|
|
|
|
|
}
|
|
|
|
|
// 使用 alterer 函数修改配置数组
|
|
|
|
|
let altered = alterer(configs);
|
|
|
|
|
// 如果没有修改,则直接返回
|
|
|
|
|
if (!altered) return;
|
|
|
|
|
// 遍历修改后的配置数组,删除以 '_' 开头的键
|
|
|
|
|
altered = altered.map((config) => {
|
|
|
|
|
const newConfig = { ...config };
|
|
|
|
|
for (const key in config) {
|
|
|
|
@ -528,48 +783,96 @@ export async function alterConfigs(location: ConfigLocation, alterer: ConfigAlte
|
|
|
|
|
}
|
|
|
|
|
return newConfig;
|
|
|
|
|
});
|
|
|
|
|
// 将修改后的配置数组转换为 JSON 字符串并写入文件
|
|
|
|
|
const data = Buffer.from(JSON.stringify(altered, null, 4));
|
|
|
|
|
try { await fs.writeFile(uri, data); } catch (e) {
|
|
|
|
|
// 记录错误
|
|
|
|
|
logging.error`Error while writing configs to ${location}: ${e}`;
|
|
|
|
|
// 抛出错误
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
// 记录日志,显示已将修改后的配置写入文件
|
|
|
|
|
logging.debug`\tWritten modified configs to ${location}`;
|
|
|
|
|
// 重新加载配置
|
|
|
|
|
await loadConfigs();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新指定的文件系统配置。
|
|
|
|
|
* 这个函数会根据给定的配置对象和可选的旧名称来更新配置。
|
|
|
|
|
* 如果没有提供旧名称,则默认为配置对象的当前名称。
|
|
|
|
|
* 函数会尝试将新配置保存到指定的位置,并在必要时覆盖旧配置。
|
|
|
|
|
* 如果旧名称与新名称不同,函数会尝试查找并覆盖旧配置。
|
|
|
|
|
* 如果没有找到旧配置,它将被添加到配置列表中。
|
|
|
|
|
* @param config - 要更新的文件系统配置对象。
|
|
|
|
|
* @param oldName - 可选的旧配置名称,如果未提供,则默认为 config 对象的 name 属性。
|
|
|
|
|
* @returns 如果更新成功,返回更新后的配置对象;如果没有更新,返回 undefined。
|
|
|
|
|
* @throws 如果在更新过程中发生错误,会抛出相应的错误。
|
|
|
|
|
*/
|
|
|
|
|
export async function updateConfig(config: FileSystemConfig, oldName = config.name) {
|
|
|
|
|
// 从 config 对象中提取 name 和 _location 属性
|
|
|
|
|
const { name, _location } = config;
|
|
|
|
|
// 如果 name 属性不存在,则抛出错误
|
|
|
|
|
if (!name) throw new Error(`The given config has no name field`);
|
|
|
|
|
// 如果 _location 属性不存在,则抛出错误
|
|
|
|
|
if (!_location) throw new Error(`The given config has no _location field`);
|
|
|
|
|
// 记录日志,显示正在保存配置
|
|
|
|
|
logging.info`Saving config ${name} to ${_location}`;
|
|
|
|
|
// 如果 oldName 不等于 config.name,则记录日志,显示将尝试覆盖旧配置
|
|
|
|
|
if (oldName !== config.name) {
|
|
|
|
|
logging.debug`\tSaving ${name} will try to overwrite old config ${oldName}`;
|
|
|
|
|
}
|
|
|
|
|
// 调用 alterConfigs 函数来修改配置
|
|
|
|
|
await alterConfigs(_location, (configs) => {
|
|
|
|
|
// 记录日志,显示配置位置的现有配置
|
|
|
|
|
logging.debug`\tConfig location '${_location}' has following configs: ${configs.map(c => c.name).join(', ')}`;
|
|
|
|
|
// 在 configs 数组中查找名称与 oldName 匹配的配置的索引
|
|
|
|
|
const index = configs.findIndex(c => c.name ? c.name.toLowerCase() === oldName.toLowerCase() : false);
|
|
|
|
|
// 如果没有找到匹配的配置,则将新配置添加到 configs 数组中
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
logging.debug`\tAdding the new config to the existing configs`;
|
|
|
|
|
configs.push(config);
|
|
|
|
|
// 如果找到了匹配的配置,则用新配置覆盖旧配置
|
|
|
|
|
} else {
|
|
|
|
|
logging.debug`\tOverwriting config '${configs[index].name}' at index ${index} with the new config`;
|
|
|
|
|
configs[index] = config;
|
|
|
|
|
}
|
|
|
|
|
// 返回修改后的 configs 数组
|
|
|
|
|
return configs;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除指定的文件系统配置。
|
|
|
|
|
* 这个函数会根据给定的配置对象来删除配置。
|
|
|
|
|
* 它会尝试在指定的位置找到并删除该配置。
|
|
|
|
|
* 如果配置不存在,函数会抛出错误。
|
|
|
|
|
* @param config - 要删除的文件系统配置对象。
|
|
|
|
|
* @returns 如果删除成功,返回 undefined;如果没有删除,抛出错误。
|
|
|
|
|
* @throws 如果在删除过程中发生错误,会抛出相应的错误。
|
|
|
|
|
*/
|
|
|
|
|
export async function deleteConfig(config: FileSystemConfig) {
|
|
|
|
|
// 从 config 对象中提取 name 和 _location 属性
|
|
|
|
|
const { name, _location } = config;
|
|
|
|
|
// 如果 name 属性不存在,则抛出错误
|
|
|
|
|
if (!name) throw new Error(`The given config has no name field`);
|
|
|
|
|
// 如果 _location 属性不存在,则抛出错误
|
|
|
|
|
if (!_location) throw new Error(`The given config has no _location field`);
|
|
|
|
|
// 记录日志,显示正在删除配置
|
|
|
|
|
logging.info`Deleting config ${name} in ${_location}`;
|
|
|
|
|
// 调用 alterConfigs 函数来修改配置
|
|
|
|
|
await alterConfigs(_location, (configs) => {
|
|
|
|
|
// 记录日志,显示配置位置的现有配置
|
|
|
|
|
logging.debug`\tConfig location '${_location}' has following configs: ${configs.map(c => c.name).join(', ')}`;
|
|
|
|
|
// 在 configs 数组中查找名称与 name 匹配的配置的索引
|
|
|
|
|
const index = configs.findIndex(c => c.name ? c.name.toLowerCase() === name.toLowerCase() : false);
|
|
|
|
|
// 如果没有找到匹配的配置,则抛出错误
|
|
|
|
|
if (index === -1) throw new Error(`Config '${name}' not found in ${_location}`);
|
|
|
|
|
// 记录日志,显示正在删除指定索引处的配置
|
|
|
|
|
logging.debug`\tDeleting config '${configs[index].name}' at index ${index}`;
|
|
|
|
|
// 从 configs 数组中删除指定索引处的配置
|
|
|
|
|
configs.splice(index, 1);
|
|
|
|
|
// 返回修改后的 configs 数组
|
|
|
|
|
return configs;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
@ -628,27 +931,62 @@ export function getConfig(input: string): FileSystemConfig | undefined {
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 比较两个值是否匹配。
|
|
|
|
|
* 这个函数会根据值的类型进行不同的比较。
|
|
|
|
|
* 如果值的类型不同,则直接返回 false。
|
|
|
|
|
* 如果值是对象或数组,则会递归地比较它们的每个属性或元素。
|
|
|
|
|
* 如果值是数组,则会比较它们的长度和每个元素。
|
|
|
|
|
* 如果值是对象,则会比较它们的键和对应的值。
|
|
|
|
|
* @param a - 要比较的第一个值。
|
|
|
|
|
* @param b - 要比较的第二个值。
|
|
|
|
|
* @returns 如果两个值匹配,返回 true;否则,返回 false。
|
|
|
|
|
*/
|
|
|
|
|
function valueMatches(a: any, b: any): boolean {
|
|
|
|
|
// 如果 a 和 b 的类型不同,则它们不匹配
|
|
|
|
|
if (typeof a !== typeof b) return false;
|
|
|
|
|
// 如果 a 和 b 不是对象,则直接比较它们的值
|
|
|
|
|
if (typeof a !== 'object') return a === b;
|
|
|
|
|
// 如果 a 或 b 是 null 或 undefined,则它们不匹配
|
|
|
|
|
if (!a || !b) return a === b;
|
|
|
|
|
// 如果 a 和 b 是数组,则比较它们的每个元素
|
|
|
|
|
if (Array.isArray(a)) {
|
|
|
|
|
// 如果 b 不是数组,则它们不匹配
|
|
|
|
|
if (!Array.isArray(b)) return false;
|
|
|
|
|
// 如果 a 和 b 的长度不同,则它们不匹配
|
|
|
|
|
if (a.length !== b.length) return false;
|
|
|
|
|
// 递归地比较数组中的每个元素
|
|
|
|
|
return a.every((value, index) => valueMatches(value, b[index]));
|
|
|
|
|
}
|
|
|
|
|
// 获取 a 和 b 的所有键
|
|
|
|
|
const keysA = Object.keys(a);
|
|
|
|
|
const keysB = Object.keys(b);
|
|
|
|
|
// 如果 a 和 b 的键的数量不同,则它们不匹配
|
|
|
|
|
if (keysA.length !== keysB.length) return false;
|
|
|
|
|
// 比较 a 和 b 的每个键对应的值
|
|
|
|
|
for (const key of keysA) {
|
|
|
|
|
// 如果 a 和 b 的对应键的值不匹配,则它们不匹配
|
|
|
|
|
if (!valueMatches(a[key], b[key])) return false;
|
|
|
|
|
}
|
|
|
|
|
// 如果所有的比较都通过了,则 a 和 b 匹配
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查两个文件系统配置对象是否匹配。
|
|
|
|
|
* 这个函数会比较两个配置对象的所有属性,以确定它们是否完全相同。
|
|
|
|
|
* 如果两个配置对象的所有属性都匹配,则返回 true;否则,返回 false。
|
|
|
|
|
* @param a - 第一个文件系统配置对象。
|
|
|
|
|
* @param b - 第二个文件系统配置对象。
|
|
|
|
|
* @returns 如果两个配置对象匹配,返回 true;否则,返回 false。
|
|
|
|
|
*/
|
|
|
|
|
export function configMatches(a: FileSystemConfig, b: FileSystemConfig): boolean {
|
|
|
|
|
// This is kind of the easiest and most robust way of checking if configs are identical.
|
|
|
|
|
// If it wasn't for `loadedConfigs` (and its contents) regularly being fully recreated, we
|
|
|
|
|
// This is kind of the easiest and most robust way of checking if two configs are the same.
|
|
|
|
|
// If it wasn't for `loadedConfigs` (and its contents) regularly being fully recreated,
|
|
|
|
|
// could just use === between the two configs. This'll do for now.
|
|
|
|
|
// 这是检查配置是否相同的最简单和最可靠的方法。
|
|
|
|
|
// 如果不是因为 `loadedConfigs`(及其内容)经常被完全重新创建,我们
|
|
|
|
|
// 可以直接使用 === 来比较两个配置。目前,这个方法就足够了。
|
|
|
|
|
return valueMatches(a, b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|