Indent deepest task leaves
This commit is contained in:
@@ -98,7 +98,7 @@ A task starts timing the first time it enters `in-progress`. Leaving that state
|
|||||||
|
|
||||||
## Pi Widget
|
## Pi Widget
|
||||||
|
|
||||||
The widget appears above Pi's editor while todos exist. Run `/todo` to hide or show it for the current Pi runtime. Ten seconds after every task is completed, the task tree is automatically deleted and task IDs reset to `1`; adding or reopening unfinished work during that delay cancels the clear. Its header is the virtual task-tree root, with connectors extending from it through every top-level task so the display always remains a tree. The outline uses only the labels needed by the deepest task: one-level trees render every task as an unlabeled leaf, two-level trees use numbered parents and unlabeled leaves, and three-level trees use Roman-numeral categories, numbered items, and unlabeled leaves. Internal task IDs remain unchanged and visible in tool output. In-progress circles rotate through `◐ ◓ ◑ ◒` twice per second; other status symbols remain still. The widget shows at most 12 task rows plus a compact count of hidden tasks, so larger plans do not crowd the editor out of an 80×24 terminal.
|
The widget appears above Pi's editor while todos exist. Run `/todo` to hide or show it for the current Pi runtime. Ten seconds after every task is completed, the task tree is automatically deleted and task IDs reset to `1`; adding or reopening unfinished work during that delay cancels the clear. Its header is the virtual task-tree root, with connectors extending from it through every top-level task so the display always remains a tree. Content at the deepest leaf level is indented one additional tree step in both the widget and expanded list output so shallower numbered items remain aligned. The outline uses only the labels needed by the deepest task: one-level trees render every task as an unlabeled leaf, two-level trees use numbered parents and unlabeled leaves, and three-level trees use Roman-numeral categories, numbered items, and unlabeled leaves. Internal task IDs remain unchanged and visible in tool output. In-progress circles rotate through `◐ ◓ ◑ ◒` twice per second; other status symbols remain still. The widget shows at most 12 task rows plus a compact count of hidden tasks, so larger plans do not crowd the editor out of an 80×24 terminal.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Todos 2/6 · !1 ━━━━━────────
|
Todos 2/6 · !1 ━━━━━────────
|
||||||
|
|||||||
@@ -151,8 +151,32 @@ export function renderTaskLine(task: Task, prefix: string, theme: Theme, width:
|
|||||||
return renderTaskLineWithSuffix(task, prefix, theme, width);
|
return renderTaskLineWithSuffix(task, prefix, theme, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STATUS_ICON_WIDTH = 1;
|
||||||
|
const TRUNCATED_STATUS_WIDTH = STATUS_ICON_WIDTH + 3;
|
||||||
|
|
||||||
|
function indentDeepestLeaf(
|
||||||
|
prefix: string,
|
||||||
|
depth: number,
|
||||||
|
maximumDepth: number,
|
||||||
|
width: number,
|
||||||
|
minimumContentWidth: number,
|
||||||
|
): string {
|
||||||
|
if (depth === 0 || depth !== maximumDepth) return prefix;
|
||||||
|
|
||||||
|
const indentedPrefix = `${prefix} `;
|
||||||
|
return visibleWidth(indentedPrefix) + minimumContentWidth <= width ? indentedPrefix : prefix;
|
||||||
|
}
|
||||||
|
|
||||||
export function renderTree(tasks: Task[], theme: Theme, width: number, padding = " "): string[] {
|
export function renderTree(tasks: Task[], theme: Theme, width: number, padding = " "): string[] {
|
||||||
return orderedTreeRows(tasks).map(({ task, prefix }) => renderTaskLine(task, `${padding}${prefix}`, theme, width));
|
const maximumDepth = maximumTaskDepth(tasks);
|
||||||
|
return orderedTreeRows(tasks).map(({ task, prefix, depth }) =>
|
||||||
|
renderTaskLine(
|
||||||
|
task,
|
||||||
|
indentDeepestLeaf(`${padding}${prefix}`, depth, maximumDepth, width, TRUNCATED_STATUS_WIDTH),
|
||||||
|
theme,
|
||||||
|
width,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toRomanNumeral(value: number): string {
|
function toRomanNumeral(value: number): string {
|
||||||
@@ -214,7 +238,16 @@ export function renderWidgetTree(
|
|||||||
const lines = visibleRows.map(({ task, prefix, depth, siblingIndex, collapsed }) => {
|
const lines = visibleRows.map(({ task, prefix, depth, siblingIndex, collapsed }) => {
|
||||||
const summary = collapsed === undefined ? undefined : `${collapsed.done}/${collapsed.total} done`;
|
const summary = collapsed === undefined ? undefined : `${collapsed.done}/${collapsed.total} done`;
|
||||||
const identifier = widgetOutlineLabel(depth, siblingIndex, maximumDepth);
|
const identifier = widgetOutlineLabel(depth, siblingIndex, maximumDepth);
|
||||||
return renderTaskLineWithSuffix(task, `${padding}${prefix}`, theme, width, summary, identifier, true, inProgressFrame);
|
return renderTaskLineWithSuffix(
|
||||||
|
task,
|
||||||
|
indentDeepestLeaf(`${padding}${prefix}`, depth, maximumDepth, width, STATUS_ICON_WIDTH),
|
||||||
|
theme,
|
||||||
|
width,
|
||||||
|
summary,
|
||||||
|
identifier,
|
||||||
|
true,
|
||||||
|
inProgressFrame,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (rows.length <= MAX_WIDGET_TASK_ROWS) return lines;
|
if (rows.length <= MAX_WIDGET_TASK_ROWS) return lines;
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,10 @@ test("uses only the outline levels needed while leaving leaf tasks unlabeled", (
|
|||||||
assert.match(statusPriority[3] ?? "", /○/);
|
assert.match(statusPriority[3] ?? "", /○/);
|
||||||
const childStatusPriority = renderWidgetTree(state.list(), plainTheme as never, 10).map(stripAnsi);
|
const childStatusPriority = renderWidgetTree(state.list(), plainTheme as never, 10).map(stripAnsi);
|
||||||
assert.match(childStatusPriority[4] ?? "", /○/);
|
assert.match(childStatusPriority[4] ?? "", /○/);
|
||||||
|
const leafStatusPriority = renderWidgetTree(state.list(), plainTheme as never, 12).map(stripAnsi);
|
||||||
|
assert.match(leafStatusPriority[5] ?? "", /○/);
|
||||||
|
const listLeafStatusPriority = renderTree(state.list(), plainTheme as never, 12).map(stripAnsi);
|
||||||
|
assert.match(listLeafStatusPriority[5] ?? "", /○/);
|
||||||
|
|
||||||
const timedState = new TaskState(() => 0);
|
const timedState = new TaskState(() => 0);
|
||||||
timedState.add([
|
timedState.add([
|
||||||
|
|||||||
Reference in New Issue
Block a user