From 34c335313ec266e1d7e1a73ff916936ccbaf7cf3 Mon Sep 17 00:00:00 2001 From: yetao Date: Mon, 28 Oct 2024 17:50:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor=F0=9F=8E=A8:=20=20(=E9=98=85=E8=AF=BB?= =?UTF-8?q?=E4=BB=A3=E7=A0=81)=EF=BC=9Aconfig.ts=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.ts | 150 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 13 deletions(-) diff --git a/src/config.ts b/src/config.ts index 12c682f..182ed6a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,109 +1,222 @@ +// 引入 common/fileSystemConfig 模块中的 ConfigLocation、FileSystemConfig、invalidConfigName、isFileSystemConfig、parseConnectionString 类型或函数 import { ConfigLocation, FileSystemConfig, invalidConfigName, isFileSystemConfig, parseConnectionString } from 'common/fileSystemConfig'; +// 引入 jsonc-parser 模块中的 ParseError、parse as parseJsonc、printParseErrorCode 类型或函数 import { ParseError, parse as parseJsonc, printParseErrorCode } from 'jsonc-parser'; +// 引入 path 模块,用于处理和转换文件路径 import * as path from 'path'; +// 引入 vscode 模块,用于访问 VS Code 的 API import * as vscode from 'vscode'; +// 引入 extension 模块中的 MANAGER 变量 import { MANAGER } from './extension'; +// 引入 logging 模块中的 Logging、OUTPUT_CHANNEL 类型或函数 import { Logging, OUTPUT_CHANNEL } from './logging'; +// 引入 utils 模块中的 catchingPromise 函数 import { catchingPromise } from './utils'; +// 定义一个常量 fs,它引用了 VS Code 工作区的文件系统 API const fs = vscode.workspace.fs; // Logger scope with default warning/error options (which enables stacktraces) disabled +// 创建一个日志记录器,范围为 undefined,禁用堆栈跟踪 const logging = Logging.scope(undefined, false); +// 禁用警告的默认选项 logging.warning.options = {}; +// 禁用错误的默认选项 logging.error.options = {}; +/** + * 生成一个随机的可用名称,该名称在给定的配置数组中不存在。 + * 如果名称已存在,则会在名称后添加一个索引,直到找到一个不存在的名称。 + * @param configs - 要检查的配置数组。 + * @param index - 起始索引,默认为 0。 + * @returns 一个包含随机可用名称和下一个索引的数组。 + */ function randomAvailableName(configs: FileSystemConfig[], index = 0): [string, number] { + // 初始化名称为 'unnamed' 或 'unnamed' 加上索引 let name = index ? `unnamed${index}` : 'unnamed'; + // 当名称在配置数组中存在时,进入循环 while (configs.find(c => c.name === name)) { + // 索引加一 index += 1; + // 更新名称为 'unnamed' 加上新的索引 name = `unnamed${index}`; } + // 返回随机可用名称和下一个索引 return [name, index + 1]; } // TODO: Do this better, especially since we can dynamically start adding configs (for workspaceFolders) +/** + * 重命名所有未命名的配置。 + * 此函数会检查全局、工作区和工作区文件夹级别的配置, + * 并为那些没有名称的配置生成一个随机的唯一名称。 + * + * @returns {Promise} 当所有配置都被检查并可能重命名后解决的 Promise。 + */ export async function renameNameless() { + // 获取 sshfs 配置对象 const conf = vscode.workspace.getConfiguration('sshfs'); + // 检查 configs 配置项,获取其全局、工作区和工作区文件夹级别的值 const inspect = conf.inspect('configs')!; + // 初始化随机索引 let randomIndex = 0; + // 收集所有级别的配置 const configs = [ - ...(inspect.globalValue || []), - ...(inspect.workspaceValue || []), - ...(inspect.workspaceFolderValue || []), + ...(inspect.globalValue || []), + ...(inspect.workspaceValue || []), + ...(inspect.workspaceFolderValue || []), ]; + + /** + * 修补配置数组,为没有名称的配置分配随机名称。 + * @param {FileSystemConfig[] | undefined} v - 要修补的配置数组。 + * @param {vscode.ConfigurationTarget} loc - 配置的目标位置。 + * @returns {Promise} - 当配置更新完成时解决的 Promise。 + */ function patch(v: FileSystemConfig[] | undefined, loc: vscode.ConfigurationTarget) { + // 如果配置数组不存在,则不进行任何操作 if (!v) return; + // 标记是否所有配置都有名称 let okay = true; + // 遍历配置数组 v.forEach((config) => { + // 如果配置没有名称 if (!config.name) { + // 生成一个随机可用名称 [config.name, randomIndex] = randomAvailableName(configs, randomIndex); + // 记录警告信息 logging.warning(`Renamed unnamed config to ${config.name}`); + // 标记配置需要更新 okay = false; } }); + // 如果所有配置都有名称,则不进行任何操作 if (okay) return; + // 更新配置 return conf.update('configs', v, loc).then(() => { }, res => logging.error`Error while saving configs (CT=${loc}): ${res}`); } + + // 依次更新全局、工作区和工作区文件夹级别的配置 await patch(inspect.globalValue, vscode.ConfigurationTarget.Global); await patch(inspect.workspaceValue, vscode.ConfigurationTarget.Workspace); await patch(inspect.workspaceFolderValue, vscode.ConfigurationTarget.WorkspaceFolder); } + +// 定义一个数组,用于存储已加载的文件系统配置 let loadedConfigs: FileSystemConfig[] = []; +/** + * 获取已加载的文件系统配置。 + * @returns {FileSystemConfig[]} 已加载的文件系统配置数组。 + */ export function getConfigs() { + // 返回已加载的文件系统配置 return loadedConfigs; } +/** + * 定义一个数组,用于存储当文件系统配置更新时需要通知的回调函数。 + * 这些回调函数将接收更新后的文件系统配置作为参数。 + */ export const UPDATE_LISTENERS: ((configs: FileSystemConfig[]) => any)[] = []; +/** + * 读取并解析指定的配置文件。 + * @param file - 要读取的配置文件的 URI。 + * @param quiet - 如果为 true,则在文件不存在时不记录错误。 + * @returns 解析后的文件系统配置数组,如果文件不存在或解析失败,则返回 undefined。 + */ async function readConfigFile(file: vscode.Uri, quiet: boolean): Promise { + // 尝试读取文件内容,如果失败则返回错误 const content = await fs.readFile(file).then(v => v, e => e); + // 如果读取失败且错误代码为 'ENOENT'(文件未找到),并且 quiet 为 true,则返回 undefined if (content instanceof Error) { if (content.code === 'ENOENT' && quiet) return undefined; + // 记录错误信息 logging.error`Error while reading config file ${file}: ${content}`; return undefined; } + // 存储解析过程中出现的错误 const errors: ParseError[] = []; + // 尝试解析文件内容为 JSON,如果失败则返回 null const parsed: FileSystemConfig[] | null = parseJsonc(Buffer.from(content.buffer).toString(), errors); + // 如果解析失败或存在错误,则记录错误信息并返回空数组 if (!parsed || errors.length) { + // 格式化错误信息 const formatted = errors.map(({ error, offset, length }) => `${printParseErrorCode(error)} at ${offset}-${offset + length}`); + // 记录错误信息 logging.error`Couldn't parse ${file} due to invalid JSON:\n${formatted.join('\n')}`; + // 显示错误信息给用户 vscode.window.showErrorMessage(`Couldn't parse the SSH FS config file at ${file}, invalid JSON`); return []; } + // 为每个配置项添加文件位置信息 parsed.forEach(c => c._locations = [c._location = file.toString()]); + // 记录调试信息 logging.debug`Read ${parsed.length} configs from ${file}`; + // 返回解析后的配置数组 return parsed; } +/** + * 读取指定目录下的所有配置文件,并返回解析后的文件系统配置数组。 + * @param uri - 要读取的目录的 URI。 + * @param quiet - 如果为 true,则在文件不存在时不记录错误。 + * @returns 解析后的文件系统配置数组,如果目录不存在或解析失败,则返回 undefined。 + */ async function readConfigDirectory(uri: vscode.Uri, quiet: boolean): Promise { + // 获取目录的状态,如果失败则返回 undefined const stat = await fs.stat(uri).then(e => e, () => undefined); if (!stat) return undefined; + // 读取目录下的所有文件和目录,如果不是目录则会出错 const files = await fs.readDirectory(uri); // errors if not a directory logging.debug`readConfigDirectory got files: ${files}`; + // 过滤出文件类型的项,并筛选出以.json 或.jsonc 结尾的文件 const parsed = await Promise.all(files - .filter(([, t]) => t & vscode.FileType.File).map(([f]) => f) - .filter(file => file.endsWith('.json') || file.endsWith('.jsonc')) - .map(file => readConfigFile(vscode.Uri.joinPath(uri, file), quiet))); + .filter(([, t]) => t & vscode.FileType.File).map(([f]) => f) + .filter(file => file.endsWith('.json') || file.endsWith('.jsonc')) + // 读取每个配置文件,并将结果存储在 parsed 数组中 + .map(file => readConfigFile(vscode.Uri.joinPath(uri, file), quiet))); + // 如果 parsed 数组中有任何元素,则返回过滤后的数组,否则返回 undefined return parsed.some(Boolean) ? parsed.filter(Array.isArray).flat() : undefined; } -const skipDisconnectedUri = (uri: vscode.Uri) => uri.scheme === 'ssh' && !MANAGER?.connectionManager.getActiveConnection(uri.authority); +/** + * 检查给定的 URI 是否应该被跳过,因为它对应的 SSH 连接未激活。 + * @param uri - 要检查的 URI。 + * @returns 如果 URI 的方案是 'ssh' 且没有对应的活动连接,则返回 true,否则返回 false。 + */ +const skipDisconnectedUri = (uri: vscode.Uri) => uri.scheme === 'ssh' &&!MANAGER?.connectionManager.getActiveConnection(uri.authority); +/** + * 尝试从指定的 URI 读取所有配置,该 URI 可以是文件或目录。 + * 如果 URI 指向一个文件,它将尝试读取该文件。如果 URI 指向一个目录,它将尝试读取该目录下的所有配置文件。 + * 此函数会报告错误给用户/日志记录器,并且永远不会拒绝。可能会返回一个空数组。 + * 当给定一个目录路径时,此函数可能会读取多个文件,并将结果聚合在一起。 + * @param uri - 要读取的配置文件或目录的 URI。 + * @param quiet - 如果为 true,则在文件不存在时不记录错误。 + * @returns 解析后的文件系统配置数组,如果 URI 不存在或解析失败,则返回 undefined。 + */ async function findConfigs(uri: vscode.Uri, quiet: boolean): Promise { + // 检查 URI 的方案是否为 'ssh' if (uri.scheme === 'ssh') { - // Ignore SSH URIs for connections that are still connecting - if (skipDisconnectedUri(uri)) { - logging.debug`Skipping config file '${uri}' for disconnected config`; - return []; - } + // Ignore SSH URIs for connections that are still connecting + // 忽略那些连接仍在进行中的 SSH URI + if (skipDisconnectedUri(uri)) { + // 记录一条调试信息,表明正在跳过配置文件 + logging.debug`Skipping config file '${uri}' for disconnected config`; + // 返回一个空数组,表示没有找到配置 + return []; + } } try { + // 尝试读取配置目录。 return await readConfigDirectory(uri, quiet); } catch { - return await readConfigFile(uri, quiet); + // 如果读取配置目录失败,则捕获错误。 + // 并且尝试读取配置文件来代替。 + return await readConfigFile(uri, quiet); } } @@ -114,6 +227,17 @@ async function findConfigs(uri: vscode.Uri, quiet: boolean): Promise { if (location instanceof vscode.Uri) { return [await findConfigs(location, quiet), true];