Skip to content

Commit bd6bf3a

Browse files
committed
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.
1 parent 87668b5 commit bd6bf3a

File tree

6 files changed

+133
-9
lines changed

6 files changed

+133
-9
lines changed

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
"command": "extension.openTerminalProject",
9999
"title": "Open RT-Thread Terminal",
100100
"icon": "$(console)"
101+
},
102+
{
103+
"command": "extension.handleTreeItemClick",
104+
"title": "Handle Tree Item Click"
101105
}
102106
],
103107
"menus": {
@@ -119,11 +123,6 @@
119123
}
120124
],
121125
"view/item/context": [
122-
{
123-
"command": "extension.switchProject",
124-
"when": "view == projectFilesId && viewItem == project_bsp",
125-
"group": "inline"
126-
},
127126
{
128127
"command": "extension.fastBuildProject",
129128
"when": "view == projectFilesId && viewItem == project_bsp",

src/dock.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as vscode from 'vscode';
33
import os from 'os';
44
import fs from 'fs';
55
import { getWorkspaceFolder, isRTThreadProject, isRTThreadWorksapce } from './api';
6-
import { buildGroupsTree, buildProjectTree, buildEmptyProjectTree, ProjectTreeItem, listFolderTreeItem, buildBSPTree } from './project/tree';
6+
import { buildGroupsTree, buildProjectTree, buildEmptyProjectTree, ProjectTreeItem, listFolderTreeItem, buildBSPTree, setTreeDataChangeEmitter } from './project/tree';
77
import { cmds } from './cmds/index';
88

99
class CmdTreeDataProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
@@ -188,6 +188,10 @@ class ProjectFilesDataProvider implements vscode.TreeDataProvider<ProjectTreeIte
188188
private _onDidChangeTreeData: vscode.EventEmitter<ProjectTreeItem | undefined> = new vscode.EventEmitter<ProjectTreeItem | undefined>();
189189
readonly onDidChangeTreeData: vscode.Event<ProjectTreeItem | undefined> = this._onDidChangeTreeData.event;
190190

191+
getTreeDataChangeEmitter(): vscode.EventEmitter<ProjectTreeItem | undefined> {
192+
return this._onDidChangeTreeData;
193+
}
194+
191195
getTreeItem(element: ProjectTreeItem): vscode.TreeItem {
192196
return element;
193197
}
@@ -291,6 +295,8 @@ export function initDockView(context: vscode.ExtensionContext) {
291295
treeDataProvider: _projectFilesDataProvider, showCollapseAll: true
292296
});
293297

298+
setTreeDataChangeEmitter(_projectFilesDataProvider.getTreeDataChangeEmitter());
299+
294300
context.subscriptions.push(view);
295301
vscode.commands.registerCommand('extension.refreshRTThread', () => refreshProjectFilesAndGroups());
296302

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { setupVEnv } from './venv';
1414
import { initAPI } from './api';
1515
import { openWorkspaceProjectsWebview } from './webviews/project';
1616
import { initProjectTree } from './project/tree';
17+
import { DecorationProvider } from './project/fileDecorationProvider';
1718

1819
let _context: vscode.ExtensionContext;
1920

@@ -47,6 +48,8 @@ export async function activate(context: vscode.ExtensionContext) {
4748
isRTThreadWorksapce = true;
4849
vscode.commands.executeCommand('setContext', 'isRTThreadWorksapce', true);
4950
context.workspaceState.update('isRTThreadWorksapce', isRTThreadWorksapce);
51+
52+
new DecorationProvider(context);
5053
}
5154
}
5255
else {

src/project/cmd.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as os from 'os';
22
import * as fs from 'fs';
3+
import * as vscode from 'vscode';
34

45
import { getWorkspaceFolder } from '../api';
56
import { executeCommand } from '../terminal';
@@ -40,6 +41,9 @@ export function openTerminalProject(arg: any) {
4041
export function setCurrentProject(arg: any) {
4142
if (arg) {
4243
_currentProject = arg.fn;
44+
45+
let cmd = 'scons -C ' + arg.fn + ' --target=vsc_workspace';
46+
executeCommand(cmd);
4347
}
4448

4549
return;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
CancellationToken,
3+
Event,
4+
EventEmitter,
5+
ExtensionContext,
6+
FileDecoration,
7+
FileDecorationProvider,
8+
ThemeColor,
9+
Uri,
10+
window,
11+
} from "vscode";
12+
13+
export class DecorationProvider implements FileDecorationProvider {
14+
private readonly _onDidChangeFileDecorations: EventEmitter<Uri | Uri[]> =
15+
new EventEmitter<Uri | Uri[]>();
16+
readonly onDidChangeFileDecorations: Event<Uri | Uri[]> =
17+
this._onDidChangeFileDecorations.event;
18+
public markedFiles: Set<string> = new Set<string>();
19+
private static instance: DecorationProvider;
20+
21+
constructor(context: ExtensionContext) {
22+
// 注册文件装饰提供者
23+
context.subscriptions.push(
24+
window.registerFileDecorationProvider(this),
25+
);
26+
27+
DecorationProvider.instance = this;
28+
}
29+
30+
static getInstance(): DecorationProvider {
31+
return DecorationProvider.instance;
32+
}
33+
34+
provideFileDecoration(uri: Uri, token: CancellationToken): FileDecoration | undefined {
35+
if (token.isCancellationRequested) {
36+
return;
37+
}
38+
39+
if (this.markedFiles.has(uri.fsPath)) {
40+
return {
41+
propagate: true,
42+
badge: "📌",
43+
color: new ThemeColor("terminal.ansiCyan"),
44+
};
45+
}
46+
}
47+
48+
public async markFile(uri: Uri) {
49+
if (!this.markedFiles.has(uri.fsPath)) {
50+
this.markedFiles.add(uri.fsPath);
51+
this._onDidChangeFileDecorations.fire(uri);
52+
}
53+
}
54+
55+
public async unmarkFile(uri: Uri) {
56+
if (this.markedFiles.has(uri.fsPath)) {
57+
this.markedFiles.delete(uri.fsPath);
58+
this._onDidChangeFileDecorations.fire(uri);
59+
}
60+
}
61+
}

src/project/tree.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path';
22
import * as vscode from 'vscode';
33
import fs from 'fs';
44
import { getWorkspaceFolder, getExtensionPath } from '../api';
5+
import { DecorationProvider } from './fileDecorationProvider';
56

67
/*
78
* contexType -> contextValue as following value:
@@ -11,6 +12,12 @@ import { getWorkspaceFolder, getExtensionPath } from '../api';
1112
* project_bsp
1213
*/
1314

15+
// 全局变量记录当前选中的project_bsp项目
16+
let currentSelectedBspItem: ProjectTreeItem | null = null;
17+
18+
// 添加树视图刷新事件发射器
19+
let _onDidChangeTreeData: vscode.EventEmitter<ProjectTreeItem | undefined> | null = null;
20+
1421
export class ProjectTreeItem extends vscode.TreeItem {
1522
children: ProjectTreeItem[];
1623
fn: string = '';
@@ -58,12 +65,16 @@ export class ProjectTreeItem extends vscode.TreeItem {
5865
else if (contextType == 'project_bsp') {
5966
this.command = {
6067
title: this.name,
61-
command: 'extension.switchProject',
68+
command: 'extension.handleTreeItemClick',
6269
tooltip: this.name,
6370
arguments: [
6471
this
6572
]
6673
};
74+
75+
// with resourceUri, the item will be rendered as a file icon
76+
// then the item can be marked with a decoration
77+
this.resourceUri = vscode.Uri.file(this.fn);
6778
}
6879
}
6980
}
@@ -333,7 +344,47 @@ export function initProjectTree(context:vscode.ExtensionContext) {
333344
vscode.commands.registerCommand('extension.openTerminalProject', (arg) => {
334345
openTerminalProject(arg);
335346
});
336-
vscode.commands.registerCommand('extension.switchProject', (arg) => {
337-
setCurrentProject(arg);
347+
348+
// Add double-clicked
349+
let lastClickTime = 0;
350+
let lastClickedItem: ProjectTreeItem | null = null;
351+
352+
vscode.commands.registerCommand('extension.handleTreeItemClick', (item: ProjectTreeItem) => {
353+
const currentTime = Date.now();
354+
const doubleClickThreshold = 500; // 500ms内的两次点击被认为是双击
355+
356+
if (item.contextType === 'project_bsp') {
357+
if (lastClickedItem === item && (currentTime - lastClickTime) < doubleClickThreshold) {
358+
if (currentSelectedBspItem && currentSelectedBspItem.fn === item.fn) {
359+
return;
360+
}
361+
362+
// double clicked
363+
if (currentSelectedBspItem && currentSelectedBspItem.fn != item.fn) {
364+
DecorationProvider.getInstance().unmarkFile(vscode.Uri.file(currentSelectedBspItem.fn));
365+
}
366+
367+
currentSelectedBspItem = item;
368+
DecorationProvider.getInstance().markFile(vscode.Uri.file(item.fn));
369+
setCurrentProject(item);
370+
371+
if (_onDidChangeTreeData) {
372+
_onDidChangeTreeData.fire(undefined);
373+
}
374+
375+
// reset status
376+
lastClickTime = 0;
377+
lastClickedItem = null;
378+
} else {
379+
// one-clicked, just record it.
380+
lastClickTime = currentTime;
381+
lastClickedItem = item;
382+
}
383+
}
338384
});
339385
}
386+
387+
// 导出函数用于设置树视图刷新事件发射器
388+
export function setTreeDataChangeEmitter(emitter: vscode.EventEmitter<ProjectTreeItem | undefined>) {
389+
_onDidChangeTreeData = emitter;
390+
}

0 commit comments

Comments
 (0)