实现 vscode 内 sidebar 的实现

This commit is contained in:
锦恢 2025-04-23 12:29:52 +08:00
parent 5bac8f8726
commit 4473421708
4 changed files with 53 additions and 27 deletions

View File

@ -34,7 +34,7 @@
},
{
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
"title": "展示 OpenMCP",
"title": "连接",
"category": "openmcp",
"icon": {
"light": "./icons/light/protocol.svg",
@ -58,8 +58,11 @@
"view/item/context": [
{
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
"group": "navigation",
"when": "view == openmcp.sidebar-view.workspace-connection"
"group": "inline@1",
"when": "view == openmcp.sidebar-view.workspace-connection",
"args": {
"view": "${viewItem}"
}
}
]
},
@ -77,7 +80,7 @@
{
"id": "openmcp.sidebar-view.workspace-connection",
"icon": "./icons/protocol.svg",
"name": "MCP 连接",
"name": "MCP 连接 (工作区)",
"type": "tree"
},
{

View File

@ -3,7 +3,7 @@ import * as fspath from 'path';
import * as OpenMCPService from '../resources/service';
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
import { registerSidebar } from './sidebar';
import { ConnectionViewItem, registerSidebar } from './sidebar';
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global';
export function activate(context: vscode.ExtensionContext) {
@ -18,7 +18,8 @@ export function activate(context: vscode.ExtensionContext) {
registerSidebar(context);
context.subscriptions.push(
vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (item: IStdioConnectionItem | ISSEConnectionItem) => {
vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (view: ConnectionViewItem) => {
const item = view.item;
revealOpenMcpWebviewPanel(context, item.name, item);
})
);

View File

@ -82,10 +82,10 @@ export function getWorkspaceConnectionConfig() {
const workspacePath = getWorkspacePath();
for (const item of connection.items) {
if (item.filePath && item.filePath.startsWith('{workspace}')) {
item.filePath = item.filePath.replace('{workspace}', workspacePath).replace('\\', '/');
item.filePath = item.filePath.replace('{workspace}', workspacePath).replace(/\\/g, '/');
}
if (item.type === 'stdio' && item.cwd && item.cwd.startsWith('{workspace}')) {
item.cwd = item.cwd.replace('{workspace}', workspacePath).replace('\\', '/');
item.cwd = item.cwd.replace('{workspace}', workspacePath).replace(/\\/g, '/');
}
}
@ -106,14 +106,14 @@ export function saveWorkspaceConnectionConfig(workspace: string) {
const workspacePath = getWorkspacePath();
for (const item of connectionConfig.items) {
if (item.filePath && item.filePath.startsWith(workspacePath)) {
item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace('\\', '/');
if (item.filePath && item.filePath.replace(/\\/g, '/').startsWith(workspacePath)) {
item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace(/\\/g, '/');
}
if (item.type ==='stdio' && item.cwd && item.cwd.startsWith(workspacePath)) {
item.cwd = item.cwd.replace(workspacePath, '{workspace}').replace('\\', '/');
if (item.type ==='stdio' && item.cwd && item.cwd.replace(/\\/g, '/').startsWith(workspacePath)) {
item.cwd = item.cwd.replace(workspacePath, '{workspace}').replace(/\\/g, '/');
}
}
fs.writeFileSync(connectionConfigPath, JSON.stringify(connectionConfig), 'utf-8');
fs.writeFileSync(connectionConfigPath, JSON.stringify(connectionConfig, null, 2), 'utf-8');
}
interface ClientStdioConnectionItem {
@ -152,9 +152,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
name: data.clientName,
command: data.command,
args: data.args,
cwd: data.cwd.replace('\\', '/'),
cwd: data.cwd.replace(/\\/g, '/'),
env: data.env,
filePath: absPath.replace('\\', '/')
filePath: absPath.replace(/\\/g, '/')
};
// 插入到第一个
@ -162,7 +162,7 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
const workspacePath = getWorkspacePath();
saveWorkspaceConnectionConfig(workspacePath);
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
} else {
}
@ -171,9 +171,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnectionItem, workspace: string) {
if (item.filePath) {
if (item.filePath.startsWith('{workspace}')) {
return item.filePath.replace('{workspace}', workspace).replace('\\', '/');
return item.filePath.replace('{workspace}', workspace).replace(/\\/g, '/');
} else {
return item.filePath.replace('\\', '/');
return item.filePath.replace(/\\/g, '/');
}
}
@ -182,7 +182,7 @@ function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnection
export function getWorkspacePath() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
return (workspaceFolder?.uri.fsPath || '').replace('\\', '/');
return (workspaceFolder?.uri.fsPath || '').replace(/\\/g, '/');
}
/**
@ -193,7 +193,7 @@ export function getWorkspaceConnectionConfigItemByPath(absPath: string) {
const workspacePath = getWorkspacePath();
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
const normaliseAbsPath = absPath.replace('\\', '/');
const normaliseAbsPath = absPath.replace(/\\/g, '/');
for (const item of workspaceConnectionConfig.items) {
const filePath = normaliseConnectionFilePath(item, workspacePath);
if (filePath === normaliseAbsPath) {

View File

@ -1,29 +1,39 @@
import * as vscode from 'vscode';
import { getConnectionConfig, getWorkspaceConnectionConfig } from './global';
import { getConnectionConfig, getWorkspaceConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
private _onDidChangeTreeData: vscode.EventEmitter<SidebarItem | undefined | null | void> = new vscode.EventEmitter<SidebarItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<SidebarItem | undefined | null | void> = this._onDidChangeTreeData.event;
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<ConnectionViewItem | undefined | null | void> = this._onDidChangeTreeData.event;
constructor(private context: vscode.ExtensionContext) {
}
// 实现 TreeDataProvider 接口
getTreeItem(element: SidebarItem): vscode.TreeItem {
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
return element;
}
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
// TODO: 读取 configDir 下的所有文件,作为子节点
const connection = getWorkspaceConnectionConfig();
const sidebarItems = connection.items.map((item, index) => {
return new SidebarItem(item.name, vscode.TreeItemCollapsibleState.None);
// 连接的名字
const itemName = this.displayName(item);
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
})
// 返回子节点
return Promise.resolve(sidebarItems);
}
public displayName(item: IStdioConnectionItem | ISSEConnectionItem) {
if (item.filePath) {
const filename = item.filePath.split('/').pop();
return `${filename} (${item.type})`;
}
return item.name;
}
// 添加 refresh 方法
public refresh(): void {
this._onDidChangeTreeData.fire();
@ -105,4 +115,16 @@ class SidebarItem extends vscode.TreeItem {
this.command = command;
this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline');
}
}
export class ConnectionViewItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly item: IStdioConnectionItem | ISSEConnectionItem,
public readonly icon?: string
) {
super(label, collapsibleState);
this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline');
}
}