@quandev104/pi-style 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/extensions/pi-style.js +7897 -0
- package/dist/extensions/pi-style.js.map +1 -0
- package/extension-src/pi-style/app/command-service.ts +244 -0
- package/extension-src/pi-style/app/commands.ts +12 -0
- package/extension-src/pi-style/app/config-storage.ts +98 -0
- package/extension-src/pi-style/app/doctor.ts +53 -0
- package/extension-src/pi-style/app/index.ts +322 -0
- package/extension-src/pi-style/app/providers.ts +268 -0
- package/extension-src/pi-style/app/render-scheduler.ts +48 -0
- package/extension-src/pi-style/app/runtime.ts +404 -0
- package/extension-src/pi-style/app/snapshot.ts +15 -0
- package/extension-src/pi-style/domain/capabilities.ts +34 -0
- package/extension-src/pi-style/domain/config-authorization.ts +20 -0
- package/extension-src/pi-style/domain/config-diagnostics.ts +20 -0
- package/extension-src/pi-style/domain/config-diff.ts +30 -0
- package/extension-src/pi-style/domain/config-migrations.ts +52 -0
- package/extension-src/pi-style/domain/config-normalization.ts +567 -0
- package/extension-src/pi-style/domain/config-presets.ts +51 -0
- package/extension-src/pi-style/domain/config-types.ts +115 -0
- package/extension-src/pi-style/domain/providers.ts +37 -0
- package/extension-src/pi-style/domain/status-presets.ts +60 -0
- package/extension-src/pi-style/domain/status-renderer.ts +142 -0
- package/extension-src/pi-style/domain/status.ts +430 -0
- package/extension-src/pi-style/domain/theme.ts +232 -0
- package/extension-src/pi-style/features/.gitkeep +0 -0
- package/extension-src/pi-style/features/editor/index.ts +304 -0
- package/extension-src/pi-style/features/messages/boxed-block.ts +74 -0
- package/extension-src/pi-style/features/messages/index.ts +145 -0
- package/extension-src/pi-style/features/messages/special-blocks.ts +281 -0
- package/extension-src/pi-style/features/startup/index.ts +564 -0
- package/extension-src/pi-style/features/startup/logo.ts +151 -0
- package/extension-src/pi-style/features/status-line/index.ts +319 -0
- package/extension-src/pi-style/features/tools/boxed/bash.ts +347 -0
- package/extension-src/pi-style/features/tools/boxed/edit.ts +113 -0
- package/extension-src/pi-style/features/tools/boxed/fallback.ts +67 -0
- package/extension-src/pi-style/features/tools/boxed/find.ts +65 -0
- package/extension-src/pi-style/features/tools/boxed/grep.ts +108 -0
- package/extension-src/pi-style/features/tools/boxed/index.ts +64 -0
- package/extension-src/pi-style/features/tools/boxed/ls.ts +65 -0
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +190 -0
- package/extension-src/pi-style/features/tools/boxed/read.ts +204 -0
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -0
- package/extension-src/pi-style/features/tools/boxed/shared.ts +157 -0
- package/extension-src/pi-style/features/tools/boxed/write.ts +89 -0
- package/extension-src/pi-style/features/tools/index.ts +480 -0
- package/extension-src/pi-style/pi/commands.ts +17 -0
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +195 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +645 -0
- package/extension-src/pi-style/pi/compatibility-registry.ts +329 -0
- package/extension-src/pi-style/pi/config-host.ts +52 -0
- package/extension-src/pi-style/pi/config-session.ts +105 -0
- package/extension-src/pi-style/pi/index.ts +77 -0
- package/extension-src/pi-style/pi/operational-state.ts +29 -0
- package/extension-src/pi-style/pi/session-coordinator.ts +187 -0
- package/extension-src/pi-style/pi/session-usage.ts +62 -0
- package/extension-src/pi-style/pi/startup-resources.ts +70 -0
- package/extension-src/pi-style/shared/.gitkeep +0 -0
- package/extension-src/pi-style/shared/ansi.ts +386 -0
- package/extension-src/pi-style/shared/box.ts +805 -0
- package/extension-src/pi-style/shared/disposable-store.ts +28 -0
- package/extension-src/pi-style/shared/elapsed.ts +88 -0
- package/extension-src/pi-style/shared/render-budget.ts +367 -0
- package/extension-src/pi-style/shared/split-diff.ts +692 -0
- package/extension-src/pi-style/shared/theme-extras.ts +292 -0
- package/package.json +68 -0
- package/themes/.gitkeep +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const PI_STYLE_SCHEMA_VERSION = 1 as const;
|
|
2
|
+
|
|
3
|
+
export type Placement = "above" | "below";
|
|
4
|
+
export type NerdFontsMode = "auto" | "on" | "off";
|
|
5
|
+
export type ToggleMode = "auto" | "on" | "off";
|
|
6
|
+
export type PresetName = "default" | "minimal" | "compact" | "full" | "ascii" | "native";
|
|
7
|
+
export type EditorStyle = "compact" | "boxed" | "dock" | "native";
|
|
8
|
+
export type EditorFrame = "auto" | "halfblock" | "line" | "solid" | "outline" | "native";
|
|
9
|
+
|
|
10
|
+
export interface StatusCustomItemConfig {
|
|
11
|
+
id: string;
|
|
12
|
+
statusKey: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
priority?: number;
|
|
15
|
+
placement?: "left" | "right" | "secondary";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PiStyleConfig {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
preset?: string;
|
|
21
|
+
placement?: string;
|
|
22
|
+
startup?: { mode?: string; showResources?: boolean; alwaysExpanded?: boolean };
|
|
23
|
+
statusLine?: {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
separator?: string;
|
|
26
|
+
layout?: { left?: string[]; right?: string[]; secondary?: string[] };
|
|
27
|
+
disabledSegments?: string[];
|
|
28
|
+
customItems?: StatusCustomItemConfig[];
|
|
29
|
+
/** Blank rows reserved below the primary status row (0 disables). */
|
|
30
|
+
bottomMargin?: number;
|
|
31
|
+
/** Context progress-bar cell count (default 10). */
|
|
32
|
+
contextBarWidth?: number;
|
|
33
|
+
};
|
|
34
|
+
editor?: { enabled?: boolean; style?: string; frame?: string; showMetadata?: boolean };
|
|
35
|
+
messages?: { enabled?: boolean; userPrefix?: boolean; assistantPrefix?: boolean; specialBlocks?: boolean };
|
|
36
|
+
tools?: {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
style?: string;
|
|
39
|
+
maxCollapsedLines?: number;
|
|
40
|
+
maxExpandedLines?: number;
|
|
41
|
+
dimOutput?: boolean;
|
|
42
|
+
showElapsed?: boolean;
|
|
43
|
+
};
|
|
44
|
+
theme?: {
|
|
45
|
+
nerdFonts?: string;
|
|
46
|
+
terminalBackgroundSync?: string;
|
|
47
|
+
colors?: Record<string, unknown>;
|
|
48
|
+
glyphs?: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
compatibility?: {
|
|
51
|
+
allowSafePatches?: boolean;
|
|
52
|
+
allowCorePatches?: boolean;
|
|
53
|
+
preferExistingEditor?: boolean;
|
|
54
|
+
preferExistingFooter?: boolean;
|
|
55
|
+
};
|
|
56
|
+
debug?: boolean;
|
|
57
|
+
schemaVersion?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface NormalizedPiStyleConfig {
|
|
61
|
+
readonly schemaVersion: typeof PI_STYLE_SCHEMA_VERSION;
|
|
62
|
+
readonly enabled: boolean;
|
|
63
|
+
readonly preset: PresetName;
|
|
64
|
+
readonly placement: Placement;
|
|
65
|
+
readonly startup: {
|
|
66
|
+
mode: "off" | "compact" | "overlay";
|
|
67
|
+
showResources: boolean;
|
|
68
|
+
alwaysExpanded: boolean;
|
|
69
|
+
};
|
|
70
|
+
readonly statusLine: {
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
separator: string;
|
|
73
|
+
layout: { left: readonly string[]; right: readonly string[]; secondary: readonly string[] };
|
|
74
|
+
disabledSegments: readonly string[];
|
|
75
|
+
customItems: readonly StatusCustomItemConfig[];
|
|
76
|
+
/** Blank rows reserved below the primary status row (0 disables). */
|
|
77
|
+
bottomMargin: number;
|
|
78
|
+
/** Context progress-bar cell count. */
|
|
79
|
+
contextBarWidth: number;
|
|
80
|
+
};
|
|
81
|
+
readonly editor: { enabled: boolean; style: EditorStyle; frame: EditorFrame; showMetadata: boolean };
|
|
82
|
+
readonly messages: { enabled: boolean; userPrefix: boolean; assistantPrefix: boolean; specialBlocks: boolean };
|
|
83
|
+
readonly tools: {
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
style: string;
|
|
86
|
+
maxCollapsedLines: number;
|
|
87
|
+
maxExpandedLines: number;
|
|
88
|
+
dimOutput: boolean;
|
|
89
|
+
showElapsed: boolean;
|
|
90
|
+
};
|
|
91
|
+
readonly theme: {
|
|
92
|
+
nerdFonts: NerdFontsMode;
|
|
93
|
+
terminalBackgroundSync: ToggleMode;
|
|
94
|
+
colors: Readonly<Record<string, string>>;
|
|
95
|
+
glyphs: Readonly<Record<string, string>>;
|
|
96
|
+
};
|
|
97
|
+
readonly compatibility: {
|
|
98
|
+
allowSafePatches: boolean;
|
|
99
|
+
allowCorePatches: boolean;
|
|
100
|
+
preferExistingEditor: boolean;
|
|
101
|
+
preferExistingFooter: boolean;
|
|
102
|
+
};
|
|
103
|
+
/** Internal provenance marker; never serialized or accepted as a command field. */
|
|
104
|
+
readonly productCorePatchesExplicit?: boolean;
|
|
105
|
+
readonly debug: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ConfigSources {
|
|
109
|
+
defaults?: unknown;
|
|
110
|
+
global?: unknown;
|
|
111
|
+
project?: unknown;
|
|
112
|
+
environment?: Record<string, string | undefined>;
|
|
113
|
+
session?: unknown;
|
|
114
|
+
projectTrusted?: boolean;
|
|
115
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ContextSnapshot, GitSnapshot, StatusSnapshot, UsageSnapshot } from "./status.js";
|
|
2
|
+
|
|
3
|
+
export interface GitCommandResult {
|
|
4
|
+
readonly stdout: string;
|
|
5
|
+
readonly stderr: string;
|
|
6
|
+
readonly code: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GitCommandRunner {
|
|
10
|
+
run(args: readonly string[], cwd: string, timeoutMs: number, signal?: AbortSignal): Promise<GitCommandResult>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface GitProvider {
|
|
14
|
+
get(cwd: string): Promise<GitSnapshot>;
|
|
15
|
+
invalidate(cwd?: string): void;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UsageProvider {
|
|
20
|
+
get(sessionId: string): UsageSnapshot;
|
|
21
|
+
record(sessionId: string, usage: Partial<UsageSnapshot>, options?: { eventId?: string; finalized?: boolean }): void;
|
|
22
|
+
reset(sessionId?: string): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ContextProvider {
|
|
26
|
+
get(sessionId: string): ContextSnapshot | undefined;
|
|
27
|
+
set(sessionId: string, context: ContextSnapshot): void;
|
|
28
|
+
clear(sessionId?: string): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StatusProviderBundle {
|
|
32
|
+
readonly git: GitProvider;
|
|
33
|
+
readonly usage: UsageProvider;
|
|
34
|
+
readonly context: ContextProvider;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SnapshotStatusValues = Pick<StatusSnapshot, "model" | "thinkingLevel" | "git" | "context" | "usage">;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { PresetName } from "./config-types.js";
|
|
2
|
+
import type { StatusLayout, StatusSegmentId } from "./status.js";
|
|
3
|
+
|
|
4
|
+
export const STATUS_PRESETS: Readonly<Record<PresetName, StatusLayout>> = Object.freeze({
|
|
5
|
+
default: Object.freeze({
|
|
6
|
+
left: ["path", "git", "context_bar", "cost"],
|
|
7
|
+
right: ["model_effort"],
|
|
8
|
+
secondary: ["extension_statuses"],
|
|
9
|
+
}),
|
|
10
|
+
minimal: Object.freeze({ left: ["path", "git"], right: ["context_pct"], secondary: [] }),
|
|
11
|
+
compact: Object.freeze({
|
|
12
|
+
left: ["model", "thinking", "git"],
|
|
13
|
+
right: ["context_pct"],
|
|
14
|
+
secondary: ["extension_statuses"],
|
|
15
|
+
}),
|
|
16
|
+
full: Object.freeze({
|
|
17
|
+
left: ["hostname", "model", "thinking", "path", "git", "session"],
|
|
18
|
+
right: ["token_in", "token_out", "cache_read", "cost", "context_pct", "time_spent", "time"],
|
|
19
|
+
secondary: ["extension_statuses"],
|
|
20
|
+
}),
|
|
21
|
+
ascii: Object.freeze({
|
|
22
|
+
left: ["path", "git", "context_bar", "cost"],
|
|
23
|
+
right: ["model_effort"],
|
|
24
|
+
secondary: ["extension_statuses"],
|
|
25
|
+
}),
|
|
26
|
+
native: Object.freeze({ left: ["model", "path"], right: ["context_pct"], secondary: [] }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function unique(values: readonly unknown[]): StatusSegmentId[] {
|
|
30
|
+
const result: StatusSegmentId[] = [];
|
|
31
|
+
for (const value of values) {
|
|
32
|
+
if (typeof value !== "string" || result.includes(value)) continue;
|
|
33
|
+
result.push(value);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeStatusLayout(
|
|
39
|
+
preset: PresetName,
|
|
40
|
+
input:
|
|
41
|
+
| {
|
|
42
|
+
left?: readonly unknown[] | undefined;
|
|
43
|
+
right?: readonly unknown[] | undefined;
|
|
44
|
+
secondary?: readonly unknown[] | undefined;
|
|
45
|
+
}
|
|
46
|
+
| undefined,
|
|
47
|
+
): StatusLayout {
|
|
48
|
+
const base = STATUS_PRESETS[preset] ?? STATUS_PRESETS.default;
|
|
49
|
+
const left = input?.left === undefined ? base.left : unique(input.left);
|
|
50
|
+
const right = input?.right === undefined ? base.right : unique(input.right);
|
|
51
|
+
const secondary = input?.secondary === undefined ? base.secondary : unique(input.secondary);
|
|
52
|
+
const seen = new Set<StatusSegmentId>();
|
|
53
|
+
const dedupe = (values: readonly StatusSegmentId[]) =>
|
|
54
|
+
values.filter((value) => {
|
|
55
|
+
if (seen.has(value)) return false;
|
|
56
|
+
seen.add(value);
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
return Object.freeze({ left: dedupe(left), right: dedupe(right), secondary: dedupe(secondary) });
|
|
60
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SegmentContext,
|
|
3
|
+
SegmentRenderResult,
|
|
4
|
+
StatusLayout,
|
|
5
|
+
StatusRenderResult,
|
|
6
|
+
StatusSegment,
|
|
7
|
+
StatusSegmentId,
|
|
8
|
+
StatusSnapshot,
|
|
9
|
+
} from "../domain/status.js";
|
|
10
|
+
import type { ResolvedTheme } from "../domain/theme.js";
|
|
11
|
+
import { truncateAnsi, visibleWidth } from "../shared/ansi.js";
|
|
12
|
+
|
|
13
|
+
export interface StatusRendererOptions {
|
|
14
|
+
readonly separator?: string;
|
|
15
|
+
readonly padding?: string;
|
|
16
|
+
readonly segments: ReadonlyMap<StatusSegmentId, StatusSegment>;
|
|
17
|
+
readonly theme: ResolvedTheme;
|
|
18
|
+
readonly options?: Readonly<
|
|
19
|
+
Record<string, { readonly disabled?: boolean; readonly [key: string]: unknown } | undefined>
|
|
20
|
+
>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Candidate {
|
|
24
|
+
readonly id: StatusSegmentId;
|
|
25
|
+
readonly segment: StatusSegment;
|
|
26
|
+
readonly result: SegmentRenderResult;
|
|
27
|
+
content: string;
|
|
28
|
+
compact: boolean;
|
|
29
|
+
moved: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function uniqueLayout(layout: StatusLayout): StatusLayout {
|
|
33
|
+
const seen = new Set<StatusSegmentId>();
|
|
34
|
+
const dedupe = (items: readonly StatusSegmentId[]) =>
|
|
35
|
+
items.filter((id) => {
|
|
36
|
+
if (seen.has(id)) return false;
|
|
37
|
+
seen.add(id);
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
return { left: dedupe(layout.left), right: dedupe(layout.right), secondary: dedupe(layout.secondary) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderGroup(items: readonly Candidate[], separator: string, padding: string): string {
|
|
44
|
+
return items
|
|
45
|
+
.map((item) => item.content)
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(`${padding}${separator}${padding}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function widthOf(items: readonly Candidate[], separator: string, padding: string): number {
|
|
51
|
+
return visibleWidth(renderGroup(items, separator, padding));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function renderStatus(
|
|
55
|
+
layout: StatusLayout,
|
|
56
|
+
snapshot: StatusSnapshot,
|
|
57
|
+
width: number,
|
|
58
|
+
options: StatusRendererOptions,
|
|
59
|
+
): StatusRenderResult {
|
|
60
|
+
if (width <= 0) return { primary: "", lines: [], visibleSegments: [] };
|
|
61
|
+
const separator = options.separator ?? "│";
|
|
62
|
+
const padding = options.padding ?? " ";
|
|
63
|
+
const normalized = uniqueLayout(layout);
|
|
64
|
+
const context: SegmentContext = { snapshot, theme: options.theme, options: options.options ?? {}, width };
|
|
65
|
+
const candidates = new Map<StatusSegmentId, Candidate>();
|
|
66
|
+
for (const id of [...normalized.left, ...normalized.right, ...normalized.secondary]) {
|
|
67
|
+
if (candidates.has(id)) continue;
|
|
68
|
+
const segment = options.segments.get(id);
|
|
69
|
+
if (!segment || options.options?.[id]?.disabled) continue;
|
|
70
|
+
try {
|
|
71
|
+
const result = segment.render(context);
|
|
72
|
+
if (!result.visible || !result.content) continue;
|
|
73
|
+
candidates.set(id, { id, segment, result, content: result.content, compact: false, moved: false });
|
|
74
|
+
} catch {
|
|
75
|
+
// A broken optional segment must not break the status row.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const primary = [...normalized.left, ...normalized.right]
|
|
79
|
+
.map((id) => candidates.get(id))
|
|
80
|
+
.filter((candidate): candidate is Candidate => candidate !== undefined)
|
|
81
|
+
.sort(
|
|
82
|
+
(a, b) =>
|
|
83
|
+
(b.segment.essential ? 1 : 0) - (a.segment.essential ? 1 : 0) ||
|
|
84
|
+
b.segment.defaultPriority - a.segment.defaultPriority,
|
|
85
|
+
);
|
|
86
|
+
const secondary = normalized.secondary
|
|
87
|
+
.map((id) => candidates.get(id))
|
|
88
|
+
.filter((candidate): candidate is Candidate => candidate !== undefined);
|
|
89
|
+
const visible: Candidate[] = [];
|
|
90
|
+
const overflow: Candidate[] = [];
|
|
91
|
+
for (const candidate of primary) {
|
|
92
|
+
visible.push(candidate);
|
|
93
|
+
if (widthOf(visible, separator, padding) <= width) continue;
|
|
94
|
+
if (candidate.result.compactContent && !candidate.compact) {
|
|
95
|
+
candidate.content = candidate.result.compactContent;
|
|
96
|
+
candidate.compact = true;
|
|
97
|
+
if (widthOf(visible, separator, padding) <= width) continue;
|
|
98
|
+
}
|
|
99
|
+
visible.pop();
|
|
100
|
+
if (candidate.segment.overflow !== "drop" && candidate.segment.overflow !== "primary") {
|
|
101
|
+
candidate.moved = true;
|
|
102
|
+
overflow.push(candidate);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const candidate of [...overflow].sort((a, b) => b.segment.defaultPriority - a.segment.defaultPriority))
|
|
106
|
+
secondary.push(candidate);
|
|
107
|
+
// Right-aligned trailing group: layout.right candidates render flush to the
|
|
108
|
+
// right edge, mirroring Pi's native footer (model • effort on the far right).
|
|
109
|
+
// Group order follows the layout declaration; the priority sort above only
|
|
110
|
+
// decides what drops on overflow.
|
|
111
|
+
const visibleIds = new Set(visible.map((candidate) => candidate.id));
|
|
112
|
+
const groupOf = (group: readonly StatusSegmentId[]) =>
|
|
113
|
+
group
|
|
114
|
+
.map((id) => candidates.get(id))
|
|
115
|
+
.filter((candidate): candidate is Candidate => candidate !== undefined && visibleIds.has(candidate.id));
|
|
116
|
+
const leftVisible = groupOf(normalized.left);
|
|
117
|
+
const rightVisible = groupOf(normalized.right);
|
|
118
|
+
const leftText = renderGroup(leftVisible, separator, padding);
|
|
119
|
+
const rightText = renderGroup(rightVisible, separator, padding);
|
|
120
|
+
let primaryText: string;
|
|
121
|
+
if (!rightText) {
|
|
122
|
+
primaryText = leftText;
|
|
123
|
+
} else {
|
|
124
|
+
const core = leftText ? `${leftText}${padding}${separator}${padding}` : "";
|
|
125
|
+
const gap = Math.max(2, width - visibleWidth(core) - visibleWidth(rightText));
|
|
126
|
+
primaryText = `${core}${" ".repeat(gap)}${rightText}`;
|
|
127
|
+
}
|
|
128
|
+
if (visibleWidth(primaryText) > width) primaryText = truncateAnsi(primaryText, width);
|
|
129
|
+
const secondaryVisible: Candidate[] = [];
|
|
130
|
+
for (const candidate of [...secondary].sort((a, b) => b.segment.defaultPriority - a.segment.defaultPriority)) {
|
|
131
|
+
secondaryVisible.push(candidate);
|
|
132
|
+
if (widthOf(secondaryVisible, separator, padding) > width) secondaryVisible.pop();
|
|
133
|
+
}
|
|
134
|
+
const secondaryText = renderGroup(secondaryVisible, separator, padding);
|
|
135
|
+
const lines = secondaryText ? [primaryText, secondaryText] : primaryText ? [primaryText] : [];
|
|
136
|
+
return {
|
|
137
|
+
primary: primaryText,
|
|
138
|
+
...(secondaryText ? { secondary: secondaryText } : {}),
|
|
139
|
+
lines,
|
|
140
|
+
visibleSegments: [...visible, ...secondaryVisible].map((candidate) => candidate.id),
|
|
141
|
+
};
|
|
142
|
+
}
|