From bd6bf3a6a1ca2598e351389594b802e1c7ca276e Mon Sep 17 00:00:00 2001 From: bernard Date: Mon, 30 Jun 2025 06:12:49 +0800 Subject: [PATCH 1/2] Implement file decoration and tree item click handling features - Added a new `DecorationProvider` class to manage file decorations in the project tree. - Introduced a command `extension.handleTreeItemClick` to handle double-click events on tree items, allowing for project selection and file marking. - Updated `package.json` to include the new command in the extension's menu. - Refactored tree view logic to support the new click handling and decoration features. - Enhanced project file management by integrating decoration updates on project selection. --- package.json | 9 ++-- src/dock.ts | 8 +++- src/extension.ts | 3 ++ src/project/cmd.ts | 4 ++ src/project/fileDecorationProvider.ts | 61 +++++++++++++++++++++++++++ src/project/tree.ts | 57 +++++++++++++++++++++++-- 6 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 src/project/fileDecorationProvider.ts diff --git a/package.json b/package.json index 1f7a978..2cd0c48 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,10 @@ "command": "extension.openTerminalProject", "title": "Open RT-Thread Terminal", "icon": "$(console)" + }, + { + "command": "extension.handleTreeItemClick", + "title": "Handle Tree Item Click" } ], "menus": { @@ -119,11 +123,6 @@ } ], "view/item/context": [ - { - "command": "extension.switchProject", - "when": "view == projectFilesId && viewItem == project_bsp", - "group": "inline" - }, { "command": "extension.fastBuildProject", "when": "view == projectFilesId && viewItem == project_bsp", diff --git a/src/dock.ts b/src/dock.ts index 30a999b..6a65fcb 100644 --- a/src/dock.ts +++ b/src/dock.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import os from 'os'; import fs from 'fs'; import { getWorkspaceFolder, isRTThreadProject, isRTThreadWorksapce } from './api'; -import { buildGroupsTree, buildProjectTree, buildEmptyProjectTree, ProjectTreeItem, listFolderTreeItem, buildBSPTree } from './project/tree'; +import { buildGroupsTree, buildProjectTree, buildEmptyProjectTree, ProjectTreeItem, listFolderTreeItem, buildBSPTree, setTreeDataChangeEmitter } from './project/tree'; import { cmds } from './cmds/index'; class CmdTreeDataProvider implements vscode.TreeDataProvider { @@ -188,6 +188,10 @@ class ProjectFilesDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + getTreeDataChangeEmitter(): vscode.EventEmitter { + return this._onDidChangeTreeData; + } + getTreeItem(element: ProjectTreeItem): vscode.TreeItem { return element; } @@ -291,6 +295,8 @@ export function initDockView(context: vscode.ExtensionContext) { treeDataProvider: _projectFilesDataProvider, showCollapseAll: true }); + setTreeDataChangeEmitter(_projectFilesDataProvider.getTreeDataChangeEmitter()); + context.subscriptions.push(view); vscode.commands.registerCommand('extension.refreshRTThread', () => refreshProjectFilesAndGroups()); diff --git a/src/extension.ts b/src/extension.ts index aca29c0..b10d466 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,7 @@ import { setupVEnv } from './venv'; import { initAPI } from './api'; import { openWorkspaceProjectsWebview } from './webviews/project'; import { initProjectTree } from './project/tree'; +import { DecorationProvider } from './project/fileDecorationProvider'; let _context: vscode.ExtensionContext; @@ -47,6 +48,8 @@ export async function activate(context: vscode.ExtensionContext) { isRTThreadWorksapce = true; vscode.commands.executeCommand('setContext', 'isRTThreadWorksapce', true); context.workspaceState.update('isRTThreadWorksapce', isRTThreadWorksapce); + + new DecorationProvider(context); } } else { diff --git a/src/project/cmd.ts b/src/project/cmd.ts index 85297d8..9aa3f18 100644 --- a/src/project/cmd.ts +++ b/src/project/cmd.ts @@ -1,5 +1,6 @@ import * as os from 'os'; import * as fs from 'fs'; +import * as vscode from 'vscode'; import { getWorkspaceFolder } from '../api'; import { executeCommand } from '../terminal'; @@ -40,6 +41,9 @@ export function openTerminalProject(arg: any) { export function setCurrentProject(arg: any) { if (arg) { _currentProject = arg.fn; + + let cmd = 'scons -C ' + arg.fn + ' --target=vsc_workspace'; + executeCommand(cmd); } return; diff --git a/src/project/fileDecorationProvider.ts b/src/project/fileDecorationProvider.ts new file mode 100644 index 0000000..e32599a --- /dev/null +++ b/src/project/fileDecorationProvider.ts @@ -0,0 +1,61 @@ +import { + CancellationToken, + Event, + EventEmitter, + ExtensionContext, + FileDecoration, + FileDecorationProvider, + ThemeColor, + Uri, + window, +} from "vscode"; + +export class DecorationProvider implements FileDecorationProvider { + private readonly _onDidChangeFileDecorations: EventEmitter = + new EventEmitter(); + readonly onDidChangeFileDecorations: Event = + this._onDidChangeFileDecorations.event; + public markedFiles: Set = new Set(); + private static instance: DecorationProvider; + + constructor(context: ExtensionContext) { + // 注册文件装饰提供者 + context.subscriptions.push( + window.registerFileDecorationProvider(this), + ); + + DecorationProvider.instance = this; + } + + static getInstance(): DecorationProvider { + return DecorationProvider.instance; + } + + provideFileDecoration(uri: Uri, token: CancellationToken): FileDecoration | undefined { + if (token.isCancellationRequested) { + return; + } + + if (this.markedFiles.has(uri.fsPath)) { + return { + propagate: true, + badge: "📌", + color: new ThemeColor("terminal.ansiCyan"), + }; + } + } + + public async markFile(uri: Uri) { + if (!this.markedFiles.has(uri.fsPath)) { + this.markedFiles.add(uri.fsPath); + this._onDidChangeFileDecorations.fire(uri); + } + } + + public async unmarkFile(uri: Uri) { + if (this.markedFiles.has(uri.fsPath)) { + this.markedFiles.delete(uri.fsPath); + this._onDidChangeFileDecorations.fire(uri); + } + } +} diff --git a/src/project/tree.ts b/src/project/tree.ts index 32413a7..3b94724 100644 --- a/src/project/tree.ts +++ b/src/project/tree.ts @@ -2,6 +2,7 @@ import path from 'path'; import * as vscode from 'vscode'; import fs from 'fs'; import { getWorkspaceFolder, getExtensionPath } from '../api'; +import { DecorationProvider } from './fileDecorationProvider'; /* * contexType -> contextValue as following value: @@ -11,6 +12,12 @@ import { getWorkspaceFolder, getExtensionPath } from '../api'; * project_bsp */ +// 全局变量记录当前选中的project_bsp项目 +let currentSelectedBspItem: ProjectTreeItem | null = null; + +// 添加树视图刷新事件发射器 +let _onDidChangeTreeData: vscode.EventEmitter | null = null; + export class ProjectTreeItem extends vscode.TreeItem { children: ProjectTreeItem[]; fn: string = ''; @@ -58,12 +65,16 @@ export class ProjectTreeItem extends vscode.TreeItem { else if (contextType == 'project_bsp') { this.command = { title: this.name, - command: 'extension.switchProject', + command: 'extension.handleTreeItemClick', tooltip: this.name, arguments: [ this ] }; + + // with resourceUri, the item will be rendered as a file icon + // then the item can be marked with a decoration + this.resourceUri = vscode.Uri.file(this.fn); } } } @@ -333,7 +344,47 @@ export function initProjectTree(context:vscode.ExtensionContext) { vscode.commands.registerCommand('extension.openTerminalProject', (arg) => { openTerminalProject(arg); }); - vscode.commands.registerCommand('extension.switchProject', (arg) => { - setCurrentProject(arg); + + // Add double-clicked + let lastClickTime = 0; + let lastClickedItem: ProjectTreeItem | null = null; + + vscode.commands.registerCommand('extension.handleTreeItemClick', (item: ProjectTreeItem) => { + const currentTime = Date.now(); + const doubleClickThreshold = 500; // 500ms内的两次点击被认为是双击 + + if (item.contextType === 'project_bsp') { + if (lastClickedItem === item && (currentTime - lastClickTime) < doubleClickThreshold) { + if (currentSelectedBspItem && currentSelectedBspItem.fn === item.fn) { + return; + } + + // double clicked + if (currentSelectedBspItem && currentSelectedBspItem.fn != item.fn) { + DecorationProvider.getInstance().unmarkFile(vscode.Uri.file(currentSelectedBspItem.fn)); + } + + currentSelectedBspItem = item; + DecorationProvider.getInstance().markFile(vscode.Uri.file(item.fn)); + setCurrentProject(item); + + if (_onDidChangeTreeData) { + _onDidChangeTreeData.fire(undefined); + } + + // reset status + lastClickTime = 0; + lastClickedItem = null; + } else { + // one-clicked, just record it. + lastClickTime = currentTime; + lastClickedItem = item; + } + } }); } + +// 导出函数用于设置树视图刷新事件发射器 +export function setTreeDataChangeEmitter(emitter: vscode.EventEmitter) { + _onDidChangeTreeData = emitter; +} From 9f1158b5bad74a2e946ffafcad2d3fcc6b68344d Mon Sep 17 00:00:00 2001 From: bernard Date: Mon, 7 Jul 2025 08:23:22 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=9A=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BB=8E=20workspace.json=20=E6=96=87=E4=BB=B6=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=B9=B6=E5=9C=A8=E6=BF=80=E6=B4=BB=E6=97=B6?= =?UTF-8?q?=E6=A0=87=E8=AE=B0=E6=96=87=E4=BB=B6=EF=BC=9B=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20workspace.json=20=E6=96=87=E4=BB=B6=E4=BB=A5=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE=EF=BC=9B=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E6=96=87=E4=BB=B6=E8=A3=85=E9=A5=B0=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E8=80=85=E4=BB=A5=E6=94=AF=E6=8C=81=E6=B8=85=E9=99=A4=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E7=9A=84=E6=96=87=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extension.ts | 7 +++ src/project/cmd.ts | 8 +++ src/project/fileDecorationProvider.ts | 4 ++ src/vue/projects/App.vue | 35 +++++++---- src/webviews/project.ts | 86 ++++++++++++++++++--------- 5 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index b10d466..e998622 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,7 @@ import { initAPI } from './api'; import { openWorkspaceProjectsWebview } from './webviews/project'; import { initProjectTree } from './project/tree'; import { DecorationProvider } from './project/fileDecorationProvider'; +import { getCurrentProjectInWorkspace } from './webviews/project'; let _context: vscode.ExtensionContext; @@ -50,6 +51,12 @@ export async function activate(context: vscode.ExtensionContext) { context.workspaceState.update('isRTThreadWorksapce', isRTThreadWorksapce); new DecorationProvider(context); + + // get current project from workspace.json file + let currentProject = getCurrentProjectInWorkspace(); + if (currentProject) { + DecorationProvider.getInstance().markFile(vscode.Uri.file(currentProject)); + } } } else { diff --git a/src/project/cmd.ts b/src/project/cmd.ts index 9aa3f18..cc73f1b 100644 --- a/src/project/cmd.ts +++ b/src/project/cmd.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import { getWorkspaceFolder } from '../api'; import { executeCommand } from '../terminal'; +import { readWorkspaceJson, writeWorkspaceJson } from '../webviews/project'; let _currentProject: string = ''; @@ -44,6 +45,13 @@ export function setCurrentProject(arg: any) { let cmd = 'scons -C ' + arg.fn + ' --target=vsc_workspace'; executeCommand(cmd); + + // update workspace.json file + let workspaceJson = readWorkspaceJson(); + if (workspaceJson) { + workspaceJson.currentProject = arg.fn; + writeWorkspaceJson(workspaceJson); + } } return; diff --git a/src/project/fileDecorationProvider.ts b/src/project/fileDecorationProvider.ts index e32599a..5ce3314 100644 --- a/src/project/fileDecorationProvider.ts +++ b/src/project/fileDecorationProvider.ts @@ -58,4 +58,8 @@ export class DecorationProvider implements FileDecorationProvider { this._onDidChangeFileDecorations.fire(uri); } } + + public async unmarkAllFiles() { + this.markedFiles.clear(); + } } diff --git a/src/vue/projects/App.vue b/src/vue/projects/App.vue index 269172e..b337613 100644 --- a/src/vue/projects/App.vue +++ b/src/vue/projects/App.vue @@ -5,10 +5,11 @@ 可以在感兴趣的BSP/工程项上✔,然后保存配置,将会在侧边栏中显示对应列表。

- - 折叠全部列表 - - 保存列表配置 +
+ 加载列表 + 折叠全部列表 + 保存列表配置 +
@@ -20,7 +21,7 @@