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:
kmou424
2026-06-17 17:37:32 +08:00
parent 9f95a34c18
commit 206c0886a6
3 changed files with 92 additions and 75 deletions

View File

@@ -4,7 +4,7 @@ import type {
} from "@earendil-works/pi-coding-agent";
import { registerModule, resetWowConfigCache } from "wow-core";
import { getStatuslineConfig } from "./config";
import { TAG, log } from "./global";
import { log, TAG } from "./global";
import { createState } from "./state";
import {
applyStatuslineUI,
@@ -12,6 +12,8 @@ import {
updateStatuslineState,
} from "./ui";
const STREAMING_REFRESH_INTERVAL_MS = 80;
const setup = (pi: ExtensionAPI): void => {
const state = createState();
let lastStreamingRefresh = 0;
@@ -26,47 +28,36 @@ const setup = (pi: ExtensionAPI): void => {
updateStatuslineState(state);
};
const refreshOnEvent = async (): Promise<void> => {
refresh();
};
pi.on("session_start", async (_event, ctx) => {
state.thinkingLevel = pi.getThinkingLevel();
apply(ctx);
log.debug("statusline applied", { cwd: ctx.cwd });
});
pi.on("agent_start", async () => {
refresh();
});
pi.on("agent_start", refreshOnEvent);
pi.on("message_update", async () => {
const now = Date.now();
if (now - lastStreamingRefresh < 80) return;
if (now - lastStreamingRefresh < STREAMING_REFRESH_INTERVAL_MS) return;
lastStreamingRefresh = now;
refresh();
});
pi.on("model_select", async () => {
refresh();
});
pi.on("model_select", refreshOnEvent);
pi.on("thinking_level_select", async (event) => {
state.thinkingLevel = event.level;
refresh();
});
pi.on("turn_end", async () => {
refresh();
});
pi.on("agent_end", async () => {
refresh();
});
pi.on("session_tree", async () => {
refresh();
});
pi.on("session_compact", async () => {
refresh();
});
pi.on("turn_end", refreshOnEvent);
pi.on("agent_end", refreshOnEvent);
pi.on("session_tree", refreshOnEvent);
pi.on("session_compact", refreshOnEvent);
pi.on("session_shutdown", async (_event, ctx) => {
clearStatuslineUI(ctx, state);

View File

@@ -7,7 +7,7 @@ import {
import type { Component, EditorTheme, TUI } from "@earendil-works/pi-tui";
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
import { buildEditorTopLine, buildStatusSummary } from "./format";
import { snapshotFromContext, type StatuslineState } from "./state";
import { type StatuslineState, snapshotFromContext } from "./state";
import type { ResolvedStatuslineConfig } from "./types";
const ANSI_SGR_RE = /\u001b\[[0-9;]*m/g;
@@ -50,46 +50,62 @@ export class StatuslineEditor extends CustomEditor {
this.statuslineState,
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 contentWidth = visibleWidth(content);
const remaining = width - contentWidth - 2;
const borderLine = border.repeat(Math.max(0, width));
const topContent = `${this.statuslineConfig.editorStyle.folderIcon} ${buildEditorTopLine(theme, snapshot, this.statuslineConfig)}`;
const remainingBorderWidth = width - visibleWidth(topContent) - 2;
lines[0] =
remaining >= 1
? content + theme.fg("dim", " ") + border.repeat(remaining) + border
: border.repeat(Math.max(0, width));
lines[0] = buildTopBorderLine(
topContent,
border,
borderLine,
theme,
remainingBorderWidth,
);
if (lines.length > 1) {
lines[1] =
theme.fg("accent", this.statuslineConfig.editorStyle.prompt) +
theme.fg("dim", " ") +
(lines[1] ?? "");
}
lines[bottom] = border.repeat(Math.max(0, width));
lines[findBottomBorderIndex(lines)] = borderLine;
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);
}
}
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(
ctx: ExtensionContext,
state: StatuslineState,

View File

@@ -1,11 +1,11 @@
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
import { buildEditorTopLine, buildStatusSummary, EMPTY_USAGE } from "./format";
import { type StatuslineState, syncFooterSnapshot } from "./state";
import {
buildStatuslineWidget,
EmptyFooter,
StatuslineEditor,
buildStatuslineWidget,
} from "./statusline-editor";
import { syncFooterSnapshot, type StatuslineState } from "./state";
import type { FooterDataSnapshot, ResolvedStatuslineConfig } from "./types";
const STATUS_WIDGET_ID = "wow-statusline:summary";
@@ -61,26 +61,10 @@ export function applyStatuslineUI(
return;
}
ctx.ui.setWorkingVisible(false);
hijackFooter(ctx, state);
installEmptyFooter(ctx, state);
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" },
);
ctx.ui.setEditorComponent(
(tui, theme, keybindings) =>
new StatuslineEditor(tui, theme, keybindings, ctx, state, config),
);
installStatusWidget(ctx, state, config);
installEditor(ctx, state, config);
}
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: "aboveEditor" });
ctx.ui.setEditorComponent(undefined);
ctx.ui.setWorkingVisible(true);
ctx.ui.setWorkingIndicator();
}
function hijackFooter(ctx: ExtensionContext, state: StatuslineState): void {
@@ -133,6 +115,35 @@ function hijackFooter(ctx: ExtensionContext, state: StatuslineState): void {
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(
ctx: ExtensionContext,
state: StatuslineState,
@@ -151,7 +162,6 @@ function installEmptyFooter(
) => {
captureFooterData(state, theme, footerData);
const dispose = footerData.onBranchChange(() => {
state.branch = footerData.getGitBranch() || "";
syncFooterSnapshot(state);
state.requestRender?.();
});