From e33cf4b6b572971d8739439004f0c3a6d6aa2add Mon Sep 17 00:00:00 2001 From: yetao Date: Tue, 29 Oct 2024 16:53:32 +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=9AfileSystemConfig.ts=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/fileSystemConfig.ts | 180 +++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 22 deletions(-) diff --git a/common/src/fileSystemConfig.ts b/common/src/fileSystemConfig.ts index d5db4b7..e92d49c 100644 --- a/common/src/fileSystemConfig.ts +++ b/common/src/fileSystemConfig.ts @@ -1,137 +1,262 @@ +// 从 'ssh2' 模块中导入类型 'ConnectConfig'。 +// 'ConnectConfig' 可能是用于配置 SSH 连接的类型,导入这个类型可以在当前模块中使用它进行类型标注等操作。 import type { ConnectConfig } from 'ssh2'; +// 导入当前目录下的 './ssh2' 文件或模块。 +// 这行代码的具体作用取决于 './ssh2' 的内容,可能是加载一个自定义的模块扩展、设置一些全局变量或者执行一些初始化操作。 import './ssh2'; +// 定义一个接口,表示代理配置。 export interface ProxyConfig { + // 代理类型,可以是'socks4'、'socks5'或'http'。 type: 'socks4' | 'socks5' | 'http'; + // 代理服务器的主机地址。 host: string; + // 代理服务器的端口号。 port: number; } +// 定义一个类型别名 ConfigLocation。 +// 这个类型可以是数字或者字符串。 +// 可能用于表示配置项的位置,可以是一个数字索引或者一个字符串标识符等,具体用途取决于使用这个类型的上下文。 export type ConfigLocation = number | string; /** Might support conditional stuff later, although ssh2/OpenSSH might not support that natively */ +// 定义一个接口,表示环境变量。 export interface EnvironmentVariable { + // 环境变量的名称。 key: string; + // 环境变量的值。 value: string; } +// 定义一个名为 formatConfigLocation 的函数,用于格式化配置位置信息。 export function formatConfigLocation(location?: ConfigLocation): string { + // 如果没有传入配置位置,则返回一个表示未知位置的字符串。 if (!location) return 'Unknown location'; + // 如果配置位置是一个数字类型。 if (typeof location === 'number') { + // 根据数字索引返回对应的配置位置描述字符串,否则返回'Unknown'。 return `${[, 'Global', 'Workspace', 'WorkspaceFolder'][location] || 'Unknown'} settings.json`; } + // 如果配置位置是一个字符串类型,则直接返回这个字符串。 return location; } +// 定义一个名为 getLocations 的函数,该函数接受一个 FileSystemConfig 类型的数组作为参数,返回一个 ConfigLocation 类型的数组。 export function getLocations(configs: FileSystemConfig[]): ConfigLocation[] { + // 初始化结果数组,包含全局配置(1)和工作区配置(2),暂不支持工作区文件夹配置(3)。 const res: ConfigLocation[] = [1, 2 /*, 3*/]; // No WorkspaceFolder support (for now) + // TODO: 这里的注释表明未来可能实现的功能,建议在当前工作区文件夹中创建 sshfs.jsonc 等文件(可能是一个用户界面功能)。 // TODO: Suggest creating sshfs.jsonc etc in current workspace folder(s) (UI feature?) + + // 遍历输入的配置数组。 for (const { _location } of configs) { + // 如果当前配置项没有位置信息,则继续下一个配置项。 if (!_location) continue; + // 如果结果数组中不存在当前配置项的位置信息,则将其添加到结果数组中。 if (!res.find(l => l === _location)) { res.push(_location); } } + // 返回包含所有不同位置信息的数组。 return res; } +// 定义一个名为 getGroups 的函数,该函数接受一个 FileSystemConfig 类型的数组和一个布尔值参数 expanded,返回一个字符串数组。 export function getGroups(configs: FileSystemConfig[], expanded = false): string[] { + // 初始化结果数组,用于存储不同的组名。 const res: string[] = []; + // 定义一个内部函数 addGroup,用于将一个组名添加到结果数组中,如果该组名不存在于结果数组中。 function addGroup(group: string) { if (!res.find(l => l === group)) { res.push(group); } } + // 遍历输入的配置数组。 for (const { group } of configs) { + // 如果当前配置项没有组信息,则继续下一个配置项。 if (!group) continue; + // 根据 expanded 参数确定如何处理组名。 const groups = expanded ? group.split('.') : [group]; + // 遍历处理后的组名数组,构建并添加不同层级的组名到结果数组中。 groups.forEach((g, i) => addGroup([...groups.slice(0, i), g].join('.'))); } + // 返回包含所有不同组名的数组。 return res; } +// 定义一个名为 groupByLocation 的函数,该函数接受一个 FileSystemConfig 类型的数组作为参数,返回一个由配置位置和对应的配置数组组成的数组。 export function groupByLocation(configs: FileSystemConfig[]): [ConfigLocation, FileSystemConfig[]][] { + // 初始化结果数组,用于存储配置位置和对应的配置数组。 const res: [ConfigLocation, FileSystemConfig[]][] = []; + // 定义一个内部函数 getForLoc,用于根据给定的配置位置获取对应的配置数组,如果不存在则创建一个新的。 function getForLoc(loc: ConfigLocation = 'Unknown') { + // 在结果数组中查找与给定位置匹配的项。 let found = res.find(([l]) => l === loc); if (found) return found; + // 如果没有找到匹配的项,则创建一个新的项并添加到结果数组中。 found = [loc, []]; res.push(found); return found; } + // 遍历输入的配置数组。 for (const config of configs) { + // 根据当前配置项的位置获取对应的配置数组,并将当前配置项添加到该数组中。 getForLoc(config._location!)[1].push(config); } + // 返回分组后的结果数组。 return res; } +// 定义一个名为 groupByGroup 的函数,该函数接受一个 FileSystemConfig 类型的数组作为参数,返回一个由组名和对应的配置数组组成的数组。 export function groupByGroup(configs: FileSystemConfig[]): [string, FileSystemConfig[]][] { + // 初始化结果数组,用于存储组名和对应的配置数组。 const res: [string, FileSystemConfig[]][] = []; + // 定义一个内部函数 getForGroup,用于根据给定的组名获取对应的配置数组,如果不存在则创建一个新的。 function getForGroup(group: string = '') { + // 在结果数组中查找与给定组名匹配的项。 let found = res.find(([l]) => l === group); if (found) return found; + // 如果没有找到匹配的项,则创建一个新的项并添加到结果数组中。 found = [group, []]; res.push(found); return found; } + // 遍历输入的配置数组。 for (const config of configs) { + // 根据当前配置项的组名获取对应的配置数组,并将当前配置项添加到该数组中。 getForGroup(config.group)[1].push(config); } + // 返回分组后的结果数组。 return res; } +// 定义一个接口,表示文件系统配置,继承自 ConnectConfig。 export interface FileSystemConfig extends ConnectConfig { - /** Name of the config. Can only exists of lowercase alphanumeric characters, slashes and any of these: _.+-@ */ + /** + * Name of the config. Can only exists of lowercase alphanumeric characters, slashes and any of these: _.+-@. + * 配置的名称。只能由小写字母数字字符、斜杠以及 _.+-@ 中的任意字符组成。 + */ name: string; - /** Optional label to display in some UI places (e.g. popups) */ + /** + * Optional label to display in some UI places (e.g. popups). + * 可选的标签,用于在一些用户界面位置(例如弹出窗口)中显示。 + */ label?: string; - /** Optional group for this config, to group configs together in some UI places. Allows subgroups, in the format "Group1.SubGroup1.Subgroup2" */ + /** + * Optional group for this config, to group configs together in some UI places. Allows subgroups, in the format "Group1.SubGroup1.Subgroup2". + * 此配置的可选组,用于在一些用户界面位置中将配置分组。允许子组,格式为"Group1.SubGroup1.Subgroup2"。 + */ group?: string; - /** Whether to merge this "lower" config (e.g. from workspace settings) into higher configs (e.g. from global settings) */ + /** + * Whether to merge this "lower" config (e.g. from workspace settings) into higher configs (e.g. from global settings). + * 是否将此“较低级别”的配置(例如来自工作区设置)合并到更高级别的配置(例如来自全局设置)中。 + */ merge?: boolean; - /** Names of other existing configs to merge into this config. Earlier entries overridden by later entries overridden by this config itself */ + /** + * Names of other existing configs to merge into this config. Earlier entries overridden by later entries overridden by this config itself. + * 要合并到此配置中的其他现有配置的名称。较早的条目被较晚的条目覆盖,较晚的条目又被此配置本身覆盖。 + */ extend?: string | string[]; - /** Path on the remote server that should be opened by default when creating a terminal or using the `Add as Workspace folder` command/button. Defaults to `/` */ + /** + * Path on the remote server that should be opened by default when creating a terminal or using the `Add as Workspace folder` command/button. Defaults to `/`. + * 在创建终端或使用“添加为工作区文件夹”命令/按钮时,应默认打开的远程服务器上的路径。默认为`/`。 + */ root?: string; - /** A name of a PuTTY session, or `true` to find the PuTTY session from the host address */ + /** + * A name of a PuTTY session, or `true` to find the PuTTY session from the host address. + * PuTTY 会话的名称,或者为`true`时从主机地址查找 PuTTY 会话。 + */ putty?: string | boolean; - /** Optional object defining a proxy to use */ + /** + * Optional object defining a proxy to use. + * 定义要使用的代理的可选对象。 + */ proxy?: ProxyConfig; - /** Optional path to a private keyfile to authenticate with */ + /** + * Optional path to a private keyfile to authenticate with. + * 用于身份验证的私钥文件的可选路径。 + */ privateKeyPath?: string; - /** A name of another config to use as a hop */ + /** + * A name of another config to use as a hop. + * 用作跳转的另一个配置的名称。 + */ hop?: string; - /** The command to run on the remote SSH session to start a SFTP session (defaults to sftp subsystem) */ + /** + * The command to run on the remote SSH session to start a SFTP session (defaults to sftp subsystem). + * 在远程 SSH 会话上运行以启动 SFTP 会话的命令(默认为 sftp 子系统)。 + */ sftpCommand?: string; - /** Whether to use a sudo shell (and for which user) to run the sftpCommand in (sftpCommand defaults to /usr/lib/openssh/sftp-server if missing) */ + /** + * Whether to use a sudo shell (and for which user) to run the sftpCommand in (sftpCommand defaults to /usr/lib/openssh/sftp-server if missing). + * 是否使用 sudo 外壳(以及针对哪个用户)来运行 sftpCommand(如果缺少,sftpCommand 默认为 /usr/lib/openssh/sftp-server)。 + */ sftpSudo?: string | boolean; - /** The command(s) to run when a new SSH terminal gets created. Defaults to `$SHELL`. Internally the command `cd ...` is run first */ + /** + * The command(s) to run when a new SSH terminal gets created. Defaults to `$SHELL`. Internally the command `cd...` is run first. + * 当创建新的 SSH 终端时要运行的命令。默认为`$SHELL`。内部首先运行`cd...`命令。 + */ terminalCommand?: string | string[]; - /** The command(s) to run when a `ssh-shell` task gets run. Defaults to the placeholder `$COMMAND`. Internally the command `cd ...` is run first */ + /** + * The command(s) to run when a `ssh-shell` task gets run. Defaults to the placeholder `$COMMAND`. Internally the command `cd...` is run first. + * 当运行`ssh-shell`任务时要运行的命令。默认为占位符`$COMMAND`。内部首先运行`cd...`命令。 + */ taskCommand?: string | string[]; - /** An object with environment variables to add to the SSH connection. Affects the whole connection thus all terminals */ + /** + * An object with environment variables to add to the SSH connection. Affects the whole connection thus all terminals. + * 一个包含要添加到 SSH 连接的环境变量的对象。影响整个连接,因此影响所有终端。 + */ environment?: EnvironmentVariable[] | Record; - /** The filemode to assign to new files created using VS Code, not the terminal. Similar to umask. Defaults to `rw-rw-r--` (regardless of server config, whether you are root, ...) */ + /** + * The filemode to assign to new files created using VS Code, not the terminal. Similar to umask. Defaults to `rw-rw-r--` (regardless of server config, whether you are root,...). + * 分配给使用 VS Code 创建的新文件的文件模式,而不是终端。类似于 umask。默认为`rw-rw-r--`(无论服务器配置如何,无论你是否是 root 用户等)。 + */ newFileMode?: number | string; - /** Whether this config was created from an instant connection string. Enables fuzzy matching for e.g. PuTTY, config-by-host, ... */ + /** + * Whether this config was created from an instant connection string. Enables fuzzy matching for e.g. PuTTY, config-by-host,... + * 此配置是否是从即时连接字符串创建的。例如,为 PuTTY、按主机配置等启用模糊匹配。 + */ instantConnection?: boolean; - /** List of special flags to enable/disable certain fixes/features. Flags are usually used for issues or beta testing. Flags can disappear/change anytime! */ + /** + * List of special flags to enable/disable certain fixes/features. Flags are usually used for issues or beta testing. Flags can disappear/change anytime! + * 用于启用/禁用某些修复/功能的特殊标志列表。标志通常用于问题或测试版。标志可能随时消失/更改! + */ flags?: string[]; - /** Internal property saying where this config comes from. Undefined if this config is merged or something */ + /** + * Internal property saying where this config comes from. Undefined if this config is merged or something. + * 内部属性,表示此配置的来源。如果此配置是合并的或其他情况,则为未定义。 + */ _location?: ConfigLocation; - /** Internal property keeping track of where this config comes from (including merges) */ + /** + * Internal property keeping track of where this config comes from (including merges). + * 内部属性,跟踪此配置的来源(包括合并的情况)。 + */ _locations: ConfigLocation[]; - /** Internal property keeping track of whether this config is an actually calculated one, and if so, which config it originates from (normally itself) */ + /** + * Internal property keeping track of whether this config is an actually calculated one, and if so, which config it originates from (normally itself). + * 内部属性,跟踪此配置是否是实际计算得到的配置,如果是,它源自哪个配置(通常是它自己)。 + */ _calculated?: FileSystemConfig; } +// 定义一个名为 isFileSystemConfig 的函数,用于判断一个对象是否是文件系统配置类型。 export function isFileSystemConfig(config: any): config is FileSystemConfig { - return typeof config === 'object' && typeof config.name === 'string' && Array.isArray(config._locations); + // 首先判断传入的参数是否是一个对象。 + return typeof config === 'object' && + // 然后判断对象是否有一个名为 'name' 的属性且该属性是字符串类型。 + typeof config.name === 'string' && + // 最后判断对象是否有一个名为 '_locations' 的属性且该属性是数组类型。 + Array.isArray(config._locations); } +// 定义一个名为 invalidConfigName 的函数,该函数接受一个字符串参数 name,用于检查 SSH 文件系统配置的名称是否有效。 export function invalidConfigName(name: string) { + // 如果名称为空,返回错误信息表示缺少 SSH 文件系统的名称。 if (!name) return 'Missing a name for this SSH FS'; + // 使用正则表达式检查名称是否符合要求,如果符合则返回 null,表示名称有效。 if (name.match(/^[\w_\\/.@\-+]+$/)) return null; + // 如果名称不符合要求,返回错误信息说明有效的 SSH 文件系统名称的规则。 return `A SSH FS name can only exists of lowercase alphanumeric characters, slashes and any of these: _.+-@`; } @@ -148,17 +273,28 @@ export function invalidConfigName(name: string) { * The resulting FileSystemConfig will have as name basically the input, but without the path. If there is no * username given, the name will start with `@`, as to differentiate between connection strings and config names. */ +// 定义一个名为 CONNECTION_REGEX 的正则表达式,用于匹配 SSH 连接字符串的格式。 const CONNECTION_REGEX = /^((?[\w\-._]+)?(;[\w-]+=[\w\d-]+(,[\w\d-]+=[\w\d-]+)*)?@)?(?[^\s@\\/:,=]+)(:(?\d+))?(?\/\S*)?$/; +// 定义一个名为 parseConnectionString 的函数,用于解析连接字符串,返回一个包含文件系统配置对象和可选路径的数组或者错误信息字符串。 export function parseConnectionString(input: string): [config: FileSystemConfig, path?: string] | string { + // 去除输入字符串两端的空白字符。 input = input.trim(); + // 使用 CONNECTION_REGEX 正则表达式对输入字符串进行匹配。 const match = input.match(CONNECTION_REGEX); + // 如果没有匹配结果,返回错误信息表示输入格式无效,期望的格式类似于 "user@example.com:22/some/path"。 if (!match) return 'Invalid format, expected something like "user@example.com:22/some/path"'; + // 从匹配结果的 groups 中解构出用户、主机和路径。 const { user, host, path } = match.groups!; + // 获取端口字符串。 const portStr = match.groups!.port; + // 如果端口字符串存在,将其转换为数字类型的端口号;否则端口号为 undefined。 const port = portStr ? Number.parseInt(portStr) : undefined; + // 如果端口字符串存在且端口号无效(小于 1 或大于 65535),返回错误信息表示端口号无效。 if (portStr && (!port || port < 1 || port > 65535)) return `The string '${port}' is not a valid port number`; + // 根据用户、主机、端口和路径生成配置的名称。 const name = `${user || ''}@${host}${port ? `:${port}` : ''}${path || ''}`; + // 返回包含文件系统配置对象和可选路径的数组,配置对象包含名称、主机、端口、设置了 instantConnection 为 true、用户名(如果用户为空则为 '$USERNAME')以及初始的 _locations 为空数组。 return [{ name, host, port, instantConnection: true,