|
|
|
@ -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<void>} 当所有配置都被检查并可能重命名后解决的 Promise。
|
|
|
|
|
*/
|
|
|
|
|
export async function renameNameless() {
|
|
|
|
|
// 获取 sshfs 配置对象
|
|
|
|
|
const conf = vscode.workspace.getConfiguration('sshfs');
|
|
|
|
|
// 检查 configs 配置项,获取其全局、工作区和工作区文件夹级别的值
|
|
|
|
|
const inspect = conf.inspect<FileSystemConfig[]>('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<void>} - 当配置更新完成时解决的 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<FileSystemConfig[] | undefined> {
|
|
|
|
|
// 尝试读取文件内容,如果失败则返回错误
|
|
|
|
|
const content = await fs.readFile(file).then<Uint8Array | NodeJS.ErrnoException>(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<FileSystemConfig[] | undefined> {
|
|
|
|
|
// 获取目录的状态,如果失败则返回 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<FileSystemConfig[] | undefined> {
|
|
|
|
|
// 检查 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<FileSystemC
|
|
|
|
|
* Will return `undefined` if the given file doesn't exist, or lead to a directory with no readable config files.
|
|
|
|
|
* Will return an empty array if the given path is a relative path.
|
|
|
|
|
*/
|
|
|
|
|
/**
|
|
|
|
|
* 尝试从指定的位置读取所有配置,该位置可以是文件或目录。
|
|
|
|
|
* 如果位置指向一个文件,它将尝试读取该文件。如果位置指向一个目录,它将尝试读取该目录下的所有配置文件。
|
|
|
|
|
* 此函数会报告错误给用户/日志记录器,并且永远不会拒绝。可能会返回一个空数组。
|
|
|
|
|
* 当给定一个目录路径时,此函数可能会读取多个文件,并将结果聚合在一起。
|
|
|
|
|
* 如果给定的文件不存在,或者指向一个目录但没有可读的配置文件,此函数将返回 undefined。
|
|
|
|
|
* 如果给定的路径是相对路径,此函数将返回一个空数组。
|
|
|
|
|
* @param location - 要读取的配置文件或目录的位置,可以是字符串或 vscode.Uri。
|
|
|
|
|
* @param quiet - 如果为 true,则在文件不存在时不记录错误。
|
|
|
|
|
* @returns 一个包含解析后的文件系统配置数组和一个布尔值的数组,表示位置是否是绝对路径。
|
|
|
|
|
*/
|
|
|
|
|
async function findConfigFiles(location: string | vscode.Uri, quiet = false): Promise<[configs: FileSystemConfig[] | undefined, isAbsolute: boolean]> {
|
|
|
|
|
if (location instanceof vscode.Uri) {
|
|
|
|
|
return [await findConfigs(location, quiet), true];
|
|
|
|
|