refactor🎨: (阅读代码):connection.ts增加注释

master
yetao 3 weeks ago
parent ca4fc9a03b
commit 77a806ec6c

@ -1,14 +1,26 @@
// 导入 common/fileSystemConfig 模块中的 EnvironmentVariable 和 FileSystemConfig 类型
import type { EnvironmentVariable, FileSystemConfig } from 'common/fileSystemConfig';
// 导入 path 模块的 posix 版本,用于处理路径
import { posix as path } from 'path';
// 导入 readline 模块,用于处理逐行读取数据
import * as readline from 'readline';
// 导入 ssh2 模块中的 Client 和 ClientChannel 类型
import type { Client, ClientChannel } from 'ssh2';
// 导入 vscode 模块,用于 VSCode 插件开发
import * as vscode from 'vscode';
// 导入 config 模块中的 configMatches 和 loadConfigs 函数
import { configMatches, loadConfigs } from './config';
// 导入 flags 模块中的 getFlag 和 getFlagBoolean 函数
import { getFlag, getFlagBoolean } from './flags';
// 导入 logging 模块中的 Logging 和 LOGGING_NO_STACKTRACE 常量
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
// 导入 pseudoTerminal 模块中的 SSHPseudoTerminal 类型
import type { SSHPseudoTerminal } from './pseudoTerminal';
// 导入 shellConfig 模块中的 calculateShellConfig、KNOWN_SHELL_CONFIGS、ShellConfig、tryCommand 和 tryEcho 函数
import { calculateShellConfig, KNOWN_SHELL_CONFIGS, ShellConfig, tryCommand, tryEcho } from './shellConfig';
// 导入 sshFileSystem 模块中的 SSHFileSystem 类型
import type { SSHFileSystem } from './sshFileSystem';
// 导入 utils 模块中的 mergeEnvironment 和 toPromise 函数
import { mergeEnvironment, toPromise } from './utils';
/**
@ -41,95 +53,183 @@ export interface Connection {
export class ConnectionManager {
// 定义事件发射器,用于在连接添加时触发事件
protected onConnectionAddedEmitter = new vscode.EventEmitter<Connection>();
// 定义事件发射器,用于在连接移除时触发事件
protected onConnectionRemovedEmitter = new vscode.EventEmitter<Connection>();
// 定义事件发射器,用于在连接更新时触发事件
protected onConnectionUpdatedEmitter = new vscode.EventEmitter<Connection>();
// 定义事件发射器,用于在待处理连接状态改变时触发事件
protected onPendingChangedEmitter = new vscode.EventEmitter<void>();
// 存储当前已建立的连接
protected connections: Connection[] = [];
// 存储待处理的连接,每个连接对应一个 Promise 和一个可选的 FileSystemConfig
protected pendingConnections: { [name: string]: [Promise<Connection>, FileSystemConfig | undefined] } = {};
/** Fired when a connection got added (and finished connecting) */
// 当有连接被添加(并且完成连接)时触发的事件
public readonly onConnectionAdded = this.onConnectionAddedEmitter.event;
/** Fired when a connection got removed */
// 当有连接被移除时触发的事件
public readonly onConnectionRemoved = this.onConnectionRemovedEmitter.event;
/** Fired when a connection got updated (terminal added/removed, ...) */
// 当有连接被更新(终端添加/移除等)时触发的事件
public readonly onConnectionUpdated = this.onConnectionUpdatedEmitter.event;
/** Fired when a pending connection gets added/removed */
// 当待处理连接状态改变时触发的事件
public readonly onPendingChanged = this.onPendingChangedEmitter.event;
/**
*
* @param name
* @param config
* @returns undefined
*/
public getActiveConnection(name: string, config?: FileSystemConfig): Connection | undefined {
// 如果提供了配置,则使用 configMatches 函数查找匹配的连接
if (config) return this.connections.find(con => configMatches(con.config, config));
// 将名称转换为小写,以便进行不区分大小写的比较
name = name.toLowerCase();
// 在连接列表中查找名称匹配的连接
return this.connections.find(con => con.config.name === name);
}
/**
*
* @returns
*/
public getActiveConnections(): Connection[] {
// 使用扩展运算符将 this.connections 数组展开并复制到一个新数组中,以避免直接返回原数组的引用
return [...this.connections];
}
/**
*
* @returns
*/
public getPendingConnections(): [string, FileSystemConfig | undefined][] {
// 使用 Object.keys 方法获取 this.pendingConnections 对象的所有键,即待处理连接的名称
return Object.keys(this.pendingConnections).map(name => [name, this.pendingConnections[name][1]]);
}
/**
*
* @param client SSH
* @param shellConfig shell
* @param authority
* @param debugLogging
* @returns
*/
protected async _createCommandTerminal(client: Client, shellConfig: ShellConfig, authority: string, debugLogging: boolean): Promise<string> {
// 使用 authority 参数创建一个日志记录器,用于记录命令终端的相关信息
const logging = Logging.scope(`CmdTerm(${authority})`);
// 检查 shellConfig 对象是否支持嵌入替换,如果不支持则抛出错误
if (!shellConfig.embedSubstitutions) throw new Error(`Shell '${shellConfig.shell}' does not support embedding substitutions`);
// 使用 client.shell 方法创建一个 shell 通道,并等待其完成
const shell = await toPromise<ClientChannel>(cb => client.shell({}, cb));
// 记录调试信息,显示即将发送的 TTY 命令
logging.debug(`TTY COMMAND: ${`echo ${shellConfig.embedSubstitutions`::sshfs:${'echo TTY'}:${'tty'}`}\n`}`);
// 将 TTY 命令发送到 shell 通道中
shell.write(`echo ${shellConfig.embedSubstitutions`::sshfs:${'echo TTY'}:${'tty'}`}\n`);
// 创建一个 Promise用于处理命令终端返回的路径
return new Promise((resolvePath, rejectPath) => {
// 设置一个超时时间,如果在 10 秒内没有获取到命令路径,则抛出错误
setTimeout(() => rejectPath(new Error('Timeout fetching command path')), 10e3);
// 使用 readline 模块创建一个接口,用于读取命令终端的输出
const rl = readline.createInterface(shell);
// 监听 shell 的 error 事件,如果发生错误,则拒绝 Promise
shell.once('error', rejectPath);
// 监听 shell 的 close 事件,如果 shell 关闭,则拒绝 Promise
shell.once('close', () => rejectPath());
// 监听 readline 接口的 'line' 事件,当接收到一行数据时,执行回调函数
rl.on('line', async line => {
// 如果启用了调试日志,则记录接收到的行数据
if (debugLogging) logging.debug('<< ' + line);
// 使用正则表达式匹配接收到的行数据,提取命令和参数
const [, prefix, cmd, args] = line.match(/(.*?)::sshfs:(\w+):(.*)$/) || [];
// 如果没有匹配到命令或前缀以 echo 结尾,则忽略该行数据
if (!cmd || prefix.endsWith('echo ')) return;
// 根据命令类型进行不同的处理
switch (cmd) {
// 如果接收到的命令是 TTY则记录日志并解析出 TTY 路径
case 'TTY':
// 记录接收到 TTY 路径的信息
logging.info('Got TTY path: ' + args);
// 解析出的 TTY 路径作为参数,调用 resolvePath 方法来解决 Promise
resolvePath(args);
// 跳出 switch 语句
break;
// 如果接收到的命令是 code则解析命令参数并执行相应操作
case 'code':
// 使用 ::: 分割 args获取 pwd 和 target
let [pwd, target] = args.split(':::');
// 如果 pwd 或 target 为空,则记录错误日志并返回
if (!pwd || !target) {
logging.error`Malformed 'code' command args: ${args}`;
return;
}
// 去除 pwd 和 target 前后的空格
pwd = pwd.trim();
target = target.trim();
// 记录接收到的打开文件命令的信息
logging.info`Received command to open '${target}' while in '${pwd}'`;
// 如果 target 以 / 开头,则 absolutePath 为 target否则为 pwd 和 target 的拼接
const absolutePath = target.startsWith('/') ? target : path.join(pwd, target);
// 使用 authority 和 absolutePath 构建 URI
const uri = vscode.Uri.parse(`ssh://${authority}/${absolutePath}`);
try {
// 获取 URI 的状态
const stat = await vscode.workspace.fs.stat(uri);
// 如果是目录,则更新工作区文件夹
if (stat.type & vscode.FileType.Directory) {
await vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders?.length || 0, 0, { uri });
} else {
// 如果是文件,则显示文件
await vscode.window.showTextDocument(uri);
}
} catch (e) {
// 如果是文件系统错误
if (e instanceof vscode.FileSystemError) {
// 如果是文件未找到错误
if (e.code === 'FileNotFound') {
// 记录文件未找到的警告日志
logging.warning(`File '${absolutePath}' not found, prompting to create empty file`);
// 显示文件未找到的警告消息,并提供创建文件的选项
const choice = await vscode.window.showWarningMessage(`File '${absolutePath}' not found, create it?`, { modal: true }, 'Yes');
// 如果用户选择不创建文件,则返回
if (choice !== 'Yes') return;
try { await vscode.workspace.fs.writeFile(uri, Buffer.of()); } catch (e) {
try {
// 尝试创建空文件
await vscode.workspace.fs.writeFile(uri, Buffer.of());
} catch (e) {
// 记录创建文件失败的错误日志
logging.error(e);
// 显示创建文件失败的错误消息
vscode.window.showErrorMessage(`Failed to create an empty file at '${absolutePath}'`);
return;
}
// 显示创建的文件
await vscode.window.showTextDocument(uri);
return;
}
// 显示其他文件系统错误
vscode.window.showErrorMessage(`Error opening ${absolutePath}: ${e.name.replace(/ \(FileSystemError\)/g, '')}`);
} else {
// 显示其他错误
vscode.window.showErrorMessage(`Error opening ${absolutePath}: ${e.message || e}`);
}
}
return;
// 如果接收到的命令不是 TTY 或 code则记录错误日志
default:
// 记录接收到未知命令的错误信息
logging.error`Unrecognized command ${cmd} with args: ${args}`;
}
});
})
}
/**
*
* @param name
* @param config
* @returns Promise
*/
protected async _createConnection(name: string, config?: FileSystemConfig): Promise<Connection> {
const logging = Logging.scope(`createConnection(${name},${config ? 'config' : 'undefined'})`);
logging.info`Creating a new connection for '${name}'`;

Loading…
Cancel
Save