fix: pick status and priority show current
This commit is contained in:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
+136
-21
@@ -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(
|
|
||||||
(status) => !TASK_STATUS_META[status].fallback,
|
const text = document.getText();
|
||||||
).map((status) => ({
|
const items: StatusItem[] = TASK_STATUS_ORDER.filter(
|
||||||
label: TASK_STATUS_LABELS[status],
|
(status) => !TASK_STATUS_META[status].fallback,
|
||||||
description: status,
|
).map((status) => ({
|
||||||
status,
|
label: TASK_STATUS_LABELS[status],
|
||||||
})),
|
description: status,
|
||||||
{ placeHolder: "Status" },
|
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(
|
|
||||||
(priority) => !TASK_PRIORITY_META[priority].fallback,
|
const text = document.getText();
|
||||||
).map((priority) => ({
|
const items: PriorityItem[] = TASK_PRIORITY_ORDER.filter(
|
||||||
label: TASK_PRIORITY_LABELS[priority],
|
(priority) => !TASK_PRIORITY_META[priority].fallback,
|
||||||
description: priority,
|
).map((priority) => ({
|
||||||
priority,
|
label: TASK_PRIORITY_LABELS[priority],
|
||||||
})),
|
description: priority,
|
||||||
{ placeHolder: "Priority" },
|
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,
|
||||||
|
|||||||
@@ -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)",
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user