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

View File

@ -3,7 +3,7 @@ import * as fspath from 'path';
import * as OpenMCPService from '../resources/service'; import * as OpenMCPService from '../resources/service';
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview'; import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
import { registerSidebar } from './sidebar'; import { ConnectionViewItem, registerSidebar } from './sidebar';
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global'; import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global';
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
@ -18,7 +18,8 @@ export function activate(context: vscode.ExtensionContext) {
registerSidebar(context); registerSidebar(context);
context.subscriptions.push( 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); revealOpenMcpWebviewPanel(context, item.name, item);
}) })
); );

View File

@ -82,10 +82,10 @@ export function getWorkspaceConnectionConfig() {
const workspacePath = getWorkspacePath(); const workspacePath = getWorkspacePath();
for (const item of connection.items) { for (const item of connection.items) {
if (item.filePath && item.filePath.startsWith('{workspace}')) { 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}')) { 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(); const workspacePath = getWorkspacePath();
for (const item of connectionConfig.items) { for (const item of connectionConfig.items) {
if (item.filePath && item.filePath.startsWith(workspacePath)) { if (item.filePath && item.filePath.replace(/\\/g, '/').startsWith(workspacePath)) {
item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace('\\', '/'); item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace(/\\/g, '/');
} }
if (item.type ==='stdio' && item.cwd && item.cwd.startsWith(workspacePath)) { if (item.type ==='stdio' && item.cwd && item.cwd.replace(/\\/g, '/').startsWith(workspacePath)) {
item.cwd = item.cwd.replace(workspacePath, '{workspace}').replace('\\', '/'); 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 { interface ClientStdioConnectionItem {
@ -152,9 +152,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
name: data.clientName, name: data.clientName,
command: data.command, command: data.command,
args: data.args, args: data.args,
cwd: data.cwd.replace('\\', '/'), cwd: data.cwd.replace(/\\/g, '/'),
env: data.env, env: data.env,
filePath: absPath.replace('\\', '/') filePath: absPath.replace(/\\/g, '/')
}; };
// 插入到第一个 // 插入到第一个
@ -171,9 +171,9 @@ export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStd
function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnectionItem, workspace: string) { function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnectionItem, workspace: string) {
if (item.filePath) { if (item.filePath) {
if (item.filePath.startsWith('{workspace}')) { if (item.filePath.startsWith('{workspace}')) {
return item.filePath.replace('{workspace}', workspace).replace('\\', '/'); return item.filePath.replace('{workspace}', workspace).replace(/\\/g, '/');
} else { } else {
return item.filePath.replace('\\', '/'); return item.filePath.replace(/\\/g, '/');
} }
} }
@ -182,7 +182,7 @@ function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnection
export function getWorkspacePath() { export function getWorkspacePath() {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 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 workspacePath = getWorkspacePath();
const workspaceConnectionConfig = getWorkspaceConnectionConfig(); const workspaceConnectionConfig = getWorkspaceConnectionConfig();
const normaliseAbsPath = absPath.replace('\\', '/'); const normaliseAbsPath = absPath.replace(/\\/g, '/');
for (const item of workspaceConnectionConfig.items) { for (const item of workspaceConnectionConfig.items) {
const filePath = normaliseConnectionFilePath(item, workspacePath); const filePath = normaliseConnectionFilePath(item, workspacePath);
if (filePath === normaliseAbsPath) { if (filePath === normaliseAbsPath) {

View File

@ -1,29 +1,39 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getConnectionConfig, getWorkspaceConnectionConfig } from './global'; import { getConnectionConfig, getWorkspaceConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<SidebarItem> { class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
private _onDidChangeTreeData: vscode.EventEmitter<SidebarItem | undefined | null | void> = new vscode.EventEmitter<SidebarItem | undefined | null | void>(); private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<SidebarItem | undefined | null | void> = this._onDidChangeTreeData.event; readonly onDidChangeTreeData: vscode.Event<ConnectionViewItem | undefined | null | void> = this._onDidChangeTreeData.event;
constructor(private context: vscode.ExtensionContext) { constructor(private context: vscode.ExtensionContext) {
} }
// 实现 TreeDataProvider 接口 // 实现 TreeDataProvider 接口
getTreeItem(element: SidebarItem): vscode.TreeItem { getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
return element; return element;
} }
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> { getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
// TODO: 读取 configDir 下的所有文件,作为子节点 // TODO: 读取 configDir 下的所有文件,作为子节点
const connection = getWorkspaceConnectionConfig(); const connection = getWorkspaceConnectionConfig();
const sidebarItems = connection.items.map((item, index) => { 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); 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 方法 // 添加 refresh 方法
public refresh(): void { public refresh(): void {
this._onDidChangeTreeData.fire(); this._onDidChangeTreeData.fire();
@ -106,3 +116,15 @@ class SidebarItem extends vscode.TreeItem {
this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline'); 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');
}
}