|
|
|
@ -1,84 +1,171 @@
|
|
|
|
|
|
|
|
|
|
// 导入 common/fileSystemConfig 模块中的 FileSystemConfig 类型和 getGroups 函数
|
|
|
|
|
import { FileSystemConfig, getGroups } from 'common/fileSystemConfig';
|
|
|
|
|
// 导入 vscode 模块
|
|
|
|
|
import * as vscode from 'vscode';
|
|
|
|
|
// 从./config 模块中导入 getConfigs 函数和 UPDATE_LISTENERS 数组
|
|
|
|
|
import { getConfigs, UPDATE_LISTENERS } from './config';
|
|
|
|
|
// 从./connection 模块中导入 Connection 和 ConnectionManager 类型
|
|
|
|
|
import type { Connection, ConnectionManager } from './connection';
|
|
|
|
|
// 从./pseudoTerminal 模块中导入 SSHPseudoTerminal 类型
|
|
|
|
|
import type { SSHPseudoTerminal } from './pseudoTerminal';
|
|
|
|
|
// 从./sshFileSystem 模块中导入 SSHFileSystem 类型
|
|
|
|
|
import type { SSHFileSystem } from './sshFileSystem';
|
|
|
|
|
// 从./ui-utils 模块中导入 formatItem 函数
|
|
|
|
|
import { formatItem } from './ui-utils';
|
|
|
|
|
|
|
|
|
|
// 表示一个待处理的连接,包含连接的名称和文件系统配置(可选)。
|
|
|
|
|
type PendingConnection = [string, FileSystemConfig | undefined];
|
|
|
|
|
|
|
|
|
|
// 表示树形视图中的数据项,可以是连接、待处理的连接、SSH 文件系统或 SSH 伪终端。
|
|
|
|
|
type TreeData = Connection | PendingConnection | SSHFileSystem | SSHPseudoTerminal;
|
|
|
|
|
|
|
|
|
|
// 表示一个树形视图的数据提供器,用于管理连接、待处理的连接、SSH 文件系统和 SSH 伪终端。
|
|
|
|
|
export class ConnectionTreeProvider implements vscode.TreeDataProvider<TreeData> {
|
|
|
|
|
// 定义一个事件发射器,用于通知树形视图数据发生变化
|
|
|
|
|
protected onDidChangeTreeDataEmitter = new vscode.EventEmitter<TreeData | void>();
|
|
|
|
|
// 定义一个事件,当树形视图数据发生变化时触发
|
|
|
|
|
public onDidChangeTreeData: vscode.Event<TreeData | void> = this.onDidChangeTreeDataEmitter.event;
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数,初始化连接管理器。
|
|
|
|
|
* @param manager - 连接管理器实例。
|
|
|
|
|
*/
|
|
|
|
|
constructor(protected readonly manager: ConnectionManager) {
|
|
|
|
|
// 监听连接添加事件,当有新连接添加时,触发树形视图数据更新
|
|
|
|
|
manager.onConnectionAdded(() => this.onDidChangeTreeDataEmitter.fire());
|
|
|
|
|
// 监听连接移除事件,当有连接移除时,触发树形视图数据更新
|
|
|
|
|
manager.onConnectionRemoved(() => this.onDidChangeTreeDataEmitter.fire());
|
|
|
|
|
// 监听连接更新事件,当有连接更新时,触发树形视图数据更新
|
|
|
|
|
manager.onConnectionUpdated(con => this.onDidChangeTreeDataEmitter.fire(con));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 刷新树形视图数据。
|
|
|
|
|
* 当调用此方法时,会触发 onDidChangeTreeData 事件,通知树形视图数据发生变化。
|
|
|
|
|
*/
|
|
|
|
|
public refresh() {
|
|
|
|
|
// 触发树形视图数据更新事件
|
|
|
|
|
this.onDidChangeTreeDataEmitter.fire();
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定元素的树形视图项。
|
|
|
|
|
* @param element - 要获取树形视图项的元素。
|
|
|
|
|
* @returns 树形视图项或 Promise 包裹的树形视图项。
|
|
|
|
|
*/
|
|
|
|
|
public getTreeItem(element: TreeData): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
|
|
|
|
if ('onDidChangeFile' in element || 'handleInput' in element) { // SSHFileSystem | SSHPseudoTerminal
|
|
|
|
|
return { ...formatItem(element), collapsibleState: vscode.TreeItemCollapsibleState.None }
|
|
|
|
|
} else if (Array.isArray(element)) { // PendingConnection
|
|
|
|
|
// 判断元素是否包含 onDidChangeFile 或 handleInput 属性,如果是,则为 SSHFileSystem 或 SSHPseudoTerminal
|
|
|
|
|
if ('onDidChangeFile' in element || 'handleInput' in element) {
|
|
|
|
|
// 返回格式化后的元素,折叠状态为 None
|
|
|
|
|
return { ...formatItem(element), collapsibleState: vscode.TreeItemCollapsibleState.None };
|
|
|
|
|
}
|
|
|
|
|
// 判断元素是否为数组,如果是,则为 PendingConnection
|
|
|
|
|
else if (Array.isArray(element)) { // PendingConnection
|
|
|
|
|
// 解构赋值,获取数组中的名称和配置
|
|
|
|
|
const [name, config] = element;
|
|
|
|
|
// 如果没有配置,则返回名称和描述为 Connecting... 的树形视图项
|
|
|
|
|
if (!config) return { label: name, description: 'Connecting...' };
|
|
|
|
|
// 返回格式化后的配置,上下文值为 pendingconnection,折叠状态为 Collapsed,图标为加载中
|
|
|
|
|
return {
|
|
|
|
|
...formatItem(config),
|
|
|
|
|
...formatItem(config),
|
|
|
|
|
contextValue: 'pendingconnection',
|
|
|
|
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
|
|
|
|
// Doesn't seem to actually spin, but still gets rendered properly otherwise
|
|
|
|
|
// 似乎没有实际旋转,但仍然可以正确渲染
|
|
|
|
|
iconPath: new vscode.ThemeIcon('loading~spin'),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// Connection
|
|
|
|
|
// 否则,元素为 Connection
|
|
|
|
|
// 返回格式化后的元素,折叠状态为 Collapsed
|
|
|
|
|
return { ...formatItem(element), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed };
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定元素的子元素。
|
|
|
|
|
* @param element - 要获取子元素的元素。
|
|
|
|
|
* @returns 子元素数组或 Promise 包裹的子元素数组。
|
|
|
|
|
*/
|
|
|
|
|
public getChildren(element?: TreeData): vscode.ProviderResult<TreeData[]> {
|
|
|
|
|
// 如果没有指定元素,则返回所有活动连接和待处理连接
|
|
|
|
|
if (!element) return [
|
|
|
|
|
...this.manager.getActiveConnections(),
|
|
|
|
|
...this.manager.getPendingConnections(),
|
|
|
|
|
...this.manager.getActiveConnections(),
|
|
|
|
|
...this.manager.getPendingConnections(),
|
|
|
|
|
];
|
|
|
|
|
// 判断元素是否包含 onDidChangeFile 属性,如果是,则为 SSHFileSystem,返回空数组
|
|
|
|
|
if ('onDidChangeFile' in element) return []; // SSHFileSystem
|
|
|
|
|
// 判断元素是否包含 handleInput 属性,如果是,则为 SSHPseudoTerminal,返回空数组
|
|
|
|
|
if ('handleInput' in element) return []; // SSHPseudoTerminal
|
|
|
|
|
// 判断元素是否为数组,如果是,则为 PendingConnection,返回空数组
|
|
|
|
|
if (Array.isArray(element)) return []; // PendingConnection
|
|
|
|
|
// 否则,元素为 Connection,返回其终端和文件系统
|
|
|
|
|
return [...element.terminals, ...element.filesystems]; // Connection
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 表示一个树形视图的数据提供器,用于管理文件系统配置和组。
|
|
|
|
|
*/
|
|
|
|
|
export class ConfigTreeProvider implements vscode.TreeDataProvider<FileSystemConfig | string> {
|
|
|
|
|
// 定义一个事件发射器,用于通知树形视图数据发生变化
|
|
|
|
|
protected onDidChangeTreeDataEmitter = new vscode.EventEmitter<FileSystemConfig | string | void>();
|
|
|
|
|
// 定义一个事件,当树形视图数据发生变化时触发
|
|
|
|
|
public onDidChangeTreeData: vscode.Event<FileSystemConfig | string | void> = this.onDidChangeTreeDataEmitter.event;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数,初始化配置树提供器。
|
|
|
|
|
* 它会监听配置更新事件,并在配置更改时刷新树形视图。
|
|
|
|
|
*/
|
|
|
|
|
constructor() {
|
|
|
|
|
// Would be very difficult (and a bit useless) to pinpoint the exact
|
|
|
|
|
// group/config that changes, so let's just update the whole tree
|
|
|
|
|
// 监听配置更新事件,当配置更新时,触发树形视图数据更新
|
|
|
|
|
UPDATE_LISTENERS.push(() => this.onDidChangeTreeDataEmitter.fire());
|
|
|
|
|
// ^ Technically a memory leak, but there should only be one ConfigTreeProvider that never gets discarded
|
|
|
|
|
// 从技术上讲,这可能会导致内存泄漏,但实际上应该只有一个 ConfigTreeProvider 实例,且它不会被丢弃。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定元素的树形视图项。
|
|
|
|
|
* @param element - 要获取树形视图项的元素。
|
|
|
|
|
* @returns 树形视图项或 Promise 包裹的树形视图项。
|
|
|
|
|
*/
|
|
|
|
|
public getTreeItem(element: FileSystemConfig | string): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
|
|
|
|
// 如果元素是字符串,则表示它是一个组
|
|
|
|
|
if (typeof element === 'string') {
|
|
|
|
|
// 返回一个树形视图项,标签为组名(去除前缀),上下文值为 group,折叠状态为 Collapsed,图标为文件夹
|
|
|
|
|
return {
|
|
|
|
|
label: element.replace(/^.+\./, ''), contextValue: 'group',
|
|
|
|
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
|
|
|
|
iconPath: vscode.ThemeIcon.Folder,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// 如果元素是 FileSystemConfig 类型,则返回格式化后的配置项
|
|
|
|
|
return { ...formatItem(element), collapsibleState: vscode.TreeItemCollapsibleState.None };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定元素的子元素。
|
|
|
|
|
* @param element - 要获取子元素的元素。
|
|
|
|
|
* @returns 子元素数组或 Promise 包裹的子元素数组。
|
|
|
|
|
*/
|
|
|
|
|
public getChildren(element: FileSystemConfig | string = ''): vscode.ProviderResult<(FileSystemConfig | string)[]> {
|
|
|
|
|
// 如果元素不是字符串,则返回空数组,因为配置项没有子元素
|
|
|
|
|
if (typeof element !== 'string') return []; // Configs don't have children
|
|
|
|
|
// 获取所有配置项
|
|
|
|
|
const configs = getConfigs();
|
|
|
|
|
// 过滤出属于指定组的配置项
|
|
|
|
|
const matching = configs.filter(({ group }) => (group || '') === element);
|
|
|
|
|
// 对匹配的配置项进行排序
|
|
|
|
|
matching.sort((a, b) => a.name > b.name ? 1 : -1);
|
|
|
|
|
// 获取所有组
|
|
|
|
|
let groups = getGroups(configs, true);
|
|
|
|
|
// 如果指定了元素,则过滤出以该元素为前缀的组
|
|
|
|
|
if (element) {
|
|
|
|
|
groups = groups.filter(g => g.startsWith(element) && g[element.length] === '.' && !g.includes('.', element.length + 1));
|
|
|
|
|
} else {
|
|
|
|
|
// 如果没有指定元素,则过滤出顶级组(不包含点号的组)
|
|
|
|
|
groups = groups.filter(g => !g.includes('.'));
|
|
|
|
|
}
|
|
|
|
|
// 返回匹配的配置项和组的数组
|
|
|
|
|
return [...matching, ...groups.sort()];
|
|
|
|
|
}
|
|
|
|
|
}
|