@f5xc-salesdemos/xcsh 18.70.0 → 18.75.3
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 +8 -2
- package/package.json +7 -7
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/status-line/presets.ts +57 -0
- package/src/modes/components/status-line/segments.ts +19 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +77 -19
- package/src/pipeline-report/generator.ts +359 -28
- package/src/pipeline-report/renderer.ts +177 -34
- package/src/pipeline-report/types.ts +64 -0
- package/src/prompts/tools/sf-pipeline-report.md +25 -0
- package/src/prompts/tools/sf-query.md +24 -18
- package/src/sdk.ts +24 -0
- package/src/services/f5xc-context-display.ts +40 -0
- package/src/services/f5xc-context-segment.ts +17 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/renderers.ts +1 -0
- package/src/tools/sf/formatters.ts +48 -0
- package/src/tools/sf-pipeline-report.ts +175 -0
- package/src/tools/sf-renderer.ts +80 -20
- package/src/tools/sf.ts +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.75.0] - 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Auto-validate credentials on context switch: SDK context change listener now fires a background `validateToken()` after emitting the switch notification, sending a follow-up `context_validation_result` custom message so the LLM knows whether the new context's credentials are valid
|
|
10
|
+
|
|
5
11
|
## [18.64.2] - 2026-05-16
|
|
6
12
|
|
|
7
13
|
### Fixed
|
|
@@ -20,11 +26,13 @@
|
|
|
20
26
|
### Changed
|
|
21
27
|
|
|
22
28
|
- Regenerated API spec index from catalog v2.1.82: http_loadbalancer CRUD verification corrections (6 new server defaults, corrected minimum configs, cross-field dependencies, default_pool inline pool discovery, 5 composable routing approaches) and tcp_loadbalancer minimum config corrections (listen_port, origin_pools_weights, do_not_advertise format, 9 server defaults, forced hash_policy default) ([#753](https://github.com/f5xc-salesdemos/xcsh/issues/753), [#757](https://github.com/f5xc-salesdemos/xcsh/issues/757))
|
|
29
|
+
|
|
23
30
|
## [18.53.0] - 2026-05-09
|
|
24
31
|
|
|
25
32
|
### Changed
|
|
26
33
|
|
|
27
34
|
- Autoresearch subsystem code quality: -513 lines (18.8% reduction), ~13% faster type checking. Un-exported internal symbols, relocated types, consolidated duplicate patterns, replaced manual deep copies with `structuredClone`, replaced `while(exec)` with `matchAll`, compressed control flow, extracted shared interfaces ([#734](https://github.com/f5xc-salesdemos/xcsh/pull/734))
|
|
35
|
+
|
|
28
36
|
## [18.53.0] - 2026-05-09
|
|
29
37
|
|
|
30
38
|
### Fixed
|
|
@@ -79,13 +87,11 @@
|
|
|
79
87
|
|
|
80
88
|
## [18.18.4] - 2026-04-26
|
|
81
89
|
|
|
82
|
-
|
|
83
90
|
### Fixed
|
|
84
91
|
|
|
85
92
|
- Fixed gutter width propagation in the fallback tool renderer: `#formatToolExecution()` now receives the actual available width at render-time and uses it for line truncation instead of a hardcoded 80-column limit. On narrow terminals (<82 cols) this prevents content wider than the gutter-adjusted viewport; on wide terminals it allows longer output lines. ([#117](https://github.com/f5xc-salesdemos/xcsh/issues/117))
|
|
86
93
|
- Fixed `resolveConfigValue` returning literal env var names (e.g. `"LITELLM_API_KEY"`) as API keys when the env var is unset, causing 401 errors on first launch. The resolver now rejects unresolved `ALL_CAPS_WITH_UNDERSCORES` patterns, matching the existing guard in `resolveYamlApiKeyConfig`. ([#241](https://github.com/f5xc-salesdemos/xcsh/issues/241))
|
|
87
94
|
|
|
88
|
-
|
|
89
95
|
### Changed
|
|
90
96
|
|
|
91
97
|
- Renamed F5 XC credential system from "profile" to "context" to align with kubectl conventions. The `/profile` command is now `/context`, all types/classes use `Context*` naming (`ContextService`, `ContextStatus`, `F5XCContext`, etc.), on-disk paths changed from `profiles/` to `contexts/` and `active_profile` to `active_context`, and the status-line segment ID is now `context_f5xc`. ([#302](https://github.com/f5xc-salesdemos/xcsh/issues/302))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.75.3",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5xc-salesdemos/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
50
50
|
"@mozilla/readability": "^0.6",
|
|
51
|
-
"@f5xc-salesdemos/xcsh-stats": "
|
|
52
|
-
"@f5xc-salesdemos/pi-agent-core": "
|
|
53
|
-
"@f5xc-salesdemos/pi-ai": "
|
|
54
|
-
"@f5xc-salesdemos/pi-natives": "
|
|
55
|
-
"@f5xc-salesdemos/pi-tui": "
|
|
56
|
-
"@f5xc-salesdemos/pi-utils": "
|
|
51
|
+
"@f5xc-salesdemos/xcsh-stats": "workspace:*",
|
|
52
|
+
"@f5xc-salesdemos/pi-agent-core": "workspace:*",
|
|
53
|
+
"@f5xc-salesdemos/pi-ai": "workspace:*",
|
|
54
|
+
"@f5xc-salesdemos/pi-natives": "workspace:*",
|
|
55
|
+
"@f5xc-salesdemos/pi-tui": "workspace:*",
|
|
56
|
+
"@f5xc-salesdemos/pi-utils": "workspace:*",
|
|
57
57
|
"@sinclair/typebox": "^0.34",
|
|
58
58
|
"@xterm/headless": "^6.0",
|
|
59
59
|
"ajv": "^8.18",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.75.3",
|
|
21
|
+
"commit": "6856457e251f8583b237e72702ac395bc7173f42",
|
|
22
|
+
"shortCommit": "6856457",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-05-
|
|
26
|
-
"buildDate": "2026-05-
|
|
24
|
+
"tag": "v18.75.3",
|
|
25
|
+
"commitDate": "2026-05-23T08:23:41Z",
|
|
26
|
+
"buildDate": "2026-05-23T17:15:18.019Z",
|
|
27
27
|
"dirty": false,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/6856457e251f8583b237e72702ac395bc7173f42",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.75.3"
|
|
33
33
|
};
|
|
@@ -10,6 +10,18 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
10
10
|
path: { abbreviate: true, maxLength: 40, stripWorkPrefix: true },
|
|
11
11
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: true },
|
|
12
12
|
},
|
|
13
|
+
dropOrder: [
|
|
14
|
+
"pr",
|
|
15
|
+
"token_total",
|
|
16
|
+
"cost",
|
|
17
|
+
"git",
|
|
18
|
+
"path",
|
|
19
|
+
"context_pct",
|
|
20
|
+
"plan_mode",
|
|
21
|
+
"model",
|
|
22
|
+
"pi",
|
|
23
|
+
"context_f5xc",
|
|
24
|
+
],
|
|
13
25
|
},
|
|
14
26
|
|
|
15
27
|
minimal: {
|
|
@@ -20,6 +32,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
20
32
|
path: { abbreviate: true, maxLength: 30 },
|
|
21
33
|
git: { showBranch: true, showStaged: false, showUnstaged: false, showUntracked: false },
|
|
22
34
|
},
|
|
35
|
+
dropOrder: ["git", "plan_mode", "context_pct", "path", "context_f5xc"],
|
|
23
36
|
},
|
|
24
37
|
|
|
25
38
|
compact: {
|
|
@@ -30,6 +43,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
30
43
|
model: { showThinkingLevel: false },
|
|
31
44
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: false },
|
|
32
45
|
},
|
|
46
|
+
dropOrder: ["pr", "cost", "git", "context_pct", "plan_mode", "model", "context_f5xc"],
|
|
33
47
|
},
|
|
34
48
|
|
|
35
49
|
full: {
|
|
@@ -52,6 +66,25 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
52
66
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: true },
|
|
53
67
|
time: { format: "24h", showSeconds: false },
|
|
54
68
|
},
|
|
69
|
+
dropOrder: [
|
|
70
|
+
"time",
|
|
71
|
+
"time_spent",
|
|
72
|
+
"cache_read",
|
|
73
|
+
"token_rate",
|
|
74
|
+
"token_out",
|
|
75
|
+
"token_in",
|
|
76
|
+
"subagents",
|
|
77
|
+
"pr",
|
|
78
|
+
"git",
|
|
79
|
+
"path",
|
|
80
|
+
"cost",
|
|
81
|
+
"context_pct",
|
|
82
|
+
"plan_mode",
|
|
83
|
+
"hostname",
|
|
84
|
+
"model",
|
|
85
|
+
"pi",
|
|
86
|
+
"context_f5xc",
|
|
87
|
+
],
|
|
55
88
|
},
|
|
56
89
|
|
|
57
90
|
nerd: {
|
|
@@ -77,6 +110,28 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
77
110
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: true },
|
|
78
111
|
time: { format: "24h", showSeconds: true },
|
|
79
112
|
},
|
|
113
|
+
dropOrder: [
|
|
114
|
+
"context_total",
|
|
115
|
+
"cache_write",
|
|
116
|
+
"session",
|
|
117
|
+
"time",
|
|
118
|
+
"time_spent",
|
|
119
|
+
"cache_read",
|
|
120
|
+
"token_rate",
|
|
121
|
+
"token_out",
|
|
122
|
+
"token_in",
|
|
123
|
+
"subagents",
|
|
124
|
+
"pr",
|
|
125
|
+
"git",
|
|
126
|
+
"path",
|
|
127
|
+
"cost",
|
|
128
|
+
"context_pct",
|
|
129
|
+
"plan_mode",
|
|
130
|
+
"hostname",
|
|
131
|
+
"model",
|
|
132
|
+
"pi",
|
|
133
|
+
"context_f5xc",
|
|
134
|
+
],
|
|
80
135
|
},
|
|
81
136
|
|
|
82
137
|
ascii: {
|
|
@@ -89,6 +144,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
89
144
|
path: { abbreviate: true, maxLength: 40 },
|
|
90
145
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: true },
|
|
91
146
|
},
|
|
147
|
+
dropOrder: ["pr", "token_total", "cost", "git", "path", "context_pct", "model", "context_f5xc"],
|
|
92
148
|
},
|
|
93
149
|
|
|
94
150
|
xcsh: {
|
|
@@ -101,6 +157,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
101
157
|
git: { showBranch: true, showStaged: true, showUnstaged: true, showUntracked: true },
|
|
102
158
|
context_pct: { compact: true },
|
|
103
159
|
},
|
|
160
|
+
dropOrder: ["plan_mode", "git", "path", "context_pct", "context_f5xc"],
|
|
104
161
|
},
|
|
105
162
|
|
|
106
163
|
custom: {
|
|
@@ -461,6 +461,25 @@ export const SEGMENTS: Record<StatusLineSegmentId, StatusLineSegment> = {
|
|
|
461
461
|
return { content: "", visible: false };
|
|
462
462
|
}
|
|
463
463
|
},
|
|
464
|
+
truncate(maxWidth: number, _ctx: SegmentContext): RenderedSegment | null {
|
|
465
|
+
try {
|
|
466
|
+
const { truncateF5XCContextSegment } = require("../../../services/f5xc-context-segment");
|
|
467
|
+
const result = truncateF5XCContextSegment(maxWidth);
|
|
468
|
+
if (!result) return null;
|
|
469
|
+
let bg = theme.fgColorAsBg("statusLineContextF5xcBg");
|
|
470
|
+
let fg = theme.getFgAnsi("statusLineContextF5xcFg");
|
|
471
|
+
if (result.tokenHealth === "expiring") {
|
|
472
|
+
bg = theme.fgColorAsBg("statusLineGitDirtyBg");
|
|
473
|
+
fg = theme.getFgAnsi("statusLineGitDirtyFg");
|
|
474
|
+
} else if (result.tokenHealth === "expired") {
|
|
475
|
+
bg = theme.fgColorAsBg("statusLineGitConflictBg");
|
|
476
|
+
fg = theme.getFgAnsi("statusLineGitConflictFg");
|
|
477
|
+
}
|
|
478
|
+
return { ...result, bg, fg };
|
|
479
|
+
} catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
},
|
|
464
483
|
},
|
|
465
484
|
};
|
|
466
485
|
|
|
@@ -66,6 +66,7 @@ export interface RenderedSegment {
|
|
|
66
66
|
export interface StatusLineSegment {
|
|
67
67
|
id: StatusLineSegmentId;
|
|
68
68
|
render(ctx: SegmentContext): RenderedSegment;
|
|
69
|
+
truncate?(maxWidth: number, ctx: SegmentContext): RenderedSegment | null;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -91,4 +92,5 @@ export interface PresetDef {
|
|
|
91
92
|
rightSegments: StatusLineSegmentId[];
|
|
92
93
|
separator: StatusLineSeparatorStyle;
|
|
93
94
|
segmentOptions?: StatusLineSegmentOptions;
|
|
95
|
+
dropOrder?: StatusLineSegmentId[];
|
|
94
96
|
}
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type PrCacheContext,
|
|
21
21
|
} from "./status-line/git-utils";
|
|
22
22
|
import { getPreset } from "./status-line/presets";
|
|
23
|
-
import { renderSegment, type SegmentContext } from "./status-line/segments";
|
|
23
|
+
import { renderSegment, SEGMENTS, type SegmentContext } from "./status-line/segments";
|
|
24
24
|
import { getSeparator } from "./status-line/separators";
|
|
25
25
|
import { calculateTokensPerSecond } from "./status-line/token-rate";
|
|
26
26
|
|
|
@@ -490,8 +490,9 @@ export class StatusLineComponent implements Component {
|
|
|
490
490
|
|
|
491
491
|
// Collect visible segments (preserving bg/fg metadata)
|
|
492
492
|
type SegPart = { content: string; bg: string; fg: string };
|
|
493
|
-
const collectParts = (segIds: readonly string[]): SegPart[] => {
|
|
493
|
+
const collectParts = (segIds: readonly string[]): { parts: SegPart[]; ids: string[] } => {
|
|
494
494
|
const parts: SegPart[] = [];
|
|
495
|
+
const ids: string[] = [];
|
|
495
496
|
for (const segId of segIds) {
|
|
496
497
|
const rendered = renderSegment(segId as any, ctx);
|
|
497
498
|
if (rendered.visible && rendered.content) {
|
|
@@ -500,13 +501,14 @@ export class StatusLineComponent implements Component {
|
|
|
500
501
|
bg: rendered.bg || defaultBg,
|
|
501
502
|
fg: rendered.fg || defaultFg,
|
|
502
503
|
});
|
|
504
|
+
ids.push(segId);
|
|
503
505
|
}
|
|
504
506
|
}
|
|
505
|
-
return parts;
|
|
507
|
+
return { parts, ids };
|
|
506
508
|
};
|
|
507
509
|
|
|
508
|
-
const leftParts = collectParts(effectiveSettings.leftSegments);
|
|
509
|
-
const rightParts = collectParts(effectiveSettings.rightSegments);
|
|
510
|
+
const { parts: leftParts, ids: leftIds } = collectParts(effectiveSettings.leftSegments);
|
|
511
|
+
const { parts: rightParts, ids: rightIds } = collectParts(effectiveSettings.rightSegments);
|
|
510
512
|
|
|
511
513
|
const runningBackgroundJobs = this.session.getAsyncJobSnapshot()?.running.length ?? 0;
|
|
512
514
|
if (runningBackgroundJobs > 0) {
|
|
@@ -534,6 +536,8 @@ export class StatusLineComponent implements Component {
|
|
|
534
536
|
const topFillWidth = Math.max(0, width);
|
|
535
537
|
const left = [...leftParts];
|
|
536
538
|
const right = [...rightParts];
|
|
539
|
+
const leftSegIds = [...leftIds];
|
|
540
|
+
const rightSegIds = [...rightIds];
|
|
537
541
|
|
|
538
542
|
const sepWidth = visibleWidth(separatorDef.left);
|
|
539
543
|
const capWidth = separatorDef.endCaps ? visibleWidth(separatorDef.endCaps.right) : 0;
|
|
@@ -541,23 +545,74 @@ export class StatusLineComponent implements Component {
|
|
|
541
545
|
const groupWidth = (parts: SegPart[]): number => {
|
|
542
546
|
if (parts.length === 0) return 0;
|
|
543
547
|
const partsWidth = parts.reduce((sum, p) => sum + visibleWidth(p.content), 0);
|
|
544
|
-
// Each segment gets 1 char padding on each side, separators between segments
|
|
545
548
|
const sepTotal = Math.max(0, parts.length - 1) * sepWidth;
|
|
546
549
|
return partsWidth + parts.length * 2 + sepTotal + capWidth;
|
|
547
550
|
};
|
|
548
551
|
|
|
549
552
|
let leftWidth = groupWidth(left);
|
|
550
553
|
let rightWidth = groupWidth(right);
|
|
551
|
-
const totalWidth = () => leftWidth + rightWidth
|
|
554
|
+
const totalWidth = () => leftWidth + rightWidth;
|
|
555
|
+
|
|
556
|
+
if (topFillWidth > 0 && totalWidth() > topFillWidth) {
|
|
557
|
+
const preset = getPreset(effectiveSettings.preset ?? "default");
|
|
558
|
+
const dropOrder = preset.dropOrder;
|
|
559
|
+
|
|
560
|
+
if (dropOrder) {
|
|
561
|
+
type SegRef = { group: "left" | "right"; part: SegPart; segId: string };
|
|
562
|
+
const segRefs: SegRef[] = [
|
|
563
|
+
...left.map((part, i) => ({ group: "left" as const, part, segId: leftSegIds[i] })),
|
|
564
|
+
...right.map((part, i) => ({ group: "right" as const, part, segId: rightSegIds[i] })),
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
const inOrder: SegRef[] = [];
|
|
568
|
+
const notInOrder: SegRef[] = [];
|
|
569
|
+
for (const ref of segRefs) {
|
|
570
|
+
const orderIdx = dropOrder.indexOf(ref.segId as any);
|
|
571
|
+
if (orderIdx === -1) notInOrder.push(ref);
|
|
572
|
+
else inOrder.push(ref);
|
|
573
|
+
}
|
|
574
|
+
inOrder.sort((a, b) => dropOrder.indexOf(a.segId as any) - dropOrder.indexOf(b.segId as any));
|
|
575
|
+
const dropQueue = [...notInOrder, ...inOrder];
|
|
576
|
+
|
|
577
|
+
for (const ref of dropQueue) {
|
|
578
|
+
if (totalWidth() <= topFillWidth) break;
|
|
579
|
+
|
|
580
|
+
const segDef = SEGMENTS[ref.segId as StatusLineSegmentId];
|
|
581
|
+
const group = ref.group === "left" ? left : right;
|
|
582
|
+
|
|
583
|
+
const currentIdx = group.indexOf(ref.part);
|
|
584
|
+
if (currentIdx === -1) continue;
|
|
585
|
+
|
|
586
|
+
if (segDef?.truncate) {
|
|
587
|
+
const currentContentWidth = visibleWidth(ref.part.content);
|
|
588
|
+
const deficit = totalWidth() - topFillWidth;
|
|
589
|
+
const targetWidth = Math.max(1, currentContentWidth - deficit);
|
|
590
|
+
const truncated = segDef.truncate(targetWidth, ctx);
|
|
591
|
+
if (truncated?.visible && truncated.content) {
|
|
592
|
+
group[currentIdx] = {
|
|
593
|
+
content: truncated.content,
|
|
594
|
+
bg: truncated.bg || ref.part.bg,
|
|
595
|
+
fg: truncated.fg || ref.part.fg,
|
|
596
|
+
};
|
|
597
|
+
if (ref.group === "left") leftWidth = groupWidth(left);
|
|
598
|
+
else rightWidth = groupWidth(right);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
552
602
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
603
|
+
group.splice(currentIdx, 1);
|
|
604
|
+
if (ref.group === "left") leftWidth = groupWidth(left);
|
|
605
|
+
else rightWidth = groupWidth(right);
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
while (totalWidth() > topFillWidth && right.length > 0) {
|
|
609
|
+
right.pop();
|
|
610
|
+
rightWidth = groupWidth(right);
|
|
611
|
+
}
|
|
612
|
+
while (totalWidth() > topFillWidth && left.length > 0) {
|
|
613
|
+
left.pop();
|
|
614
|
+
leftWidth = groupWidth(left);
|
|
615
|
+
}
|
|
561
616
|
}
|
|
562
617
|
}
|
|
563
618
|
|
|
@@ -636,13 +691,16 @@ export class StatusLineComponent implements Component {
|
|
|
636
691
|
const rightGroup = renderGroup(right, "right");
|
|
637
692
|
if (!leftGroup && !rightGroup) return "";
|
|
638
693
|
|
|
639
|
-
if (topFillWidth === 0 || left.length === 0
|
|
694
|
+
if (topFillWidth === 0 || (left.length === 0 && right.length === 0)) {
|
|
640
695
|
return leftGroup + (leftGroup && rightGroup ? " " : "") + rightGroup;
|
|
641
696
|
}
|
|
642
697
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const gapWidth = Math.max(
|
|
698
|
+
const actualLeftWidth = visibleWidth(leftGroup);
|
|
699
|
+
const actualRightWidth = visibleWidth(rightGroup);
|
|
700
|
+
const gapWidth = Math.max(0, topFillWidth - actualLeftWidth - actualRightWidth);
|
|
701
|
+
if (gapWidth === 0) {
|
|
702
|
+
return leftGroup + rightGroup;
|
|
703
|
+
}
|
|
646
704
|
const gapFill = theme.fg("border", theme.boxRound.horizontal.repeat(gapWidth));
|
|
647
705
|
return leftGroup + gapFill + rightGroup;
|
|
648
706
|
}
|