From 2bf31407088bf6dfbb84f69b2dec936a9c619a71 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sun, 19 Jul 2026 07:41:58 +0500 Subject: [PATCH] feat: change ui from decorations to codelens --- src/extension.ts | 10 +-- src/ui/taskCodeLenses.ts | 90 +++++++++++++++++++++ src/ui/taskDecorations.ts | 162 -------------------------------------- 3 files changed, 95 insertions(+), 167 deletions(-) create mode 100644 src/ui/taskCodeLenses.ts delete mode 100644 src/ui/taskDecorations.ts diff --git a/src/extension.ts b/src/extension.ts index f425a8b..4815732 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,18 +1,18 @@ -import * as path from "path"; -import * as vscode from "vscode"; import { resolveTaskLocations } from "@/commands/resolveTaskLocations"; import { TASK_FILE_EXTENSION } from "@/model/taskFile"; +import { IConfigProvider } from "@/ports"; import { FsTaskRepository } from "@/storage/FsTaskRepository"; import { NodeFileSystem } from "@/storage/NodeFileSystem"; import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider"; -import { IConfigProvider } from "@/ports"; import { registerTaskCodeActions } from "@/ui/taskCodeActions"; +import { registerTaskCodeLenses } from "@/ui/taskCodeLenses"; import { registerTaskCommands } from "@/ui/taskCommands"; -import { registerTaskDecorations } from "@/ui/taskDecorations"; import { registerTaskDiagnostics } from "@/ui/taskDiagnostics"; import { TaskDashboardPanel } from "@/views/taskDashboardPanel"; import { TaskKanbanPanel } from "@/views/taskKanbanPanel"; import { TaskTreeProvider } from "@/views/taskTreeProvider"; +import * as path from "path"; +import * as vscode from "vscode"; function createTaskWatchers( config: IConfigProvider, @@ -74,7 +74,7 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(statusBarItem); registerTaskDiagnostics(context, { fs: fileSystem, config }); - registerTaskDecorations(context); + registerTaskCodeLenses(context); registerTaskCodeActions(context); registerTaskCommands(context, { diff --git a/src/ui/taskCodeLenses.ts b/src/ui/taskCodeLenses.ts new file mode 100644 index 0000000..5ff7a25 --- /dev/null +++ b/src/ui/taskCodeLenses.ts @@ -0,0 +1,90 @@ +import { findFieldRange } from "@/frontmatter/frontmatterFields"; +import { + isTaskFileName, + TASK_FILE_EXTENSION, + TASK_LANGUAGE_ID, +} from "@/model/taskFile"; +import * as vscode from "vscode"; +import { EditableField } from "./editFrontmatterField"; + +const NBSP = "\u00A0"; + +type FieldLensDef = { + field: EditableField; + icon: string; + label: string; + command: string; +}; + +const FIELDS: FieldLensDef[] = [ + { + field: EditableField.STATUS, + icon: "$(symbol-misc)", + label: "Change status", + command: "xboctukFlow.editTaskStatus", + }, + { + field: EditableField.PRIORITY, + icon: "$(arrow-up)", + label: "Change priority", + command: "xboctukFlow.editTaskPriority", + }, + { + field: EditableField.TAGS, + icon: "$(tag)", + label: "Edit tags", + command: "xboctukFlow.editTaskTags", + }, + { + field: EditableField.ASSIGNEE, + icon: "$(account)", + label: "Assign me", + command: "xboctukFlow.setAssigneeMe", + }, +]; + +function isTaskDocument(document: vscode.TextDocument): boolean { + if (document.languageId === TASK_LANGUAGE_ID) return true; + return isTaskFileName(document.fileName); +} + +class TaskFieldCodeLensProvider implements vscode.CodeLensProvider { + provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] { + if (!isTaskDocument(document)) return []; + + const lenses: vscode.CodeLens[] = []; + const text = document.getText(); + const uri = document.uri.toString(); + + for (const { field, icon, label, command } of FIELDS) { + const range = findFieldRange(text, field); + if (!range) continue; + + lenses.push( + new vscode.CodeLens( + new vscode.Range(range.startLine, 0, range.startLine, 0), + { + title: `${icon}${NBSP}${label}`, + command, + arguments: [uri], + }, + ), + ); + } + + return lenses; + } +} + +export function registerTaskCodeLenses(context: vscode.ExtensionContext): void { + const provider = new TaskFieldCodeLensProvider(); + context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + [ + { language: TASK_LANGUAGE_ID }, + { pattern: `**/*${TASK_FILE_EXTENSION}` }, + ], + provider, + ), + ); +} diff --git a/src/ui/taskDecorations.ts b/src/ui/taskDecorations.ts deleted file mode 100644 index b97e39b..0000000 --- a/src/ui/taskDecorations.ts +++ /dev/null @@ -1,162 +0,0 @@ -import * as vscode from "vscode"; -import { findFieldRange } from "@/frontmatter/frontmatterFields"; -import { isTaskFileName, TASK_LANGUAGE_ID } from "@/model/taskFile"; -import { EditableField } from "./editFrontmatterField"; - -function isTaskEditor(editor: vscode.TextEditor): boolean { - const { document } = editor; - if (document.languageId === TASK_LANGUAGE_ID) { - return true; - } - return isTaskFileName(document.fileName); -} - -function commandUri(command: string, args: unknown[]): string { - return `command:${command}?${encodeURIComponent(JSON.stringify(args))}`; -} - -function hoverEditField( - document: vscode.TextDocument, - field: typeof EditableField.STATUS | typeof EditableField.PRIORITY, -): vscode.MarkdownString { - const command = - field === EditableField.STATUS - ? "xboctukFlow.editTaskStatus" - : "xboctukFlow.editTaskPriority"; - const label = - field === EditableField.STATUS ? "Change status…" : "Change priority…"; - const md = new vscode.MarkdownString( - `[$(edit) ${label}](${commandUri(command, [document.uri.toString()])})`, - ); - md.isTrusted = true; - md.supportThemeIcons = true; - return md; -} - -function hoverAssigneeMe(document: vscode.TextDocument): vscode.MarkdownString { - const md = new vscode.MarkdownString( - `[$(account) me](${commandUri("xboctukFlow.setAssigneeMe", [document.uri.toString()])}) — set assignee from \`git config user.name\``, - ); - md.isTrusted = true; - md.supportThemeIcons = true; - return md; -} - -function hoverTags(document: vscode.TextDocument): vscode.MarkdownString { - const uri = document.uri.toString(); - const md = new vscode.MarkdownString( - `[$(edit) Edit tags…](${commandUri("xboctukFlow.editTaskTags", [uri])})`, - ); - md.isTrusted = true; - md.supportThemeIcons = true; - return md; -} - -export function registerTaskDecorations( - context: vscode.ExtensionContext, -): void { - const decorationType = vscode.window.createTextEditorDecorationType({ - after: { - margin: "0 0 0 1.5em", - color: new vscode.ThemeColor("editorCodeLens.foreground"), - }, - }); - context.subscriptions.push(decorationType); - - const refresh = (editor: vscode.TextEditor | undefined): void => { - if (!editor || !isTaskEditor(editor)) { - if (editor) { - editor.setDecorations(decorationType, []); - } - return; - } - - const text = editor.document.getText(); - const options: vscode.DecorationOptions[] = []; - - for (const field of [ - EditableField.STATUS, - EditableField.PRIORITY, - ] as const) { - const range = findFieldRange(text, field); - if (!range) continue; - - options.push({ - range: new vscode.Range( - range.startLine, - range.startCol, - range.endLine, - range.endCol, - ), - hoverMessage: hoverEditField(editor.document, field), - renderOptions: { - after: { - contentText: " ✎", - }, - }, - }); - } - - const assigneeRange = findFieldRange(text, EditableField.ASSIGNEE); - if (assigneeRange) { - options.push({ - range: new vscode.Range( - assigneeRange.startLine, - assigneeRange.startCol, - assigneeRange.endLine, - assigneeRange.endCol, - ), - hoverMessage: hoverAssigneeMe(editor.document), - renderOptions: { - after: { - contentText: " me", - color: new vscode.ThemeColor("textLink.foreground"), - }, - }, - }); - } - - const tagsRange = findFieldRange(text, EditableField.TAGS); - if (tagsRange) { - const lines = text.split(/\r?\n/); - const lineEnd = lines[tagsRange.startLine]?.length ?? 0; - options.push({ - range: new vscode.Range( - tagsRange.startLine, - lineEnd, - tagsRange.startLine, - lineEnd, - ), - hoverMessage: hoverTags(editor.document), - renderOptions: { - after: { - contentText: " ✎ tags", - color: new vscode.ThemeColor("textLink.foreground"), - }, - }, - }); - } - - editor.setDecorations(decorationType, options); - }; - - const refreshVisible = (): void => { - for (const editor of vscode.window.visibleTextEditors) { - refresh(editor); - } - }; - - refreshVisible(); - - context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor(refresh), - vscode.window.onDidChangeVisibleTextEditors(() => refreshVisible()), - vscode.workspace.onDidChangeTextDocument((event) => { - for (const editor of vscode.window.visibleTextEditors) { - if (editor.document === event.document) { - refresh(editor); - } - } - }), - ); -}