pi-one-ui 0.8.0 → 0.9.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 +48 -1
- package/LICENSE +1 -1
- package/README.en.md +12 -10
- package/README.md +12 -10
- package/THIRD-PARTY-NOTICES.md +98 -0
- package/extensions/app/config/renderer.ts +1 -12
- package/extensions/app/config/shell.ts +5 -5
- package/extensions/app/runtime/tui-runtime.ts +6 -6
- package/extensions/app/settings/controller.ts +1 -1
- package/extensions/layouts/overlay/settings-panel.ts +9 -8
- package/extensions/layouts/overlay/settings-previews.ts +1 -1
- package/extensions/layouts/{context → transcript}/index.ts +8 -8
- package/extensions/layouts/{context → transcript}/renderer/compact-mode.ts +4 -4
- package/extensions/layouts/{context → transcript}/renderer/default-mode.ts +37 -7
- package/extensions/layouts/{context → transcript}/renderer/index.ts +8 -8
- package/extensions/layouts/{context → transcript}/renderer/mouse/hover.ts +0 -4
- package/extensions/layouts/{context → transcript}/renderer/mouse/interaction.ts +104 -93
- package/extensions/layouts/{context → transcript}/renderer/mouse/layout.ts +0 -4
- package/extensions/layouts/transcript/renderer/mouse/scroll.ts +35 -0
- package/extensions/layouts/{context → transcript}/renderer/tool/message-display.ts +1 -1
- package/extensions/layouts/{context → transcript}/renderer/tool/names.ts +19 -2
- package/extensions/layouts/{context/renderer/context-refresh.ts → transcript/renderer/transcript-refresh.ts} +6 -6
- package/extensions/layouts/{context → transcript}/thinking/compact-thinking.ts +4 -4
- package/extensions/tools/component-tree.ts +1 -1
- package/extensions/tools/patch-keys.ts +0 -3
- package/package.json +3 -2
- package/extensions/layouts/context/renderer/mouse/scroll.ts +0 -216
- /package/extensions/app/config/{context.ts → user-messages.ts} +0 -0
- /package/extensions/layouts/{context → transcript}/message/user-message-osc.ts +0 -0
- /package/extensions/layouts/{context → transcript}/message/user-message-styles.ts +0 -0
- /package/extensions/layouts/{context → transcript}/message/user-message.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/markdown-enhance.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/mouse/packets.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/ATTRIBUTION.md +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/ansi-utils.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-component.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-edit-render.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-header.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-highlight.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-inline.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-layout.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-limits.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-palette.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-parse.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-presentation.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-renderer.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-text.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/diff-write-render.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/index.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/line-width-safety.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/shiki-highlight.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/diff/write-execution.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/grouping.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/result.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/show-more-hint.ts +0 -0
- /package/extensions/layouts/{context → transcript}/renderer/tool/toggle-render-cache.ts +0 -0
- /package/extensions/layouts/{context → transcript}/summary/core.ts +0 -0
- /package/extensions/layouts/{context → transcript}/summary/index.ts +0 -0
|
@@ -11,22 +11,22 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// pi-coding-agent 类型声明中 AssistantMessageComponent 仅能以 value 形式使用,
|
|
13
13
|
// updateContent/lastMessage 用结构化类型访问。
|
|
14
|
-
type
|
|
14
|
+
type TranscriptComponentInternals = {
|
|
15
15
|
lastMessage?: AssistantMessage;
|
|
16
16
|
updateContent(message: AssistantMessage): void;
|
|
17
17
|
invalidate?(): void;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Refreshes one constructed
|
|
21
|
+
* Refreshes one constructed transcript component.
|
|
22
22
|
*
|
|
23
23
|
* Assistant components receive their latest message, while tool components
|
|
24
24
|
* refresh their display so resumed content returns to the current shell.
|
|
25
25
|
*/
|
|
26
|
-
export function
|
|
26
|
+
export function refreshTranscriptComponent(value: unknown): boolean {
|
|
27
27
|
if (!value || typeof value !== "object") return false;
|
|
28
28
|
if (value instanceof AssistantMessageComponent) {
|
|
29
|
-
const self = value as unknown as
|
|
29
|
+
const self = value as unknown as TranscriptComponentInternals;
|
|
30
30
|
if (self.lastMessage) self.updateContent(self.lastMessage);
|
|
31
31
|
else self.invalidate?.();
|
|
32
32
|
return true;
|
|
@@ -65,13 +65,13 @@ function assertCompactModeOutermost(): void {
|
|
|
65
65
|
* This covers reload, resume and compaction paths where Pi rebuilds components
|
|
66
66
|
* before the renderer patches are installed.
|
|
67
67
|
*/
|
|
68
|
-
export function
|
|
68
|
+
export function refreshMountedTranscript(tui?: unknown): void {
|
|
69
69
|
if (!tui || typeof (tui as any).getMountedRoots !== "function") return;
|
|
70
70
|
// Fix patch order before refreshing so updateContent runs through the outer patch.
|
|
71
71
|
assertCompactModeOutermost();
|
|
72
72
|
walkComponentTree(tui, (value: any) => {
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
refreshTranscriptComponent(value);
|
|
75
75
|
} catch {
|
|
76
76
|
// Isolate one component failure; a later tree rebuild can retry it.
|
|
77
77
|
}
|
|
@@ -33,8 +33,8 @@ import {
|
|
|
33
33
|
formatThoughtDuration,
|
|
34
34
|
styleCompactThinkingText,
|
|
35
35
|
} from "../renderer/compact-mode.ts";
|
|
36
|
-
import { refreshMountedContext } from "../renderer/context-refresh.ts";
|
|
37
36
|
import { isToolTuiFullscreen } from "../renderer/tool/show-more-hint.ts";
|
|
37
|
+
import { refreshMountedTranscript } from "../renderer/transcript-refresh.ts";
|
|
38
38
|
|
|
39
39
|
// 保持导出兼容:渲染函数已并入 renderer/compact-mode.ts,这里 re-export。
|
|
40
40
|
export {
|
|
@@ -1088,15 +1088,15 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
1088
1088
|
|
|
1089
1089
|
// pi 在 reload/resume 时先于 session_start 用原始原型重建聊天组件
|
|
1090
1090
|
// (rebuildChatFromMessages / renderCurrentSessionState)。由共享的
|
|
1091
|
-
//
|
|
1091
|
+
// refreshMountedTranscript 扫描挂载树重绘这些组件,恢复 compact 渲染、
|
|
1092
1092
|
// 持久化时长与工具调用显示。
|
|
1093
|
-
|
|
1093
|
+
refreshMountedTranscript(activeTui);
|
|
1094
1094
|
activeTui?.requestRender(true);
|
|
1095
1095
|
});
|
|
1096
1096
|
|
|
1097
1097
|
pi.on("session_tree", (_event, ctx) => {
|
|
1098
1098
|
restoreDurationEntries(ctx.sessionManager.getBranch(), completedDurations);
|
|
1099
|
-
|
|
1099
|
+
refreshMountedTranscript(activeTui);
|
|
1100
1100
|
activeTui?.requestRender(true);
|
|
1101
1101
|
});
|
|
1102
1102
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared recursive traversal for component trees.
|
|
3
3
|
*
|
|
4
4
|
* The implementation covers the children/getMountedRoots/seen traversal used by
|
|
5
|
-
*
|
|
5
|
+
* transcript rendering, grouping and mouse packet handling.
|
|
6
6
|
*
|
|
7
7
|
* The visitor runs once for every non-array object. Returning `false` stops
|
|
8
8
|
* traversal into that node's children and mounted roots.
|
|
@@ -81,9 +81,6 @@ export const TOOL_GROUPING_GENERATION_KEY = Symbol.for(
|
|
|
81
81
|
// ── 鼠标交互 ──
|
|
82
82
|
export const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi-one-ui.tool-mouse-owner");
|
|
83
83
|
export const TOOL_MOUSE_TUI_SLOT = Symbol.for("pi-one-ui.tool-mouse-tui");
|
|
84
|
-
export const SCROLL_BUTTON_STATE_SLOT = Symbol.for(
|
|
85
|
-
"pi-one-ui.scroll-button-state",
|
|
86
|
-
);
|
|
87
84
|
export const FLUSH_DOCKED_BASH_PATCH = Symbol.for(
|
|
88
85
|
"pi-one-ui.flush-docked-bash-patch",
|
|
89
86
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-one-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "A single, customizable Pi UI package with layout-oriented Context and Shell rendering for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"README.md",
|
|
26
26
|
"README.en.md",
|
|
27
27
|
"CHANGELOG.md",
|
|
28
|
-
"LICENSE"
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"THIRD-PARTY-NOTICES.md"
|
|
29
30
|
],
|
|
30
31
|
"scripts": {
|
|
31
32
|
"format": "biome format --write extensions tests package.json tsconfig.json vitest.config.ts biome.json",
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getKeybindings,
|
|
3
|
-
type Keybinding,
|
|
4
|
-
matchesKey,
|
|
5
|
-
truncateToWidth,
|
|
6
|
-
visibleWidth,
|
|
7
|
-
} from "@earendil-works/pi-tui";
|
|
8
|
-
import { config } from "../../../../app/config/renderer.ts";
|
|
9
|
-
import { isLazyProxyTui } from "../../../../tools/fullscreen-detect.ts";
|
|
10
|
-
import {
|
|
11
|
-
patchRegistry,
|
|
12
|
-
SCROLL_BUTTON_STATE_SLOT,
|
|
13
|
-
TOOL_MOUSE_TUI_SLOT,
|
|
14
|
-
} from "../../../../tools/patch-keys.ts";
|
|
15
|
-
import { parseSgrMousePackets } from "./packets.ts";
|
|
16
|
-
|
|
17
|
-
const PAGE_UP_INPUT =
|
|
18
|
-
/^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
|
|
19
|
-
const PAGE_DOWN_INPUT =
|
|
20
|
-
/^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
21
|
-
const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 当前安装的 tui 宿主。宿主放本模块(滚动按钮/调度依赖它),
|
|
25
|
-
* 由 mouse-interaction 经 setToolMouseTui 维护;跨模块一律经绑定/setter 访问。
|
|
26
|
-
*
|
|
27
|
-
* 状态镜像到 globalThis(Symbol 槽):jiti 转译下经 re-export 链读取的
|
|
28
|
-
* 模块级 let 绑定是初始值快照(死绑定,实测恒 null),函数调用才是活引用。
|
|
29
|
-
* 跨模块读取一律用 getToolMouseTui(),避免拿到加载时的快照。
|
|
30
|
-
*/
|
|
31
|
-
patchRegistry.ensure(TOOL_MOUSE_TUI_SLOT, () => null);
|
|
32
|
-
export let toolMouseTui: any = null;
|
|
33
|
-
export function getToolMouseTui(): any {
|
|
34
|
-
return patchRegistry.get(TOOL_MOUSE_TUI_SLOT);
|
|
35
|
-
}
|
|
36
|
-
export function setToolMouseTui(tui: any): void {
|
|
37
|
-
toolMouseTui = tui;
|
|
38
|
-
patchRegistry.install(TOOL_MOUSE_TUI_SLOT, tui);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 滚动按钮状态同 toolMouseTui:镜像到 globalThis(Symbol 槽),跨模块读取
|
|
42
|
-
// 一律用 getter(jiti 转译下模块级 let 绑定是初始值快照)。
|
|
43
|
-
type ScrollButtonState = { visible: boolean; hovered: boolean; widget: any };
|
|
44
|
-
function scrollButtonState(): ScrollButtonState {
|
|
45
|
-
return patchRegistry.ensure(SCROLL_BUTTON_STATE_SLOT, () => ({
|
|
46
|
-
visible: false,
|
|
47
|
-
hovered: false,
|
|
48
|
-
widget: null,
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
export function getScrollButtonVisible(): boolean {
|
|
52
|
-
return scrollButtonState().visible;
|
|
53
|
-
}
|
|
54
|
-
export function getScrollButtonHovered(): boolean {
|
|
55
|
-
return scrollButtonState().hovered;
|
|
56
|
-
}
|
|
57
|
-
export function getScrollButtonWidget(): any {
|
|
58
|
-
return scrollButtonState().widget;
|
|
59
|
-
}
|
|
60
|
-
export function setScrollButtonVisible(visible: boolean): void {
|
|
61
|
-
scrollButtonState().visible = visible;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** 返回是否发生变化(调用方据此决定是否需要重渲染)。 */
|
|
65
|
-
export function setScrollButtonHovered(hovered: boolean): boolean {
|
|
66
|
-
if (hovered === scrollButtonState().hovered) return false;
|
|
67
|
-
scrollButtonState().hovered = hovered;
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function setScrollButtonWidget(widget: any): void {
|
|
72
|
-
scrollButtonState().widget = widget;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** teardown 全量清零(visible/hovered/widget/sync 调度)。 */
|
|
76
|
-
export function resetScrollButtonState(): void {
|
|
77
|
-
scrollButtonState().visible = false;
|
|
78
|
-
scrollButtonState().hovered = false;
|
|
79
|
-
scrollButtonState().widget = null;
|
|
80
|
-
scrollButtonSyncScheduled = false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
let scrollButtonSyncScheduled = false;
|
|
84
|
-
|
|
85
|
-
// 交互开关只取决于配置模式:原实现按 isLazyProxyTui(toolMouseTui) 分两分支,
|
|
86
|
-
// 两分支恒真(0.84+ 惰性 Proxy 下判定不再影响开关),折叠为单条件。
|
|
87
|
-
export function toolMouseInteractionActive(): boolean {
|
|
88
|
-
return config.mode !== "off";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** 惰性 Proxy 官方 fullscreen(TuiAltScreen)判定。 */
|
|
92
|
-
export function fullscreenLazyTui(tui: any): boolean {
|
|
93
|
-
return isLazyProxyTui(tui) && tui.mode === "fullscreen";
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Reports whether fullscreen output is following the bottom of the context.
|
|
98
|
-
*/
|
|
99
|
-
export function isFullscreenAtBottom(tui: any): boolean {
|
|
100
|
-
const following =
|
|
101
|
-
tui.isFollowingOutput ??
|
|
102
|
-
tui.getPrimaryScrollView?.()?.isFollowingEnd ??
|
|
103
|
-
true;
|
|
104
|
-
return Boolean(following);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function formatShortcut(shortcut: string): string {
|
|
108
|
-
return shortcut
|
|
109
|
-
.split("+")
|
|
110
|
-
.map((part) =>
|
|
111
|
-
part.length <= 1
|
|
112
|
-
? part.toUpperCase()
|
|
113
|
-
: `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`,
|
|
114
|
-
)
|
|
115
|
-
.join("+");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function isScrollBottomInput(data: string): boolean {
|
|
119
|
-
return matchesKey(data, SCROLL_BOTTOM_SHORTCUT);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isScrollNavigationInput(data: string): boolean {
|
|
123
|
-
if (
|
|
124
|
-
matchesKey(data, "pageUp") ||
|
|
125
|
-
matchesKey(data, "pageDown") ||
|
|
126
|
-
PAGE_UP_INPUT.test(data) ||
|
|
127
|
-
PAGE_DOWN_INPUT.test(data) ||
|
|
128
|
-
// 官方 fullscreen viewport 的可滚动键(half-page/prompt/top/bottom)。
|
|
129
|
-
[
|
|
130
|
-
"tui.altScreen.pageUp",
|
|
131
|
-
"tui.altScreen.pageDown",
|
|
132
|
-
"tui.altScreen.halfPageUp",
|
|
133
|
-
"tui.altScreen.halfPageDown",
|
|
134
|
-
"tui.altScreen.previousPrompt",
|
|
135
|
-
"tui.altScreen.nextPrompt",
|
|
136
|
-
"tui.altScreen.top",
|
|
137
|
-
"tui.altScreen.bottom",
|
|
138
|
-
].some((key) => getKeybindings().matches(data, key as Keybinding))
|
|
139
|
-
) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
const packets = parseSgrMousePackets(data);
|
|
143
|
-
return Boolean(
|
|
144
|
-
packets?.some((packet) => {
|
|
145
|
-
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
146
|
-
return packet.final === "M" && (baseButton === 64 || baseButton === 65);
|
|
147
|
-
}),
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Reports whether the context is at the bottom for the detected TUI mode.
|
|
153
|
-
*/
|
|
154
|
-
function isAtContextBottom(tui: any): boolean {
|
|
155
|
-
// 惰性 Proxy fullscreen:官方 viewport 以 isFollowingOutput 判定是否在底部。
|
|
156
|
-
if (fullscreenLazyTui(tui)) return isFullscreenAtBottom(tui);
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function hideScrollButton(tui: any): void {
|
|
161
|
-
const changed = getScrollButtonVisible() || getScrollButtonHovered();
|
|
162
|
-
setScrollButtonVisible(false);
|
|
163
|
-
setScrollButtonHovered(false);
|
|
164
|
-
if (changed) tui.requestRender?.();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
168
|
-
if (
|
|
169
|
-
!fullscreenLazyTui(tui) ||
|
|
170
|
-
!toolMouseInteractionActive() ||
|
|
171
|
-
!isScrollNavigationInput(data) ||
|
|
172
|
-
scrollButtonSyncScheduled
|
|
173
|
-
)
|
|
174
|
-
return;
|
|
175
|
-
scrollButtonSyncScheduled = true;
|
|
176
|
-
const previousLines = tui.previousLines;
|
|
177
|
-
const check = (attempt: number) => {
|
|
178
|
-
scrollButtonSyncScheduled = false;
|
|
179
|
-
if (toolMouseTui !== tui) return;
|
|
180
|
-
// Pi renders on its own frame timer. Inspect the resulting viewport before
|
|
181
|
-
// showing the button so empty or non-scrollable contexts never flash it.
|
|
182
|
-
const rendered = tui.previousLines !== previousLines;
|
|
183
|
-
// fullscreen 下 isFollowingOutput 是即时状态,无需等待官方帧渲染。
|
|
184
|
-
if (!rendered && attempt < 4 && !fullscreenLazyTui(tui)) {
|
|
185
|
-
scrollButtonSyncScheduled = true;
|
|
186
|
-
const timer = setTimeout(() => check(attempt + 1), 16);
|
|
187
|
-
if (typeof timer === "object" && timer !== null && "unref" in timer) {
|
|
188
|
-
(timer as { unref: () => void }).unref();
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const nextVisible = !isAtContextBottom(tui);
|
|
193
|
-
if (nextVisible !== getScrollButtonVisible()) {
|
|
194
|
-
setScrollButtonVisible(nextVisible);
|
|
195
|
-
tui.requestRender?.();
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
process.nextTick(() => check(0));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
202
|
-
if (!fullscreenLazyTui(tui) || !toolMouseInteractionActive()) return;
|
|
203
|
-
if (matchesKey(data, "enter") || matchesKey(data, "return"))
|
|
204
|
-
hideScrollButton(tui);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function renderScrollButton(width: number, theme: any): string[] {
|
|
208
|
-
if (!getScrollButtonVisible() || !fullscreenLazyTui(toolMouseTui)) return [];
|
|
209
|
-
const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
|
|
210
|
-
const label = theme.fg(
|
|
211
|
-
getScrollButtonHovered() ? "text" : "accent",
|
|
212
|
-
`[ ↓ Back to bottom · ${shortcut} ]`,
|
|
213
|
-
);
|
|
214
|
-
const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
|
|
215
|
-
return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
|
|
216
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|