fix(wow-statusline): stopped hiding working indicator when applying statusline UI
- Removed setWorkingVisible(false) call in applyStatuslineUI that hid the working indicator. - Removed setWorkingVisible(true) and setWorkingIndicator() calls in clearStatuslineUI on teardown. - Extracted installStatusWidget and installEditor functions from applyStatuslineUI. - Consolidated duplicate inline event handlers into shared refreshOnEvent function. - Extracted buildTopBorderLine and findBottomBorderIndex helpers in StatuslineEditor.
This commit is contained in:
@@ -4,7 +4,7 @@ import type {
|
|||||||
} from "@earendil-works/pi-coding-agent";
|
} from "@earendil-works/pi-coding-agent";
|
||||||
import { registerModule, resetWowConfigCache } from "wow-core";
|
import { registerModule, resetWowConfigCache } from "wow-core";
|
||||||
import { getStatuslineConfig } from "./config";
|
import { getStatuslineConfig } from "./config";
|
||||||
import { TAG, log } from "./global";
|
import { log, TAG } from "./global";
|
||||||
import { createState } from "./state";
|
import { createState } from "./state";
|
||||||
import {
|
import {
|
||||||
applyStatuslineUI,
|
applyStatuslineUI,
|
||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
updateStatuslineState,
|
updateStatuslineState,
|
||||||
} from "./ui";
|
} from "./ui";
|
||||||
|
|
||||||
|
const STREAMING_REFRESH_INTERVAL_MS = 80;
|
||||||
|
|
||||||
const setup = (pi: ExtensionAPI): void => {
|
const setup = (pi: ExtensionAPI): void => {
|
||||||
const state = createState();
|
const state = createState();
|
||||||
let lastStreamingRefresh = 0;
|
let lastStreamingRefresh = 0;
|
||||||
@@ -26,47 +28,36 @@ const setup = (pi: ExtensionAPI): void => {
|
|||||||
updateStatuslineState(state);
|
updateStatuslineState(state);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refreshOnEvent = async (): Promise<void> => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
state.thinkingLevel = pi.getThinkingLevel();
|
state.thinkingLevel = pi.getThinkingLevel();
|
||||||
apply(ctx);
|
apply(ctx);
|
||||||
log.debug("statusline applied", { cwd: ctx.cwd });
|
log.debug("statusline applied", { cwd: ctx.cwd });
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.on("agent_start", async () => {
|
pi.on("agent_start", refreshOnEvent);
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("message_update", async () => {
|
pi.on("message_update", async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastStreamingRefresh < 80) return;
|
if (now - lastStreamingRefresh < STREAMING_REFRESH_INTERVAL_MS) return;
|
||||||
lastStreamingRefresh = now;
|
lastStreamingRefresh = now;
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.on("model_select", async () => {
|
pi.on("model_select", refreshOnEvent);
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("thinking_level_select", async (event) => {
|
pi.on("thinking_level_select", async (event) => {
|
||||||
state.thinkingLevel = event.level;
|
state.thinkingLevel = event.level;
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.on("turn_end", async () => {
|
pi.on("turn_end", refreshOnEvent);
|
||||||
refresh();
|
pi.on("agent_end", refreshOnEvent);
|
||||||
});
|
pi.on("session_tree", refreshOnEvent);
|
||||||
|
pi.on("session_compact", refreshOnEvent);
|
||||||
pi.on("agent_end", async () => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_tree", async () => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_compact", async () => {
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_shutdown", async (_event, ctx) => {
|
pi.on("session_shutdown", async (_event, ctx) => {
|
||||||
clearStatuslineUI(ctx, state);
|
clearStatuslineUI(ctx, state);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import type { Component, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
import type { Component, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
||||||
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
||||||
import { buildEditorTopLine, buildStatusSummary } from "./format";
|
import { buildEditorTopLine, buildStatusSummary } from "./format";
|
||||||
import { snapshotFromContext, type StatuslineState } from "./state";
|
import { type StatuslineState, snapshotFromContext } from "./state";
|
||||||
import type { ResolvedStatuslineConfig } from "./types";
|
import type { ResolvedStatuslineConfig } from "./types";
|
||||||
|
|
||||||
const ANSI_SGR_RE = /\u001b\[[0-9;]*m/g;
|
const ANSI_SGR_RE = /\u001b\[[0-9;]*m/g;
|
||||||
@@ -50,46 +50,62 @@ export class StatuslineEditor extends CustomEditor {
|
|||||||
this.statuslineState,
|
this.statuslineState,
|
||||||
this.extensionCtx,
|
this.extensionCtx,
|
||||||
);
|
);
|
||||||
let bottom = lines.length - 1;
|
|
||||||
for (let index = lines.length - 1; index >= 1; index--) {
|
|
||||||
const plain = lines[index]!.replace(ANSI_SGR_RE, "");
|
|
||||||
if (plain.length > 0 && /^─{3,}/.test(plain)) {
|
|
||||||
bottom = index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const topText = buildEditorTopLine(theme, snapshot, this.statuslineConfig);
|
|
||||||
const folder = this.statuslineConfig.editorStyle.folderIcon;
|
|
||||||
const content = `${folder} ${topText}`;
|
|
||||||
const border = theme.fg("borderMuted", "─");
|
const border = theme.fg("borderMuted", "─");
|
||||||
const contentWidth = visibleWidth(content);
|
const borderLine = border.repeat(Math.max(0, width));
|
||||||
const remaining = width - contentWidth - 2;
|
const topContent = `${this.statuslineConfig.editorStyle.folderIcon} ${buildEditorTopLine(theme, snapshot, this.statuslineConfig)}`;
|
||||||
|
const remainingBorderWidth = width - visibleWidth(topContent) - 2;
|
||||||
|
|
||||||
lines[0] =
|
lines[0] = buildTopBorderLine(
|
||||||
remaining >= 1
|
topContent,
|
||||||
? content + theme.fg("dim", " ") + border.repeat(remaining) + border
|
border,
|
||||||
: border.repeat(Math.max(0, width));
|
borderLine,
|
||||||
|
theme,
|
||||||
|
remainingBorderWidth,
|
||||||
|
);
|
||||||
|
|
||||||
if (lines.length > 1) {
|
lines[1] =
|
||||||
lines[1] =
|
theme.fg("accent", this.statuslineConfig.editorStyle.prompt) +
|
||||||
theme.fg("accent", this.statuslineConfig.editorStyle.prompt) +
|
theme.fg("dim", " ") +
|
||||||
theme.fg("dim", " ") +
|
(lines[1] ?? "");
|
||||||
(lines[1] ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
lines[bottom] = border.repeat(Math.max(0, width));
|
lines[findBottomBorderIndex(lines)] = borderLine;
|
||||||
|
|
||||||
for (let index = 0; index < lines.length; index++) {
|
for (let index = 0; index < lines.length; index++) {
|
||||||
if (visibleWidth(lines[index]!) > width) {
|
if (visibleWidth(lines[index]!) <= width) continue;
|
||||||
lines[index] = truncateToWidth(lines[index]!, width);
|
lines[index] = truncateToWidth(lines[index]!, width);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildTopBorderLine(
|
||||||
|
content: string,
|
||||||
|
border: string,
|
||||||
|
borderLine: string,
|
||||||
|
theme: Theme,
|
||||||
|
remainingBorderWidth: number,
|
||||||
|
): string {
|
||||||
|
if (remainingBorderWidth < 1) return borderLine;
|
||||||
|
return (
|
||||||
|
content +
|
||||||
|
theme.fg("dim", " ") +
|
||||||
|
border.repeat(remainingBorderWidth) +
|
||||||
|
border
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findBottomBorderIndex(lines: string[]): number {
|
||||||
|
for (let index = lines.length - 1; index >= 1; index--) {
|
||||||
|
const plain = lines[index]!.replace(ANSI_SGR_RE, "");
|
||||||
|
if (plain.length > 0 && /^─{3,}/.test(plain)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildStatuslineWidget(
|
export function buildStatuslineWidget(
|
||||||
ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
state: StatuslineState,
|
state: StatuslineState,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
||||||
import { buildEditorTopLine, buildStatusSummary, EMPTY_USAGE } from "./format";
|
import { buildEditorTopLine, buildStatusSummary, EMPTY_USAGE } from "./format";
|
||||||
|
import { type StatuslineState, syncFooterSnapshot } from "./state";
|
||||||
import {
|
import {
|
||||||
|
buildStatuslineWidget,
|
||||||
EmptyFooter,
|
EmptyFooter,
|
||||||
StatuslineEditor,
|
StatuslineEditor,
|
||||||
buildStatuslineWidget,
|
|
||||||
} from "./statusline-editor";
|
} from "./statusline-editor";
|
||||||
import { syncFooterSnapshot, type StatuslineState } from "./state";
|
|
||||||
import type { FooterDataSnapshot, ResolvedStatuslineConfig } from "./types";
|
import type { FooterDataSnapshot, ResolvedStatuslineConfig } from "./types";
|
||||||
|
|
||||||
const STATUS_WIDGET_ID = "wow-statusline:summary";
|
const STATUS_WIDGET_ID = "wow-statusline:summary";
|
||||||
@@ -61,26 +61,10 @@ export function applyStatuslineUI(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ui.setWorkingVisible(false);
|
|
||||||
hijackFooter(ctx, state);
|
hijackFooter(ctx, state);
|
||||||
installEmptyFooter(ctx, state);
|
installEmptyFooter(ctx, state);
|
||||||
|
installStatusWidget(ctx, state, config);
|
||||||
ctx.ui.setWidget(
|
installEditor(ctx, state, config);
|
||||||
STATUS_WIDGET_ID,
|
|
||||||
(_tui, theme) => ({
|
|
||||||
render(width: number): string[] {
|
|
||||||
const render = buildStatuslineWidget(ctx, state, config);
|
|
||||||
return render(width, theme);
|
|
||||||
},
|
|
||||||
invalidate() {},
|
|
||||||
}),
|
|
||||||
{ placement: "belowEditor" },
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.ui.setEditorComponent(
|
|
||||||
(tui, theme, keybindings) =>
|
|
||||||
new StatuslineEditor(tui, theme, keybindings, ctx, state, config),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateStatuslineState(state: StatuslineState): void {
|
export function updateStatuslineState(state: StatuslineState): void {
|
||||||
@@ -102,8 +86,6 @@ export function clearStatuslineUI(
|
|||||||
ctx.ui.setWidget(STATUS_WIDGET_ID, undefined, { placement: "belowEditor" });
|
ctx.ui.setWidget(STATUS_WIDGET_ID, undefined, { placement: "belowEditor" });
|
||||||
ctx.ui.setWidget(STATUS_WIDGET_ID, undefined, { placement: "aboveEditor" });
|
ctx.ui.setWidget(STATUS_WIDGET_ID, undefined, { placement: "aboveEditor" });
|
||||||
ctx.ui.setEditorComponent(undefined);
|
ctx.ui.setEditorComponent(undefined);
|
||||||
ctx.ui.setWorkingVisible(true);
|
|
||||||
ctx.ui.setWorkingIndicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hijackFooter(ctx: ExtensionContext, state: StatuslineState): void {
|
function hijackFooter(ctx: ExtensionContext, state: StatuslineState): void {
|
||||||
@@ -133,6 +115,35 @@ function hijackFooter(ctx: ExtensionContext, state: StatuslineState): void {
|
|||||||
state.footerHijacked = true;
|
state.footerHijacked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function installStatusWidget(
|
||||||
|
ctx: ExtensionContext,
|
||||||
|
state: StatuslineState,
|
||||||
|
config: ResolvedStatuslineConfig,
|
||||||
|
): void {
|
||||||
|
ctx.ui.setWidget(
|
||||||
|
STATUS_WIDGET_ID,
|
||||||
|
(_tui, theme) => ({
|
||||||
|
render(width: number): string[] {
|
||||||
|
const render = buildStatuslineWidget(ctx, state, config);
|
||||||
|
return render(width, theme);
|
||||||
|
},
|
||||||
|
invalidate() {},
|
||||||
|
}),
|
||||||
|
{ placement: "belowEditor" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function installEditor(
|
||||||
|
ctx: ExtensionContext,
|
||||||
|
state: StatuslineState,
|
||||||
|
config: ResolvedStatuslineConfig,
|
||||||
|
): void {
|
||||||
|
ctx.ui.setEditorComponent(
|
||||||
|
(tui, theme, keybindings) =>
|
||||||
|
new StatuslineEditor(tui, theme, keybindings, ctx, state, config),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function installEmptyFooter(
|
function installEmptyFooter(
|
||||||
ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
state: StatuslineState,
|
state: StatuslineState,
|
||||||
@@ -151,7 +162,6 @@ function installEmptyFooter(
|
|||||||
) => {
|
) => {
|
||||||
captureFooterData(state, theme, footerData);
|
captureFooterData(state, theme, footerData);
|
||||||
const dispose = footerData.onBranchChange(() => {
|
const dispose = footerData.onBranchChange(() => {
|
||||||
state.branch = footerData.getGitBranch() || "";
|
|
||||||
syncFooterSnapshot(state);
|
syncFooterSnapshot(state);
|
||||||
state.requestRender?.();
|
state.requestRender?.();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user