From 000f36b938e2dfbf1fb03ee043f1685edcc8479e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernat=20Jufr=C3=A9?= Date: Wed, 20 May 2026 14:56:02 +0200 Subject: [PATCH 1/2] fix collapsed dock wrapping --- .changeset/fuzzy-docks-breathe.md | 5 +++ src/components/log-dock-component.test.ts | 29 +++++++++++++++ src/components/log-dock-component.ts | 43 +++++++++++++---------- 3 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 .changeset/fuzzy-docks-breathe.md create mode 100644 src/components/log-dock-component.test.ts diff --git a/.changeset/fuzzy-docks-breathe.md b/.changeset/fuzzy-docks-breathe.md new file mode 100644 index 0000000..16e6dd5 --- /dev/null +++ b/.changeset/fuzzy-docks-breathe.md @@ -0,0 +1,5 @@ +--- +"@aliou/pi-processes": patch +--- + +Leave one terminal column unused in the collapsed log dock so long process output does not wrap into the editor. diff --git a/src/components/log-dock-component.test.ts b/src/components/log-dock-component.test.ts new file mode 100644 index 0000000..d8b0557 --- /dev/null +++ b/src/components/log-dock-component.test.ts @@ -0,0 +1,29 @@ +import { visibleWidth } from "@earendil-works/pi-tui"; +import { describe, expect, it } from "vitest"; +import { renderCollapsedDockLine } from "./log-dock-component"; + +const CIRCLECI_LINE = + "run-cm-be-tests - db5d24ee pending ↻ https://app.circleci.com/pipelines/gh/coursedog/coursedogv3/106313/workflows/f68e6db3-667d-48a0-ac75-d34be7c17e09?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-checks-link"; + +describe("renderCollapsedDockLine", () => { + it("leaves a spare terminal column for long log lines", () => { + for (const width of [1, 2, 40, 80, 120]) { + const rendered = renderCollapsedDockLine(CIRCLECI_LINE, width); + + expect(visibleWidth(rendered)).toBe(width - 1); + } + }); + + it("leaves a spare terminal column for ansi-styled log lines", () => { + const rendered = renderCollapsedDockLine( + `\u001b[2m${CIRCLECI_LINE}\u001b[22m`, + 80, + ); + + expect(visibleWidth(rendered)).toBe(79); + }); + + it("renders nothing for zero width", () => { + expect(renderCollapsedDockLine(CIRCLECI_LINE, 0)).toBe(""); + }); +}); diff --git a/src/components/log-dock-component.ts b/src/components/log-dock-component.ts index 11a2482..81b5169 100644 --- a/src/components/log-dock-component.ts +++ b/src/components/log-dock-component.ts @@ -29,6 +29,24 @@ const PROCESS_COLORS: ThemeColor[] = [ "warning", ]; +const COLLAPSED_DOCK_RIGHT_MARGIN = 1; + +function getCollapsedDockLineWidth(width: number): number { + return Math.max(0, width - COLLAPSED_DOCK_RIGHT_MARGIN); +} + +export function renderCollapsedDockLine( + content: string, + width: number, +): string { + const lineWidth = getCollapsedDockLineWidth(width); + if (lineWidth === 0) return ""; + + const innerWidth = Math.max(0, lineWidth - 2); + const line = truncateToWidth(content, innerWidth, "", true); + return ` ${line}${" ".repeat(Math.max(0, lineWidth - 1 - visibleWidth(line)))}`; +} + interface LogDockOptions { manager: ProcessManager; theme: Theme; @@ -120,17 +138,12 @@ export class LogDockComponent implements Component { const fg = (color: ThemeColor, s: string) => theme.fg(color, s); const processes = this.manager.list(); - const innerWidth = width - 2; - const padLine = (content: string) => { - const line = - visibleWidth(content) > innerWidth - ? truncateToWidth(content, innerWidth, "", true) - : content; - return ` ${line}${" ".repeat(Math.max(0, width - 1 - visibleWidth(line)))}`; - }; + const lineWidth = getCollapsedDockLineWidth(width); + const padLine = (content: string) => + renderCollapsedDockLine(content, width); if (processes.length === 0) { - return [renderPanelRule(width, theme), padLine(dim("No processes"))]; + return [renderPanelRule(lineWidth, theme), padLine(dim("No processes"))]; } const running = processes.filter((p) => LIVE_STATUSES.has(p.status)); @@ -146,20 +159,12 @@ export class LogDockComponent implements Component { } const firstLine = parts.join(" | "); - const lines = [ - renderPanelRule(width, theme), - padLine(truncateToWidth(firstLine, innerWidth, "", true)), - ]; + const lines = [renderPanelRule(lineWidth, theme), padLine(firstLine)]; if (running.length > 0) { const lastLogs = this.manager.getCombinedOutput(running[0].id, 1); if (lastLogs && lastLogs.length > 0) { - const lastLog = truncateToWidth( - stripAnsi(lastLogs[lastLogs.length - 1].text), - innerWidth, - "", - true, - ); + const lastLog = stripAnsi(lastLogs[lastLogs.length - 1].text); lines.push(padLine(dim(lastLog))); } } From 117a5fd2bf639fec796ea186baf645b75020d5c3 Mon Sep 17 00:00:00 2001 From: Aliou Diallo Date: Wed, 20 May 2026 15:34:02 +0200 Subject: [PATCH 2/2] test: use generic long dock line --- src/components/log-dock-component.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/log-dock-component.test.ts b/src/components/log-dock-component.test.ts index d8b0557..6d7fae0 100644 --- a/src/components/log-dock-component.test.ts +++ b/src/components/log-dock-component.test.ts @@ -2,13 +2,13 @@ import { visibleWidth } from "@earendil-works/pi-tui"; import { describe, expect, it } from "vitest"; import { renderCollapsedDockLine } from "./log-dock-component"; -const CIRCLECI_LINE = - "run-cm-be-tests - db5d24ee pending ↻ https://app.circleci.com/pipelines/gh/coursedog/coursedogv3/106313/workflows/f68e6db3-667d-48a0-ac75-d34be7c17e09?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-checks-link"; +const LONG_LOG_LINE = + "build-step - 3f7a9c2 pending ↻ https://example.com/pipelines/project/service/123456/workflows/abcdef12-3456-7890-abcd-ef1234567890/jobs/integration-test?token=very-long-unbroken-log-segment-and-query-string"; describe("renderCollapsedDockLine", () => { it("leaves a spare terminal column for long log lines", () => { for (const width of [1, 2, 40, 80, 120]) { - const rendered = renderCollapsedDockLine(CIRCLECI_LINE, width); + const rendered = renderCollapsedDockLine(LONG_LOG_LINE, width); expect(visibleWidth(rendered)).toBe(width - 1); } @@ -16,7 +16,7 @@ describe("renderCollapsedDockLine", () => { it("leaves a spare terminal column for ansi-styled log lines", () => { const rendered = renderCollapsedDockLine( - `\u001b[2m${CIRCLECI_LINE}\u001b[22m`, + `\u001b[2m${LONG_LOG_LINE}\u001b[22m`, 80, ); @@ -24,6 +24,6 @@ describe("renderCollapsedDockLine", () => { }); it("renders nothing for zero width", () => { - expect(renderCollapsedDockLine(CIRCLECI_LINE, 0)).toBe(""); + expect(renderCollapsedDockLine(LONG_LOG_LINE, 0)).toBe(""); }); });