feat: change ui from decorations to codelens

This commit is contained in:
2026-07-19 07:41:58 +05:00
parent c818ed551e
commit 2bf3140708
3 changed files with 95 additions and 167 deletions
+5 -5
View File
@@ -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, {
+90
View File
@@ -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,
),
);
}
-162
View File
@@ -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);
}
}
}),
);
}