refactor🎨: (阅读代码):mananger.ts关联文件增加注释

master
yetao 3 weeks ago
parent c3737330b7
commit 9266c7dcae

@ -454,27 +454,53 @@ export async function deleteConfig(config: FileSystemConfig) {
* Otherwise, if it contains a `@`, we parse it as a connection string. * Otherwise, if it contains a `@`, we parse it as a connection string.
* If this results in no (valid) configuration, `undefined` is returned. * If this results in no (valid) configuration, `undefined` is returned.
*/ */
/**
*
*
* '@'
*
*
*
* @param input -
* @returns undefined
*/
export function getConfig(input: string): FileSystemConfig | undefined { export function getConfig(input: string): FileSystemConfig | undefined {
// 将输入字符串转换为小写,以便进行不区分大小写的比较
const lower = input.toLowerCase(); const lower = input.toLowerCase();
// 查找已加载的配置中是否存在名称与输入字符串匹配的配置
const loaded = getConfigs().find(c => c.name.toLowerCase() === lower); const loaded = getConfigs().find(c => c.name.toLowerCase() === lower);
// 如果找到匹配的配置,则直接返回该配置
if (loaded) return loaded; if (loaded) return loaded;
// 如果输入字符串不包含 '@' 符号,则返回 undefined
if (!input.includes('@')) return undefined; if (!input.includes('@')) return undefined;
// 尝试解析输入字符串为连接字符串
const parseString = parseConnectionString(input); const parseString = parseConnectionString(input);
// 如果解析失败(返回类型为字符串),则返回 undefined
if (typeof parseString === 'string') return undefined; if (typeof parseString === 'string') return undefined;
// 从解析结果中提取第一个配置对象
const [parsed] = parseString; const [parsed] = parseString;
// If we're using the instant connection string, the host name might be a config name // If we're using the instant connection string, the host name might be a config name
// 如果我们使用的是即时连接字符串,主机名可能是一个配置名称,我们需要将其替换为会话名称,并标记该配置为合并配置
const existing = getConfigs().find(c => c.name.toLowerCase() === parsed.host!.toLowerCase()); const existing = getConfigs().find(c => c.name.toLowerCase() === parsed.host!.toLowerCase());
// 如果找到匹配的配置
if (existing) { if (existing) {
// 记录日志,说明 getConfig 函数的调用导致了一个配置名称与现有配置名称相匹配
Logging.info`getConfig('${input}') led to '${parsed.name}' which matches config '${existing.name}'`; Logging.info`getConfig('${input}') led to '${parsed.name}' which matches config '${existing.name}'`;
// Take the existing config, but (more or less) override it with the values present in `parsed` // Take the existing config, but (more or less) override it with the values present in `parsed`
// `name` be the same as in `parsed`, meaning it can be reused with `getConfig` on window reload. // `name` be the same as in `parsed`, meaning it can be reused with `getConfig` on window reload.
// 合并现有配置和解析后的配置,优先使用解析后的配置中的值
return { return {
// 复制现有配置的所有属性,复制解析后的配置的所有属性
...existing, ...parsed, ...existing, ...parsed,
// 如果现有配置中存在 host 属性,则使用现有配置的 host 属性,否则使用解析后的配置的 host 属性
host: existing.host || parsed.host, // `parsed.host` is the session name, which might not be the actual hostname host: existing.host || parsed.host, // `parsed.host` is the session name, which might not be the actual hostname
// 由于这是一个合并配置,我们将 _location 属性标记为 undefined
_location: undefined, // Since this is a merged config, we have to flag it as such _location: undefined, // Since this is a merged config, we have to flag it as such
// 合并现有配置和解析后的配置的 _locations 属性
_locations: [...existing._locations, ...parsed._locations], // Merge locations _locations: [...existing._locations, ...parsed._locations], // Merge locations
}; };
} }
// 如果没有找到匹配的配置,则直接返回解析后的配置
return parsed; return parsed;
} }

@ -11,20 +11,35 @@ import { calculateShellConfig, KNOWN_SHELL_CONFIGS, ShellConfig, tryCommand, try
import type { SSHFileSystem } from './sshFileSystem'; import type { SSHFileSystem } from './sshFileSystem';
import { mergeEnvironment, toPromise } from './utils'; import { mergeEnvironment, toPromise } from './utils';
/**
* SSH
*/
export interface Connection { export interface Connection {
// 文件系统的配置。
config: FileSystemConfig; config: FileSystemConfig;
// 实际使用的文件系统配置。
actualConfig: FileSystemConfig; actualConfig: FileSystemConfig;
// SSH 客户端实例。
client: Client; client: Client;
// 用户的主目录。
home: string; home: string;
// Shell 配置。
shellConfig: ShellConfig; shellConfig: ShellConfig;
// 环境变量。
environment: EnvironmentVariable[]; environment: EnvironmentVariable[];
// 伪终端列表
terminals: SSHPseudoTerminal[]; terminals: SSHPseudoTerminal[];
// 文件系统列表。
filesystems: SSHFileSystem[]; filesystems: SSHFileSystem[];
// 缓存数据。
cache: Record<string, any>; cache: Record<string, any>;
// 等待连接的用户数量。
pendingUserCount: number; pendingUserCount: number;
//空闲计时器。
idleTimer: NodeJS.Timeout; idleTimer: NodeJS.Timeout;
} }
export class ConnectionManager { export class ConnectionManager {
protected onConnectionAddedEmitter = new vscode.EventEmitter<Connection>(); protected onConnectionAddedEmitter = new vscode.EventEmitter<Connection>();
protected onConnectionRemovedEmitter = new vscode.EventEmitter<Connection>(); protected onConnectionRemovedEmitter = new vscode.EventEmitter<Connection>();

@ -77,6 +77,35 @@ export let MANAGER: Manager | undefined;
* *
* @param context - * @param context -
*/ */
/**
* @startuml
* start
* :;
* if () then ()
* : _location _locations ;
* if () then ()
* :;
* :;
* else ()
* :;
* :;
* endif
* else ()
* :;
* :;
* :;
* if () then ()
* :;
* :;
* :;
* else ()
* :使;
* :;
* endif
* endif
* stop
* @enduml
*/
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
// 通过 VS Code API 获取名为 'Kelvin.vscode-sshfs' 的扩展实例 // 通过 VS Code API 获取名为 'Kelvin.vscode-sshfs' 的扩展实例
const extension = vscode.extensions.getExtension('Kelvin.vscode-sshfs'); const extension = vscode.extensions.getExtension('Kelvin.vscode-sshfs');
@ -132,7 +161,26 @@ export function activate(context: vscode.ExtensionContext) {
// I really don't like having to pass context to *everything*, so let's do it this way // I really don't like having to pass context to *everything*, so let's do it this way
// 真的很糟糕我们确实需要扩展上下文ExtensionContext来获取相对资源。 // 真的很糟糕我们确实需要扩展上下文ExtensionContext来获取相对资源。
// 我真的不喜欢必须将上下文传递给所有东西,所以让我们这样做吧。 // 我真的不喜欢必须将上下文传递给所有东西,所以让我们这样做吧。
// 将 context.asAbsolutePath 方法绑定到当前上下文中,并赋值给 setAsAbsolutePath 变量 // 将 context.asAbsolutePath 方法绑定到当前上下文中,并赋值给 asAbsolutePath 变量
// 这样,我们就可以在代码中使用 asAbsolutePath 变量来获取相对路径的绝对路径
/** Function bind() this
*
*
* const module = {
* x: 42,
* getX: function () {
* return this.x;
* },
* };
*
* const unboundGetX = module.getX;
* console.log(unboundGetX()); // The function gets invoked at the global scope
* // Expected output: undefined
*
* const boundGetX = unboundGetX.bind(module);
* console.log(boundGetX());
* // Expected output: 42
*/
setAsAbsolutePath(context.asAbsolutePath.bind(context)); setAsAbsolutePath(context.asAbsolutePath.bind(context));
// 创建一个 Manager 实例,并将其赋值给全局变量 MANAGER // 创建一个 Manager 实例,并将其赋值给全局变量 MANAGER

@ -107,6 +107,74 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param {FileSystemConfig} config - * @param {FileSystemConfig} config -
* @returns {Promise<SSHFileSystem>} - Promise * @returns {Promise<SSHFileSystem>} - Promise
*/ */
/**
* @startuml
* class SSHFileSystemManager {
* + createFileSystem(name: string, config?: FileSystemConfig): Promise<SSHFileSystem>
* + createTerminal(name: string, config?: FileSystemConfig | Connection, uri?: vscode.Uri): Promise<void>
* + getActiveFileSystems(): readonly SSHFileSystem[]
* + getFs(uri: vscode.Uri): SSHFileSystem | null
* + promptReconnect(name: string): Promise<void>
* }
* class SSHFileSystem {
* + onClose(handler: () => void): void
* }
* class Connection {
* + actualConfig: FileSystemConfig
* + filesystems: SSHFileSystem[]
* + pendingUserCount: number
* + home: string
* + client: any
* }
* class FileSystemConfig {
* + flags: any
* }
* SSHFileSystemManager --> Connection : uses
* SSHFileSystemManager --> SSHFileSystem : creates
* SSHFileSystem --> Connection : belongs to
* Connection --> FileSystemConfig : has
* @enduml
*/
/**
* @startuml
* start
* :;
* if ( SSHFileSystem ?) then (yes)
* :;
* else (no)
* : con Connection ;
* : creatingFileSystems Promise Promise;
* :;
* if (?) then (yes)
* :;
* else (no)
* :;
* :;
* :;
* : getSFTP SSHFileSystem ;
* :使 SFTP ;
* : SSH ;
* : SSH ;
* :;
* : fileSystems ;
* : creatingFileSystems Promise;
* : fileSystems ;
* :;
* :;
* if (访?) then (yes)
* : Promise;
* else (no)
* :show error message and options;
* if (?) then (yes)
* : Promise ;
* else (no)
* : Promise;
* endif
* endif
* endif
* stop
* @enduml
*/
public async createFileSystem(name: string, config?: FileSystemConfig): Promise<SSHFileSystem> { public async createFileSystem(name: string, config?: FileSystemConfig): Promise<SSHFileSystem> {
await LOADING_CONFIGS; // Prevent race condition on startup, and wait for any current config reload to finish 防止启动时的竞态条件,并等待当前配置重载完成 await LOADING_CONFIGS; // Prevent race condition on startup, and wait for any current config reload to finish 防止启动时的竞态条件,并等待当前配置重载完成
// 检查是否已经存在一个具有指定名称的 SSHFileSystem 实例 // 检查是否已经存在一个具有指定名称的 SSHFileSystem 实例
@ -117,7 +185,7 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
let con: Connection | undefined; let con: Connection | undefined;
// 尝试从 creatingFileSystems 中获取已存在的 Promise如果不存在则创建一个新的 Promise // 尝试从 creatingFileSystems 中获取已存在的 Promise如果不存在则创建一个新的 Promise
return this.creatingFileSystems[name] ||= catchingPromise<SSHFileSystem>(async (resolve, reject) => { return this.creatingFileSystems[name] ||= catchingPromise<SSHFileSystem>(async (resolve, reject) => {
// 如果没有提供配置,则从默认配置中获取 // 获取文件系统配置,如果没有提供配置,则尝试从配置管理器中获取
config ||= getConfig(name); config ||= getConfig(name);
// 如果没有找到配置,则抛出错误 // 如果没有找到配置,则抛出错误
if (!config) throw new Error(`Couldn't find a configuration with the name '${name}'`); if (!config) throw new Error(`Couldn't find a configuration with the name '${name}'`);
@ -221,6 +289,21 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param {vscode.Uri} uri - URI * @param {vscode.Uri} uri - URI
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
/**
* @startuml
* start
* : createTerminal ;
* :;
* :pendingUserCount 1;
* :;
* :;
* :pendingUserCount 1;
* :;
* :;
* :;
* end
* @enduml
*/
public async createTerminal(name: string, config?: FileSystemConfig | Connection, uri?: vscode.Uri): Promise<void> { public async createTerminal(name: string, config?: FileSystemConfig | Connection, uri?: vscode.Uri): Promise<void> {
const { createTerminal } = await import('./pseudoTerminal'); const { createTerminal } = await import('./pseudoTerminal');
// Create connection (early so we have .actualConfig.root) // Create connection (early so we have .actualConfig.root)
@ -421,6 +504,24 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param {FileSystemConfig} config - * @param {FileSystemConfig} config -
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
/**
* @startuml
* start
* :;
* :;
* : SSH ;
* if () then ()
* :;
* :;
* else ()
* : '/';
* : '~' ;
* : '/' '/';
* : SSH ;
* endif
* stop
* @enduml
*/
public async commandConnect(config: FileSystemConfig) { public async commandConnect(config: FileSystemConfig) {
// 记录接收到的连接命令信息 // 记录接收到的连接命令信息
Logging.info`Command received to connect ${config.name}`; Logging.info`Command received to connect ${config.name}`;
@ -452,6 +553,27 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param target - * @param target -
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
/**
* @startuml
* start
* :;
* :;
* if ( 'client' ) then ()
* :;
* :;
* else ()
* :;
* endif
* : closeConnection ;
* :;
* if () then ()
* :;
* else ()
* :;
* endif
* stop
* @enduml
*/
public commandDisconnect(target: string | Connection) { public commandDisconnect(target: string | Connection) {
// 记录接收到的断开连接命令信息 // 记录接收到的断开连接命令信息
Logging.info`Command received to disconnect ${commandArgumentToName(target)}`; Logging.info`Command received to disconnect ${commandArgumentToName(target)}`;
@ -503,6 +625,24 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param uri - URI * @param uri - URI
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
/**
* @startuml
* start
* :;
* :;
* : SSH ;
* if () then ()
* :;
* :;
* else ()
* : '/';
* : '~' ;
* : '/' '/';
* : SSH ;
* endif
* stop
* @enduml
*/
public async commandTerminal(target: FileSystemConfig | Connection, uri?: vscode.Uri) { public async commandTerminal(target: FileSystemConfig | Connection, uri?: vscode.Uri) {
// 记录接收到的打开终端命令信息 // 记录接收到的打开终端命令信息
Logging.info`Command received to open a terminal for ${commandArgumentToName(target)}${uri ? ` in ${uri}` : ''}`; Logging.info`Command received to open a terminal for ${commandArgumentToName(target)}${uri ? ` in ${uri}` : ''}`;
@ -527,6 +667,35 @@ export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider
* @param target - * @param target -
* @returns {Promise<void>} - Promise * @returns {Promise<void>} - Promise
*/ */
/**
* @startuml
* start
* :;
* if () then ()
* : _location _locations ;
* if () then ()
* :;
* stop
* else ()
* :;
* stop
* endif
* else ()
* :;
* :;
* :;
* if () then ()
* :;
* :;
* :;
* else ()
* :使;
* :;
* endif
* endif
* stop
* @enduml
*/
public async commandConfigure(target: string | FileSystemConfig) { public async commandConfigure(target: string | FileSystemConfig) {
// 记录接收到的配置命令信息 // 记录接收到的配置命令信息
Logging.info`Command received to configure ${typeof target === 'string' ? target : target.name}`; Logging.info`Command received to configure ${typeof target === 'string' ? target : target.name}`;

@ -17,40 +17,67 @@ function trimError(error: Error, depth: number): [string[], Error] {
Error.prepareStackTrace = pst; Error.prepareStackTrace = pst;
return [trimmed.split('\n').slice(1), error]; return [trimmed.split('\n').slice(1), error];
} }
/**
* Promise
* @param executor - resolve reject
* @param trimStack -
* @param causeName -
* @returns Promise
*/
/** Wrapper around async callback-based functions */ /** Wrapper around async callback-based functions */
export async function catchingPromise<T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => any, trimStack = 0, causeName = 'catchingPromise'): Promise<T> { export async function catchingPromise<T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => any, trimStack = 0, causeName = 'catchingPromise'): Promise<T> {
// 声明一个包含两个元素的数组,其中第一个元素是一个字符串数组,第二个元素是一个错误对象
let [trimmed, promiseCause]: [string[], Error] = [] as any; let [trimmed, promiseCause]: [string[], Error] = [] as any;
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
// 使用 trimError 函数处理新创建的错误对象,并设置要修剪的堆栈帧数
[trimmed, promiseCause] = trimError(new Error(), trimStack + 2); [trimmed, promiseCause] = trimError(new Error(), trimStack + 2);
// 如果 DEBUG 模式开启,则修改 promiseCause 的堆栈跟踪信息
if (DEBUG) promiseCause.stack = promiseCause.stack!.split('\n', 2)[0] + trimmed.map(l => l.replace('at', '~at')).join('\n') + '\n' + promiseCause.stack!.split('\n').slice(1).join('\n'); if (DEBUG) promiseCause.stack = promiseCause.stack!.split('\n', 2)[0] + trimmed.map(l => l.replace('at', '~at')).join('\n') + '\n' + promiseCause.stack!.split('\n').slice(1).join('\n');
try { try {
// 执行传入的执行器函数,并获取其返回的 Promise
const p = executor(resolve, reject); const p = executor(resolve, reject);
// 如果返回的是 Promise则捕获其拒绝事件
if (p instanceof Promise) { if (p instanceof Promise) {
p.catch(reject); p.catch(reject);
} }
} catch (e) { } catch (e) {
// 如果执行器函数抛出异常,则拒绝 Promise并将错误对象传递给 reject 函数
reject(e); reject(e);
} }
}).catch(e => { }).catch(e => {
// 检查捕获的异常是否为 Error 类型的实例
if (e instanceof Error) { if (e instanceof Error) {
// 获取异常的堆栈跟踪信息
let stack = e.stack; let stack = e.stack;
// 如果堆栈跟踪信息存在
if (stack) { if (stack) {
// 将堆栈跟踪信息分割成行
const lines = stack.split('\n'); const lines = stack.split('\n');
// 查找修剪后的堆栈跟踪信息的第三行在原始堆栈跟踪信息中的索引
let index = lines.indexOf(trimmed[3]); let index = lines.indexOf(trimmed[3]);
// 如果找到了
if (index !== -1) { if (index !== -1) {
// 计算新的索引,减去 2 是因为要跳过前两行trimStack 是用户指定的修剪帧数
index -= 2 + trimStack; index -= 2 + trimStack;
// 更新异常的堆栈跟踪信息,只保留前 index 行
e.stack = lines[0] + '\n' + lines.slice(1, index).join('\n'); e.stack = lines[0] + '\n' + lines.slice(1, index).join('\n');
// 如果 DEBUG 模式开启,则在堆栈跟踪信息后面添加修剪后的堆栈跟踪信息,并将 'at' 替换为 '~at'
if (DEBUG) e.stack += '\n' + lines.slice(index).map(l => l.replace('at', '~at')).join('\n'); if (DEBUG) e.stack += '\n' + lines.slice(index).map(l => l.replace('at', '~at')).join('\n');
} }
} }
// 尝试获取异常对象的 promiseCause 属性
let t = (e as any).promiseCause; let t = (e as any).promiseCause;
// 如果 promiseCause 属性不存在或者不是 Error 类型
if (!(t instanceof Error)) t = e; if (!(t instanceof Error)) t = e;
// 如果异常对象没有 promiseCause 属性
if (!('promiseCause' in t)) { if (!('promiseCause' in t)) {
// 为异常对象定义一个 promiseCause 属性,其值为 promiseCause 变量的值
Object.defineProperty(t, 'promiseCause', { Object.defineProperty(t, 'promiseCause', {
value: promiseCause, value: promiseCause,
configurable: true, configurable: true,
enumerable: false, enumerable: false,
}); });
// 为异常对象定义一个 promiseCauseName 属性,其值为 causeName 变量的值
Object.defineProperty(t, 'promiseCauseName', { Object.defineProperty(t, 'promiseCauseName', {
value: causeName, value: causeName,
configurable: true, configurable: true,

Loading…
Cancel
Save