fix: pick status and priority show current

This commit is contained in:
2026-07-19 08:05:10 +05:00
parent 2bf3140708
commit 01661dc60e
4 changed files with 208 additions and 21 deletions
+60
View File
@@ -0,0 +1,60 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("vscode", () => ({}));
import { getCurrentFieldValue } from "./editFrontmatterField";
const inlineSample = `---
title: Test
status: in-progress
priority: high
tags:
- bug
- urgent
assignee: me
---
Body
`;
const quotedSample = `---
status: "done"
priority: 'low'
---
Body
`;
describe("getCurrentFieldValue", () => {
it("returns undefined when field is missing", () => {
expect(getCurrentFieldValue(inlineSample, "nonexistent")).toBeUndefined();
});
it("returns inline status value", () => {
expect(getCurrentFieldValue(inlineSample, "status")).toBe("in-progress");
});
it("returns inline priority value", () => {
expect(getCurrentFieldValue(inlineSample, "priority")).toBe("high");
});
it("returns assignee value", () => {
expect(getCurrentFieldValue(inlineSample, "assignee")).toBe("me");
});
it("strips double quotes", () => {
expect(getCurrentFieldValue(quotedSample, "status")).toBe("done");
});
it("strips single quotes", () => {
expect(getCurrentFieldValue(quotedSample, "priority")).toBe("low");
});
it("returns undefined for empty frontmatter", () => {
expect(getCurrentFieldValue("---\n---\n\nBody", "status")).toBeUndefined();
});
it("returns undefined for document without frontmatter", () => {
expect(getCurrentFieldValue("Just body text", "status")).toBeUndefined();
});
});
+126 -11
View File
@@ -8,7 +8,7 @@ import {
setFrontmatterTags, setFrontmatterTags,
} from "@/frontmatter/frontmatterFields"; } from "@/frontmatter/frontmatterFields";
import { parseTaskTags } from "@/frontmatter/parseTaskTags"; import { parseTaskTags } from "@/frontmatter/parseTaskTags";
import { normalizeTag } from "@/model/projectConfig"; import { getProjectConfigPath, normalizeTag } from "@/model/projectConfig";
import { import {
isTaskFileName, isTaskFileName,
TASK_FILE_EXTENSION, TASK_FILE_EXTENSION,
@@ -19,6 +19,8 @@ import * as vscode from "vscode";
import { getGitUserName, yamlInlineScalar } from "./getGitUserName"; import { getGitUserName, yamlInlineScalar } from "./getGitUserName";
import { presentError } from "./presentError"; import { presentError } from "./presentError";
import { resolveTaskFolderForPath } from "./resolveTaskFolder"; import { resolveTaskFolderForPath } from "./resolveTaskFolder";
import { type TaskPriority } from "@/model/taskPriority";
import { type TaskStatus } from "@/model/taskStatus";
import { import {
TASK_PRIORITY_LABELS, TASK_PRIORITY_LABELS,
TASK_PRIORITY_META, TASK_PRIORITY_META,
@@ -107,6 +109,20 @@ async function applyFieldValue(
return vscode.workspace.applyEdit(edit); return vscode.workspace.applyEdit(edit);
} }
export function getCurrentFieldValue(text: string, field: string): string | undefined {
const range = findFieldRange(text, field);
if (!range) return undefined;
const lines = text.split(/\r?\n/);
const raw = lines[range.startLine]!.slice(range.startCol, range.endCol).trim();
if (
(raw.startsWith('"') && raw.endsWith('"')) ||
(raw.startsWith("'") && raw.endsWith("'"))
) {
return raw.slice(1, raw.length - 1);
}
return raw;
}
export async function runEditTaskStatus(uriArg?: string): Promise<void> { export async function runEditTaskStatus(uriArg?: string): Promise<void> {
const document = await resolveDocument(uriArg); const document = await resolveDocument(uriArg);
if (!document) { if (!document) {
@@ -116,16 +132,49 @@ export async function runEditTaskStatus(uriArg?: string): Promise<void> {
return; return;
} }
const picked = await vscode.window.showQuickPick( type StatusItem = vscode.QuickPickItem & { status: TaskStatus };
TASK_STATUS_ORDER.filter(
const text = document.getText();
const items: StatusItem[] = TASK_STATUS_ORDER.filter(
(status) => !TASK_STATUS_META[status].fallback, (status) => !TASK_STATUS_META[status].fallback,
).map((status) => ({ ).map((status) => ({
label: TASK_STATUS_LABELS[status], label: TASK_STATUS_LABELS[status],
description: status, description: status,
status, status,
})), }));
{ placeHolder: "Status" },
); const currentValue = getCurrentFieldValue(text, EditableField.STATUS);
const activeItem = currentValue
? items.find((item) => item.status === currentValue)
: undefined;
const picked = await new Promise<StatusItem | undefined>((resolve) => {
const qp = vscode.window.createQuickPick<StatusItem>();
qp.items = items;
qp.placeholder = "Status";
if (activeItem) qp.activeItems = [activeItem];
let accepted = false;
qp.onDidAccept(() => {
accepted = true;
const picked = qp.activeItems[0];
if (!picked) return;
qp.hide();
qp.dispose();
resolve(picked);
});
qp.onDidHide(() => {
if (!accepted) {
qp.dispose();
resolve(undefined);
}
});
qp.show();
});
if (!picked) return; if (!picked) return;
await applyFieldValue(document, EditableField.STATUS, picked.status); await applyFieldValue(document, EditableField.STATUS, picked.status);
@@ -140,16 +189,49 @@ export async function runEditTaskPriority(uriArg?: string): Promise<void> {
return; return;
} }
const picked = await vscode.window.showQuickPick( type PriorityItem = vscode.QuickPickItem & { priority: TaskPriority };
TASK_PRIORITY_ORDER.filter(
const text = document.getText();
const items: PriorityItem[] = TASK_PRIORITY_ORDER.filter(
(priority) => !TASK_PRIORITY_META[priority].fallback, (priority) => !TASK_PRIORITY_META[priority].fallback,
).map((priority) => ({ ).map((priority) => ({
label: TASK_PRIORITY_LABELS[priority], label: TASK_PRIORITY_LABELS[priority],
description: priority, description: priority,
priority, priority,
})), }));
{ placeHolder: "Priority" },
); const currentValue = getCurrentFieldValue(text, EditableField.PRIORITY);
const activeItem = currentValue
? items.find((item) => item.priority === currentValue)
: undefined;
const picked = await new Promise<PriorityItem | undefined>((resolve) => {
const qp = vscode.window.createQuickPick<PriorityItem>();
qp.items = items;
qp.placeholder = "Priority";
if (activeItem) qp.activeItems = [activeItem];
let accepted = false;
qp.onDidAccept(() => {
accepted = true;
const picked = qp.activeItems[0];
if (!picked) return;
qp.hide();
qp.dispose();
resolve(picked);
});
qp.onDidHide(() => {
if (!accepted) {
qp.dispose();
resolve(undefined);
}
});
qp.show();
});
if (!picked) return; if (!picked) return;
await applyFieldValue(document, EditableField.PRIORITY, picked.priority); await applyFieldValue(document, EditableField.PRIORITY, picked.priority);
@@ -180,6 +262,39 @@ export async function runSetAssigneeMe(uriArg?: string): Promise<void> {
); );
} }
export async function runOpenProjectConfig(
deps: FrontmatterEditDeps,
uriArg?: string,
): Promise<void> {
const document = await resolveDocument(uriArg);
if (!document) {
void vscode.window.showErrorMessage(
`Open a ${TASK_FILE_EXTENSION} task file first`,
);
return;
}
const folder = await resolveTaskFolder(document, deps.config);
if (!folder) {
void vscode.window.showErrorMessage(
"Task file is outside a configured task folder",
);
return;
}
const configPath = getProjectConfigPath(folder);
const uri = vscode.Uri.file(configPath);
try {
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
} catch {
void vscode.window.showErrorMessage(
`Could not open config file: ${configPath}`,
);
}
}
async function resolveTaskFolder( async function resolveTaskFolder(
document: vscode.TextDocument, document: vscode.TextDocument,
config: IConfigProvider, config: IConfigProvider,
+6
View File
@@ -35,6 +35,12 @@ const FIELDS: FieldLensDef[] = [
label: "Edit tags", label: "Edit tags",
command: "xboctukFlow.editTaskTags", command: "xboctukFlow.editTaskTags",
}, },
{
field: EditableField.TAGS,
icon: "$(gear)",
label: "Configure project tags",
command: "xboctukFlow.openProjectConfig",
},
{ {
field: EditableField.ASSIGNEE, field: EditableField.ASSIGNEE,
icon: "$(account)", icon: "$(account)",
+6
View File
@@ -16,6 +16,7 @@ import {
runEditTaskPriority, runEditTaskPriority,
runEditTaskStatus, runEditTaskStatus,
runEditTaskTags, runEditTaskTags,
runOpenProjectConfig,
runSetAssigneeMe, runSetAssigneeMe,
} from "./editFrontmatterField"; } from "./editFrontmatterField";
import { presentError } from "./presentError"; import { presentError } from "./presentError";
@@ -267,6 +268,11 @@ export function registerTaskCommands(
(uri?: string) => (uri?: string) =>
runEditTaskTags({ fs: deps.fs, config: deps.config }, uri), runEditTaskTags({ fs: deps.fs, config: deps.config }, uri),
), ),
vscode.commands.registerCommand(
"xboctukFlow.openProjectConfig",
(uri?: string) =>
runOpenProjectConfig({ fs: deps.fs, config: deps.config }, uri),
),
vscode.commands.registerCommand( vscode.commands.registerCommand(
"xboctukFlow.addTagToProject", "xboctukFlow.addTagToProject",
(uri?: string, tag?: string) => (uri?: string, tag?: string) =>