From 4704a1d5a06e61a8273c8522fc66e8438e9299b0 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sun, 12 Jul 2026 21:02:51 +0500 Subject: [PATCH] feat: add sidebar panel --- src/extension.ts | 18 +++++++ src/views/taskTreeProvider.ts | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/views/taskTreeProvider.ts diff --git a/src/extension.ts b/src/extension.ts index e866325..b5a44bc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,10 +1,28 @@ +import * as path from "path"; import * as vscode from "vscode"; import { ConfigStore } from "./storage/configStore"; import { TaskStore } from "./storage/taskStore"; +import { TaskTreeProvider } from "./views/taskTreeProvider"; export function activate(context: vscode.ExtensionContext) { const taskStore = new TaskStore(); const configStore = new ConfigStore(); + const taskTreeProvider = new TaskTreeProvider(); + + vscode.window.registerTreeDataProvider( + "projectTasks.tasks", + taskTreeProvider, + ); + + const projectPath = configStore.getProjectTaskPath(); + if (projectPath) { + const taskPattern = path.join(projectPath, "*.task.md").replace(/\\/g, "/"); + const watcher = vscode.workspace.createFileSystemWatcher(taskPattern); + watcher.onDidCreate(() => taskTreeProvider.refresh()); + watcher.onDidChange(() => taskTreeProvider.refresh()); + watcher.onDidDelete(() => taskTreeProvider.refresh()); + context.subscriptions.push(watcher); + } const statusBarItem = vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, diff --git a/src/views/taskTreeProvider.ts b/src/views/taskTreeProvider.ts new file mode 100644 index 0000000..d13185c --- /dev/null +++ b/src/views/taskTreeProvider.ts @@ -0,0 +1,88 @@ +import * as vscode from "vscode"; +import { Task, TaskStatus } from "../model/task"; +import { TaskStore } from "../storage/taskStore"; +import { ConfigStore } from "../storage/configStore"; + +const STATUS_ORDER: TaskStatus[] = ["todo", "in-progress", "done", "cancelled"]; + +const STATUS_ICONS: Record = { + todo: new vscode.ThemeIcon("circle-outline"), + "in-progress": new vscode.ThemeIcon("sync"), + done: new vscode.ThemeIcon("check"), + cancelled: new vscode.ThemeIcon("close"), +}; + +class TaskGroupTreeItem extends vscode.TreeItem { + constructor( + label: string, + public readonly tasks: Task[], + icon: vscode.ThemeIcon, + ) { + super(label, vscode.TreeItemCollapsibleState.Collapsed); + this.iconPath = icon; + this.description = `(${tasks.length})`; + } +} + +export class TaskTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter< + vscode.TreeItem | undefined | null | void + >(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private taskStore = new TaskStore(); + private configStore = new ConfigStore(); + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: vscode.TreeItem): vscode.TreeItem { + return element; + } + + async getChildren(element?: vscode.TreeItem): Promise { + if (!element) { + return this.getRootChildren(); + } + return this.getTaskChildren(element as TaskGroupTreeItem); + } + + private async getRootChildren(): Promise { + const projectPath = this.configStore.getProjectTaskPath(); + if (!projectPath) { + return [new vscode.TreeItem("Open a workspace folder to start")]; + } + + const tasks = await this.taskStore.listTasks(projectPath); + + return STATUS_ORDER.map((status) => { + const groupTasks = tasks.filter((t) => t.status === status); + const label = status === "todo" ? "To Do" + : status === "in-progress" ? "In Progress" + : status === "done" ? "Done" + : "Cancelled"; + const item = new TaskGroupTreeItem( + label, + groupTasks, + STATUS_ICONS[status], + ); + item.id = `group-${status}`; + item.contextValue = "taskGroup"; + return item; + }); + } + + private getTaskChildren(group: TaskGroupTreeItem): vscode.TreeItem[] { + return group.tasks.map((task) => { + const item = new vscode.TreeItem(task.title); + item.id = task.id; + item.description = task.priority; + item.tooltip = new vscode.MarkdownString( + `**${task.title}**\n\nPriority: ${task.priority}\nStatus: ${task.status}${task.assignee ? `\nAssignee: ${task.assignee}` : ""}`, + ); + item.contextValue = "task"; + return item; + }); + } +}