import * as vscode from "vscode"; import { changeStatus } from "@/commands/changeStatus"; import { createTask } from "@/commands/createTask"; import { deleteTask } from "@/commands/deleteTask"; import { openTask } from "@/commands/openTask"; import { resolveTaskLocations } from "@/commands/resolveTaskLocations"; import { appError, AppErrorVariant } from "@/error"; import { TaskLocation } from "@/model/taskLocation"; import { IConfigProvider, ITaskRepository } from "@/ports"; import { TaskDashboardPanel } from "@/views/taskDashboardPanel"; import { TaskKanbanPanel } from "@/views/taskKanbanPanel"; import { TaskTreeProvider } from "@/views/taskTreeProvider"; import { presentError } from "./presentError"; import { resolveTaskRef } from "./resolveTaskRef"; import { TASK_SCOPE_LABELS, TASK_STATUS_LABELS, TASK_STATUS_ORDER, } from "./taskLabels"; export type TaskCommandDeps = { repo: ITaskRepository; config: IConfigProvider; tree: TaskTreeProvider; /** Refresh dashboard (and any other listeners) after task mutations. */ onTasksMutated?: () => void; }; function dashboardDeps(deps: TaskCommandDeps) { return { repo: deps.repo, config: deps.config, onTasksMutated: () => deps.tree.refresh(), }; } function notifyMutated(deps: TaskCommandDeps): void { deps.tree.refresh(); deps.onTasksMutated?.(); } async function pickCreateLocation( config: IConfigProvider, ): Promise { const locations = resolveTaskLocations(config); if (locations.length === 0) { presentError(appError(AppErrorVariant.NO_FOLDER)); return undefined; } if (locations.length === 1) { return locations[0]; } const picked = await vscode.window.showQuickPick( locations.map((location) => ({ label: TASK_SCOPE_LABELS[location.scope], description: location.folderPath, location, })), { placeHolder: "Create task in…" }, ); return picked?.location; } export async function runCreateTask(deps: TaskCommandDeps): Promise { const title = await vscode.window.showInputBox({ prompt: "Task title", placeHolder: "Enter task title...", }); if (title === undefined) { return; } const location = await pickCreateLocation(deps.config); if (location === undefined) { return; } const result = await createTask(deps.repo, { folderPath: location.folderPath, title, }); if (result.isErr()) { presentError(result.error); return; } notifyMutated(deps); void vscode.window.showInformationMessage( `Task created (${TASK_SCOPE_LABELS[location.scope]}): ${result.value.title}`, ); } export async function runOpenTask( deps: TaskCommandDeps, item?: unknown, ): Promise { const ref = await resolveTaskRef(deps.repo, deps.config, item); if (ref === undefined) { if (resolveTaskLocations(deps.config).length === 0) { presentError(appError(AppErrorVariant.NO_FOLDER)); } return; } const result = await openTask(deps.repo, { folderPath: ref.location.folderPath, id: ref.id, }); if (result.isErr()) { presentError(result.error); return; } const uri = vscode.Uri.file(result.value.path); await vscode.window.showTextDocument(uri); } export async function runDeleteTask( deps: TaskCommandDeps, item?: unknown, ): Promise { const ref = await resolveTaskRef(deps.repo, deps.config, item); if (ref === undefined) { if (resolveTaskLocations(deps.config).length === 0) { presentError(appError(AppErrorVariant.NO_FOLDER)); } return; } const confirm = await vscode.window.showWarningMessage( "Delete this task?", { modal: true }, "Delete", ); if (confirm !== "Delete") { return; } const result = await deleteTask(deps.repo, { folderPath: ref.location.folderPath, id: ref.id, }); if (result.isErr()) { presentError(result.error); return; } notifyMutated(deps); void vscode.window.showInformationMessage("Task deleted"); } export async function runChangeStatus( deps: TaskCommandDeps, item?: unknown, ): Promise { const ref = await resolveTaskRef(deps.repo, deps.config, item); if (ref === undefined) { if (resolveTaskLocations(deps.config).length === 0) { presentError(appError(AppErrorVariant.NO_FOLDER)); } return; } const picked = await vscode.window.showQuickPick( TASK_STATUS_ORDER.map((value) => ({ label: TASK_STATUS_LABELS[value], status: value, })), { placeHolder: "New status" }, ); if (!picked) { return; } const result = await changeStatus(deps.repo, { folderPath: ref.location.folderPath, id: ref.id, status: picked.status, }); if (result.isErr()) { presentError(result.error); return; } notifyMutated(deps); void vscode.window.showInformationMessage( `Status: ${TASK_STATUS_LABELS[result.value.status]}`, ); } export async function runOpenInDashboard( deps: TaskCommandDeps, item?: unknown, ): Promise { const ref = await resolveTaskRef(deps.repo, deps.config, item); if (ref === undefined) { if (resolveTaskLocations(deps.config).length === 0) { presentError(appError(AppErrorVariant.NO_FOLDER)); } return; } TaskDashboardPanel.show(dashboardDeps(deps), { selectedId: ref.id }); } export function runOpenDashboard(deps: TaskCommandDeps): void { TaskDashboardPanel.show(dashboardDeps(deps)); } export function runOpenKanban(deps: TaskCommandDeps): void { TaskKanbanPanel.show({ repo: deps.repo, config: deps.config, onTasksMutated: () => { deps.tree.refresh(); TaskDashboardPanel.refreshIfOpen(); }, }); } /** Register all task command adapters on the extension host. */ export function registerTaskCommands( context: vscode.ExtensionContext, deps: TaskCommandDeps, ): void { context.subscriptions.push( vscode.commands.registerCommand("xboctFlow.createTask", () => runCreateTask(deps), ), vscode.commands.registerCommand("xboctFlow.openTask", (item?: unknown) => runOpenTask(deps, item), ), vscode.commands.registerCommand("xboctFlow.deleteTask", (item?: unknown) => runDeleteTask(deps, item), ), vscode.commands.registerCommand( "xboctFlow.changeStatus", (item?: unknown) => runChangeStatus(deps, item), ), vscode.commands.registerCommand("xboctFlow.openDashboard", () => runOpenDashboard(deps), ), vscode.commands.registerCommand( "xboctFlow.openInDashboard", (item?: unknown) => runOpenInDashboard(deps, item), ), vscode.commands.registerCommand("xboctFlow.openKanban", () => runOpenKanban(deps), ), ); }