pi-one-ui 0.7.2 → 0.9.0
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 +52 -1
- package/README.en.md +5 -8
- package/README.md +6 -7
- package/extensions/app/config/footer.ts +21 -5
- package/extensions/app/config/renderer.ts +1 -12
- package/extensions/app/config/shell.ts +62 -60
- package/extensions/app/runtime/prototype-patch-registry.ts +13 -9
- package/extensions/app/runtime/tui-runtime.ts +19 -12
- package/extensions/app/settings/controller.ts +2 -2
- package/extensions/layouts/context/index.ts +2 -2
- package/extensions/layouts/context/message/user-message-osc.ts +1 -1
- package/extensions/layouts/context/message/user-message-styles.ts +9 -6
- package/extensions/layouts/context/message/user-message.ts +5 -5
- package/extensions/layouts/context/renderer/compact-mode.ts +2 -2
- package/extensions/layouts/context/renderer/default-mode.ts +10 -10
- package/extensions/layouts/context/renderer/index.ts +0 -1
- package/extensions/layouts/context/renderer/mouse/hover.ts +0 -4
- package/extensions/layouts/context/renderer/mouse/interaction.ts +106 -95
- package/extensions/layouts/context/renderer/mouse/layout.ts +0 -4
- package/extensions/layouts/context/renderer/mouse/scroll.ts +2 -183
- package/extensions/layouts/context/renderer/tool/diff/diff-edit-render.ts +1 -1
- package/extensions/layouts/context/renderer/tool/diff/index.ts +1 -1
- package/extensions/layouts/context/renderer/tool/grouping.ts +3 -3
- package/extensions/layouts/context/renderer/tool/message-display.ts +7 -7
- package/extensions/layouts/context/renderer/tool/result.ts +16 -16
- package/extensions/layouts/context/renderer/tool/toggle-render-cache.ts +9 -9
- package/extensions/layouts/editor/controller.ts +17 -17
- package/extensions/layouts/editor/editor-metadata-format.ts +5 -5
- package/extensions/layouts/editor/factory.ts +5 -5
- package/extensions/layouts/editor/minimalist-editor.ts +8 -8
- package/extensions/layouts/editor/ownership.ts +24 -20
- package/extensions/layouts/editor/ui.ts +10 -15
- package/extensions/layouts/footer/controller.ts +4 -4
- package/extensions/layouts/footer/data.ts +2 -2
- package/extensions/layouts/footer/extension-status.ts +4 -4
- package/extensions/layouts/footer/footer.ts +5 -2
- package/extensions/layouts/overlay/selector-border.ts +4 -4
- package/extensions/layouts/overlay/selector-controller.ts +1 -1
- package/extensions/layouts/overlay/settings-previews.ts +2 -2
- package/extensions/layouts/working-line/interaction-summary.ts +3 -1
- package/extensions/layouts/working-line/working-line.ts +4 -4
- package/extensions/services/package-data.ts +1 -1
- package/extensions/shared/style.ts +66 -56
- package/extensions/tools/patch-keys.ts +22 -25
- package/package.json +7 -7
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { showMoreHintText } from "./show-more-hint.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* 接管三个消息组件(`<skill>` 块、压缩摘要、分支摘要),
|
|
16
|
+
* 接管三个消息组件(`<skill>` 块、压缩摘要、分支摘要),pi-one-ui on 时渲染为
|
|
17
17
|
* 与工具调用一致的单行风格(● Title · hint),off 时回退原生。
|
|
18
18
|
*
|
|
19
19
|
* 三者结构同构:Box + markdownTheme + updateDisplay,构造/setExpanded/invalidate
|
|
@@ -68,12 +68,12 @@ function ensureHintHover(component: any): void {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function
|
|
71
|
+
function renderPolishedMessage(component: any, kind: DisplayKind): void {
|
|
72
72
|
const theme = displayTheme;
|
|
73
73
|
if (!theme) return; // 主题未就绪时保留原生渲染
|
|
74
74
|
if (component.bgFn) component.setBgFn?.(undefined); // 与工具调用一致,去掉灰底
|
|
75
75
|
if (component.paddingY !== 0) {
|
|
76
|
-
component.
|
|
76
|
+
component._oneuiOriginalPaddingY = component.paddingY;
|
|
77
77
|
component.paddingY = 0; // 工具组件 paddingY=0;原生 Box 默认 1,会上下各留一个空行
|
|
78
78
|
}
|
|
79
79
|
component.clear();
|
|
@@ -128,16 +128,16 @@ export function installMessageDisplayRendering(): () => void {
|
|
|
128
128
|
const installed = function (this: any) {
|
|
129
129
|
if (patch.active && config.mode !== "off") {
|
|
130
130
|
try {
|
|
131
|
-
|
|
131
|
+
renderPolishedMessage(this, kind);
|
|
132
132
|
return;
|
|
133
133
|
} catch {
|
|
134
134
|
// 渲染失败回退原生
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
// 回退原生前恢复 paddingY,避免原生渲染丢失上下内边距
|
|
138
|
-
if (this.
|
|
139
|
-
this.paddingY = this.
|
|
140
|
-
delete this.
|
|
138
|
+
if (this._oneuiOriginalPaddingY !== undefined) {
|
|
139
|
+
this.paddingY = this._oneuiOriginalPaddingY;
|
|
140
|
+
delete this._oneuiOriginalPaddingY;
|
|
141
141
|
}
|
|
142
142
|
original.call(this);
|
|
143
143
|
};
|
|
@@ -247,8 +247,8 @@ const activeAnimationContexts = new Set<any>();
|
|
|
247
247
|
let sharedAnimationTimer: ReturnType<typeof setTimeout> | null = null;
|
|
248
248
|
|
|
249
249
|
function clearAnimation(context: any) {
|
|
250
|
-
if (!context?.state?.
|
|
251
|
-
context.state.
|
|
250
|
+
if (!context?.state?.oneuiAnimationScheduled) return;
|
|
251
|
+
context.state.oneuiAnimationScheduled = false;
|
|
252
252
|
activeAnimationContexts.delete(context);
|
|
253
253
|
if (activeAnimationContexts.size === 0 && sharedAnimationTimer) {
|
|
254
254
|
clearTimeout(sharedAnimationTimer);
|
|
@@ -258,7 +258,7 @@ function clearAnimation(context: any) {
|
|
|
258
258
|
|
|
259
259
|
export function clearAllAnimations() {
|
|
260
260
|
for (const ctx of activeAnimationContexts) {
|
|
261
|
-
ctx.state.
|
|
261
|
+
ctx.state.oneuiAnimationScheduled = false;
|
|
262
262
|
}
|
|
263
263
|
activeAnimationContexts.clear();
|
|
264
264
|
if (sharedAnimationTimer) {
|
|
@@ -272,8 +272,8 @@ export function scheduleAnimation(
|
|
|
272
272
|
intervalMs = TOOL_LOADING_INTERVAL_MS,
|
|
273
273
|
) {
|
|
274
274
|
const state = (context.state ??= {});
|
|
275
|
-
if (state.
|
|
276
|
-
state.
|
|
275
|
+
if (state.oneuiAnimationScheduled) return;
|
|
276
|
+
state.oneuiAnimationScheduled = true;
|
|
277
277
|
activeAnimationContexts.add(context);
|
|
278
278
|
if (!sharedAnimationTimer) {
|
|
279
279
|
sharedAnimationTimer = setTimeout(() => {
|
|
@@ -281,7 +281,7 @@ export function scheduleAnimation(
|
|
|
281
281
|
const contexts = Array.from(activeAnimationContexts);
|
|
282
282
|
activeAnimationContexts.clear();
|
|
283
283
|
for (const ctx of contexts) {
|
|
284
|
-
ctx.state.
|
|
284
|
+
ctx.state.oneuiAnimationScheduled = false;
|
|
285
285
|
ctx.invalidate?.();
|
|
286
286
|
}
|
|
287
287
|
}, intervalMs);
|
|
@@ -307,8 +307,8 @@ export function settledIcon(
|
|
|
307
307
|
export function setToolVisualState(context: any, visualState: ToolVisualState) {
|
|
308
308
|
const state = (context.state ??= {});
|
|
309
309
|
if (visualState !== "pending") clearAnimation(context);
|
|
310
|
-
if (state.
|
|
311
|
-
state.
|
|
310
|
+
if (state.oneuiToolVisualState === visualState) return;
|
|
311
|
+
state.oneuiToolVisualState = visualState;
|
|
312
312
|
// Do not invalidate synchronously from renderResult. Pi is already rendering
|
|
313
313
|
// this tool row; recursively scheduling another render here can retain both
|
|
314
314
|
// the finalized result component and its previous secondary/partial component,
|
|
@@ -317,7 +317,7 @@ export function setToolVisualState(context: any, visualState: ToolVisualState) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
function getToolVisualState(context: any): ToolVisualState | undefined {
|
|
320
|
-
return context?.state?.
|
|
320
|
+
return context?.state?.oneuiToolVisualState as ToolVisualState | undefined;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
export function resolveToolVisualState(
|
|
@@ -346,7 +346,7 @@ export function toolIconColor(
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
export function isToolExpanded(options: any, context: any): boolean {
|
|
349
|
-
const local = context?.state?.
|
|
349
|
+
const local = context?.state?.oneuiToolExpanded;
|
|
350
350
|
return typeof local === "boolean"
|
|
351
351
|
? local
|
|
352
352
|
: Boolean(options?.expanded ?? context?.expanded);
|
|
@@ -407,7 +407,7 @@ export type ToolIoSection = "input" | "output";
|
|
|
407
407
|
// Module-local token: /reload creates a fresh token, so stale view instances are
|
|
408
408
|
// replaced instead of retaining their old render implementation.
|
|
409
409
|
const EXPANDED_TOOL_IO_VIEW_GENERATION = Symbol(
|
|
410
|
-
"
|
|
410
|
+
"pi-one-ui.expanded-tool-io-view",
|
|
411
411
|
);
|
|
412
412
|
|
|
413
413
|
/**
|
|
@@ -931,7 +931,7 @@ export function renderExpandedToolResult(
|
|
|
931
931
|
|
|
932
932
|
// Prefer structured Input/Output when we have args or non-empty output.
|
|
933
933
|
if (inputBody.trim() || outputBody.trim()) {
|
|
934
|
-
const cached = context?.state?.
|
|
934
|
+
const cached = context?.state?.oneuiExpandedIoView;
|
|
935
935
|
let view: ExpandedToolIoView;
|
|
936
936
|
if (isExpandedToolIoView(lastComponent)) {
|
|
937
937
|
view = lastComponent;
|
|
@@ -962,8 +962,8 @@ export function renderExpandedToolResult(
|
|
|
962
962
|
}
|
|
963
963
|
|
|
964
964
|
if (context?.state) {
|
|
965
|
-
context.state.
|
|
966
|
-
context.state.
|
|
965
|
+
context.state.oneuiIoView = undefined;
|
|
966
|
+
context.state.oneuiExpandedIoView = undefined;
|
|
967
967
|
}
|
|
968
968
|
const color = isError ? "error" : "muted";
|
|
969
969
|
return new Text(theme.fg(color, renderCollapsedToolResult("Done")), 0, 0);
|
|
@@ -1079,8 +1079,8 @@ function rememberIoView(context: any, view: ExpandedToolIoView): void {
|
|
|
1079
1079
|
ioViewInvalidators.set(view, () => context.invalidate());
|
|
1080
1080
|
}
|
|
1081
1081
|
if (!context.state || typeof context.state !== "object") context.state = {};
|
|
1082
|
-
context.state.
|
|
1083
|
-
context.state.
|
|
1082
|
+
context.state.oneuiIoView = view;
|
|
1083
|
+
context.state.oneuiExpandedIoView = view;
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
1086
|
/** hover 状态变更后刷新上一个/下一个视图(context.invalidate 缓存于 ioViewInvalidators)。 */
|
|
@@ -35,7 +35,7 @@ type RenderSlot = {
|
|
|
35
35
|
shell: "box" | "self" | "text" | null;
|
|
36
36
|
callComponent: unknown;
|
|
37
37
|
resultComponent: unknown;
|
|
38
|
-
/** rendererState 中
|
|
38
|
+
/** rendererState 中 pi-one-ui 渲染层的指针快照(切槽重装时恢复)。 */
|
|
39
39
|
stateSnapshot: [unknown, unknown, unknown];
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -89,7 +89,7 @@ function fingerprintOf(
|
|
|
89
89
|
* 不再排期,长命令期间 loading 图标就会冻结在当前帧。
|
|
90
90
|
*/
|
|
91
91
|
function schedulesPendingAnimation(component: any): boolean {
|
|
92
|
-
const visualState = component?.rendererState?.
|
|
92
|
+
const visualState = component?.rendererState?.oneuiToolVisualState as
|
|
93
93
|
| string
|
|
94
94
|
| undefined;
|
|
95
95
|
const pending =
|
|
@@ -166,9 +166,9 @@ function syncShell(component: any, shell: "box" | "self" | "text"): void {
|
|
|
166
166
|
function stateSnapshotOf(component: any): [unknown, unknown, unknown] {
|
|
167
167
|
const state = component?.rendererState;
|
|
168
168
|
return [
|
|
169
|
-
state?.
|
|
170
|
-
state?.
|
|
171
|
-
state?.
|
|
169
|
+
state?.oneuiIoView,
|
|
170
|
+
state?.oneuiExpandedIoView,
|
|
171
|
+
state?.oneuiToolVisualState,
|
|
172
172
|
];
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -186,14 +186,14 @@ function restoreStateSnapshot(
|
|
|
186
186
|
const state = (component.rendererState ??= {});
|
|
187
187
|
// ioView/visualState 每槽构建时都会写入,按槽恢复;
|
|
188
188
|
// expandedIoView 是跨槽保留指针(折叠 renderer 不清),只在展开槽恢复。
|
|
189
|
-
state.
|
|
190
|
-
state.
|
|
191
|
-
if (slotKey === "expanded") state.
|
|
189
|
+
state.oneuiIoView = snapshot[0];
|
|
190
|
+
state.oneuiToolVisualState = snapshot[1];
|
|
191
|
+
if (slotKey === "expanded") state.oneuiExpandedIoView = snapshot[2];
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
195
|
* 把缓存槽的 call/result 组件装回目标壳容器并恢复派生字段。
|
|
196
|
-
* 同时恢复
|
|
196
|
+
* 同时恢复 pi-one-ui 渲染层的 rendererState 指针快照(ioView / expandedIoView /
|
|
197
197
|
* visualState),补齐切槽跳过 renderer 调用导致的状态缺失;pending 展开时
|
|
198
198
|
* 重新调度加载动画。text fallback 槽(无容器装载语义)返回 false,调用方
|
|
199
199
|
* 退化为重建。
|
|
@@ -20,9 +20,9 @@ import {
|
|
|
20
20
|
} from "./factory.ts";
|
|
21
21
|
import {
|
|
22
22
|
type EditorFactory,
|
|
23
|
-
|
|
23
|
+
getPolishedEditorBaseFactory,
|
|
24
24
|
isOwnedEditorFactory,
|
|
25
|
-
|
|
25
|
+
isPolishedEditorFactory,
|
|
26
26
|
} from "./ownership.ts";
|
|
27
27
|
|
|
28
28
|
export type EditorChangeResult = { ok: true } | { ok: false; reason: string };
|
|
@@ -72,18 +72,18 @@ export class EditorLayoutController {
|
|
|
72
72
|
* Installs or reconciles the configured Editor for an active session.
|
|
73
73
|
*
|
|
74
74
|
* @param ctx Active Pi extension context.
|
|
75
|
-
* @param
|
|
75
|
+
* @param allowStalePolished Whether a stale predecessor may be replaced.
|
|
76
76
|
* @returns The replacement result when a host factory changed.
|
|
77
77
|
*/
|
|
78
78
|
install(
|
|
79
79
|
ctx: ExtensionContext,
|
|
80
|
-
|
|
80
|
+
allowStalePolished = true,
|
|
81
81
|
): EditorChangeResult | undefined {
|
|
82
82
|
if (!this.isTuiContext(ctx)) return;
|
|
83
83
|
this.activeTuiContext = ctx;
|
|
84
84
|
if (this.isEditorEnabled()) this.clearEditorOwnership();
|
|
85
|
-
else this.uninstall(ctx, {
|
|
86
|
-
const result = this.reconcile(ctx, {
|
|
85
|
+
else this.uninstall(ctx, { allowStalePolished });
|
|
86
|
+
const result = this.reconcile(ctx, { allowStalePolished });
|
|
87
87
|
this.scheduleReconciliation(ctx);
|
|
88
88
|
return result;
|
|
89
89
|
}
|
|
@@ -97,15 +97,15 @@ export class EditorLayoutController {
|
|
|
97
97
|
*/
|
|
98
98
|
reconcile(
|
|
99
99
|
ctx: ExtensionContext,
|
|
100
|
-
options: {
|
|
100
|
+
options: { allowStalePolished?: boolean } = {},
|
|
101
101
|
): EditorChangeResult | undefined {
|
|
102
102
|
try {
|
|
103
103
|
if (this.isEditorEnabled()) {
|
|
104
104
|
const currentFactory = ctx.ui.getEditorComponent();
|
|
105
105
|
if (
|
|
106
|
-
|
|
106
|
+
isPolishedEditorFactory(currentFactory) &&
|
|
107
107
|
!this.ownsFactory(currentFactory) &&
|
|
108
|
-
!options.
|
|
108
|
+
!options.allowStalePolished
|
|
109
109
|
) {
|
|
110
110
|
this.clearEditorOwnership();
|
|
111
111
|
return;
|
|
@@ -254,7 +254,7 @@ export class EditorLayoutController {
|
|
|
254
254
|
*/
|
|
255
255
|
uninstall(
|
|
256
256
|
ctx: ExtensionContext,
|
|
257
|
-
options: {
|
|
257
|
+
options: { allowStalePolished?: boolean } = {},
|
|
258
258
|
): EditorChangeResult {
|
|
259
259
|
let currentFactory: EditorFactory | undefined;
|
|
260
260
|
try {
|
|
@@ -266,17 +266,17 @@ export class EditorLayoutController {
|
|
|
266
266
|
"the current editor factory could not be observed safely; reload Pi to apply this change",
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
|
-
if (!currentFactory || !
|
|
269
|
+
if (!currentFactory || !isPolishedEditorFactory(currentFactory)) {
|
|
270
270
|
this.clearEditorOwnership();
|
|
271
271
|
return { ok: true };
|
|
272
272
|
}
|
|
273
|
-
if (!this.ownsFactory(currentFactory) && !options.
|
|
273
|
+
if (!this.ownsFactory(currentFactory) && !options.allowStalePolished) {
|
|
274
274
|
this.clearEditorOwnership();
|
|
275
275
|
return { ok: true };
|
|
276
276
|
}
|
|
277
277
|
const result = this.replace(
|
|
278
278
|
ctx,
|
|
279
|
-
|
|
279
|
+
getPolishedEditorBaseFactory(currentFactory) ??
|
|
280
280
|
(this.editorInstallMode === "wrapper"
|
|
281
281
|
? this.wrappedEditorFactory
|
|
282
282
|
: undefined),
|
|
@@ -299,7 +299,7 @@ export class EditorLayoutController {
|
|
|
299
299
|
if (currentFactory && this.ownsFactory(currentFactory)) {
|
|
300
300
|
this.replace(
|
|
301
301
|
ctx,
|
|
302
|
-
|
|
302
|
+
getPolishedEditorBaseFactory(currentFactory) ??
|
|
303
303
|
(this.editorInstallMode === "wrapper"
|
|
304
304
|
? this.wrappedEditorFactory
|
|
305
305
|
: undefined),
|
|
@@ -395,7 +395,7 @@ export class EditorLayoutController {
|
|
|
395
395
|
* @param factory Factory created by this controller.
|
|
396
396
|
*/
|
|
397
397
|
private trackFactory(factory: EditorFactory): void {
|
|
398
|
-
const baseFactory =
|
|
398
|
+
const baseFactory = getPolishedEditorBaseFactory(factory);
|
|
399
399
|
this.wrappedEditorFactory = baseFactory;
|
|
400
400
|
this.installedEditorFactory = factory;
|
|
401
401
|
this.editorInstallMode = baseFactory ? "wrapper" : "standalone";
|
|
@@ -444,8 +444,8 @@ export class EditorLayoutController {
|
|
|
444
444
|
return { ok: true };
|
|
445
445
|
}
|
|
446
446
|
const baseFactory =
|
|
447
|
-
|
|
448
|
-
(currentFactory && !
|
|
447
|
+
getPolishedEditorBaseFactory(currentFactory) ??
|
|
448
|
+
(currentFactory && !isPolishedEditorFactory(currentFactory)
|
|
449
449
|
? currentFactory
|
|
450
450
|
: undefined);
|
|
451
451
|
const runtime = this.factoryRuntime(ctx);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PolishedTuiConfig } from "../../app/config/shell.ts";
|
|
3
3
|
import {
|
|
4
4
|
EDITOR_ACCENT_FALLBACK,
|
|
5
5
|
renderThemeStyleOrFallback,
|
|
@@ -140,7 +140,7 @@ export function sanitizeEditorMetadataText(value: string): string {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
function editorThinkingStyle(
|
|
143
|
-
config:
|
|
143
|
+
config: PolishedTuiConfig,
|
|
144
144
|
level: string,
|
|
145
145
|
): string | undefined {
|
|
146
146
|
switch (level.toLowerCase()) {
|
|
@@ -171,7 +171,7 @@ function renderVariable(
|
|
|
171
171
|
name: string,
|
|
172
172
|
values: EditorMetadataValues,
|
|
173
173
|
uiTheme: Theme,
|
|
174
|
-
config:
|
|
174
|
+
config: PolishedTuiConfig,
|
|
175
175
|
): { plain: string; styled: string } {
|
|
176
176
|
const thinking =
|
|
177
177
|
values.thinking.toLowerCase() === "off" ? "" : values.thinking;
|
|
@@ -235,7 +235,7 @@ function renderTokens(
|
|
|
235
235
|
tokens: FormatToken[],
|
|
236
236
|
values: EditorMetadataValues,
|
|
237
237
|
uiTheme: Theme,
|
|
238
|
-
config:
|
|
238
|
+
config: PolishedTuiConfig,
|
|
239
239
|
): RenderedTokens {
|
|
240
240
|
let styled = "";
|
|
241
241
|
let hasDynamic = false;
|
|
@@ -275,7 +275,7 @@ export function renderEditorMetadataFormat(
|
|
|
275
275
|
format: string,
|
|
276
276
|
values: EditorMetadataValues,
|
|
277
277
|
uiTheme: Theme,
|
|
278
|
-
config:
|
|
278
|
+
config: PolishedTuiConfig,
|
|
279
279
|
): string {
|
|
280
280
|
return renderTokens(
|
|
281
281
|
parseFooterFormat(sanitizeEditorMetadataText(format)),
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type EditorFactory,
|
|
12
12
|
markEditorFactory,
|
|
13
13
|
markWrappedEditorFactory,
|
|
14
|
-
type
|
|
14
|
+
type PolishedEditorFactory,
|
|
15
15
|
} from "./ownership.ts";
|
|
16
16
|
import { PolishedEditor, WrappedPolishedEditor } from "./ui.ts";
|
|
17
17
|
|
|
@@ -38,7 +38,7 @@ export type EditorFactoryRuntime = {
|
|
|
38
38
|
export function createEditorFactory(
|
|
39
39
|
ctx: ExtensionContext,
|
|
40
40
|
runtime: EditorFactoryRuntime,
|
|
41
|
-
):
|
|
41
|
+
): PolishedEditorFactory {
|
|
42
42
|
const factory = ((
|
|
43
43
|
tui: TUI,
|
|
44
44
|
theme: EditorTheme,
|
|
@@ -56,7 +56,7 @@ export function createEditorFactory(
|
|
|
56
56
|
() => editorDecoration(ctx, runtime),
|
|
57
57
|
runtime.onDecorationActive,
|
|
58
58
|
);
|
|
59
|
-
}) as
|
|
59
|
+
}) as PolishedEditorFactory;
|
|
60
60
|
return markEditorFactory(factory, runtime.ownerToken);
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -72,7 +72,7 @@ export function createWrappedEditorFactory(
|
|
|
72
72
|
ctx: ExtensionContext,
|
|
73
73
|
baseFactory: EditorFactory,
|
|
74
74
|
runtime: EditorFactoryRuntime,
|
|
75
|
-
):
|
|
75
|
+
): PolishedEditorFactory {
|
|
76
76
|
const factory = ((
|
|
77
77
|
tui: TUI,
|
|
78
78
|
theme: EditorTheme,
|
|
@@ -88,7 +88,7 @@ export function createWrappedEditorFactory(
|
|
|
88
88
|
() => editorDecoration(ctx, runtime),
|
|
89
89
|
runtime.onDecorationActive,
|
|
90
90
|
);
|
|
91
|
-
}) as
|
|
91
|
+
}) as PolishedEditorFactory;
|
|
92
92
|
return markWrappedEditorFactory(factory, baseFactory, runtime.ownerToken);
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { basename, isAbsolute, relative, sep } from "node:path";
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
|
-
import type { ColorSpec,
|
|
4
|
+
import type { ColorSpec, PolishedTuiConfig } from "../../app/config/shell.ts";
|
|
5
5
|
import { formatCwdLabel, formatElapsedDuration } from "../../shared/format.ts";
|
|
6
6
|
import { sanitizeEditorMetadataText } from "./editor-metadata-format.ts";
|
|
7
7
|
|
|
@@ -46,7 +46,7 @@ export type MinimalistFrameOptions = {
|
|
|
46
46
|
inputText: string;
|
|
47
47
|
metadata: MinimalistEditorMetadata;
|
|
48
48
|
uiTheme: Theme;
|
|
49
|
-
config:
|
|
49
|
+
config: PolishedTuiConfig;
|
|
50
50
|
borderColor?: (text: string) => string;
|
|
51
51
|
};
|
|
52
52
|
|
|
@@ -103,7 +103,7 @@ function joinStyled(parts: string[], separator: string): string {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function thinkingStyle(
|
|
106
|
-
config:
|
|
106
|
+
config: PolishedTuiConfig,
|
|
107
107
|
level: string,
|
|
108
108
|
): string | undefined {
|
|
109
109
|
switch (level.toLowerCase()) {
|
|
@@ -134,7 +134,7 @@ function renderTopLeft(
|
|
|
134
134
|
inputText: string,
|
|
135
135
|
metadata: MinimalistEditorMetadata,
|
|
136
136
|
uiTheme: Theme,
|
|
137
|
-
config:
|
|
137
|
+
config: PolishedTuiConfig,
|
|
138
138
|
includeSessionName = true,
|
|
139
139
|
): string {
|
|
140
140
|
const trimmed = inputText.trimStart();
|
|
@@ -184,7 +184,7 @@ function renderTopLeft(
|
|
|
184
184
|
function renderTopRight(
|
|
185
185
|
metadata: MinimalistEditorMetadata,
|
|
186
186
|
uiTheme: Theme,
|
|
187
|
-
config:
|
|
187
|
+
config: PolishedTuiConfig,
|
|
188
188
|
renderBorder: (text: string) => string,
|
|
189
189
|
renderThinking: (text: string) => string,
|
|
190
190
|
): string {
|
|
@@ -223,7 +223,7 @@ function renderTopRight(
|
|
|
223
223
|
function renderBottomLeft(
|
|
224
224
|
metadata: MinimalistEditorMetadata,
|
|
225
225
|
uiTheme: Theme,
|
|
226
|
-
config:
|
|
226
|
+
config: PolishedTuiConfig,
|
|
227
227
|
): string {
|
|
228
228
|
if (!config.components.editor.styles.minimalist.showGit) return "";
|
|
229
229
|
const branch = sanitizeEditorMetadataText(metadata.branch ?? "");
|
|
@@ -251,7 +251,7 @@ function renderBottomLeft(
|
|
|
251
251
|
|
|
252
252
|
function minimalistCwdLabel(
|
|
253
253
|
metadata: MinimalistEditorMetadata,
|
|
254
|
-
config:
|
|
254
|
+
config: PolishedTuiConfig,
|
|
255
255
|
): string {
|
|
256
256
|
const full = () =>
|
|
257
257
|
formatCwdLabel(metadata.cwd, "", { mode: "full", depth: 0 });
|
|
@@ -276,7 +276,7 @@ function minimalistCwdLabel(
|
|
|
276
276
|
function renderBottomRight(
|
|
277
277
|
metadata: MinimalistEditorMetadata,
|
|
278
278
|
uiTheme: Theme,
|
|
279
|
-
config:
|
|
279
|
+
config: PolishedTuiConfig,
|
|
280
280
|
): string {
|
|
281
281
|
if (config.components.editor.styles.minimalist.showCwd === false) {
|
|
282
282
|
return "";
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Symbol used to identify factories created by the Editor layout. All wrappers
|
|
5
|
+
* and marks live in one process, so the key only has to be stable across
|
|
6
|
+
* `/reload` within that process.
|
|
7
|
+
*/
|
|
8
|
+
export const POLISHED_EDITOR_FACTORY = Symbol.for("pi-one-ui.editor-factory");
|
|
5
9
|
|
|
6
10
|
/** Symbol used to retain the wrapped third-party editor factory. */
|
|
7
|
-
export const
|
|
8
|
-
"pi-
|
|
11
|
+
export const POLISHED_EDITOR_BASE_FACTORY = Symbol.for(
|
|
12
|
+
"pi-one-ui.editor-base-factory",
|
|
9
13
|
);
|
|
10
14
|
|
|
11
15
|
/** Symbol used to identify the active Editor layout owner. */
|
|
12
|
-
export const
|
|
16
|
+
export const POLISHED_EDITOR_OWNER = Symbol.for("pi-one-ui.editor-owner");
|
|
13
17
|
|
|
14
18
|
export type EditorFactory = NonNullable<
|
|
15
19
|
Parameters<ExtensionContext["ui"]["setEditorComponent"]>[0]
|
|
16
20
|
>;
|
|
17
21
|
|
|
18
|
-
export type
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
+
export type PolishedEditorFactory = EditorFactory & {
|
|
23
|
+
[POLISHED_EDITOR_FACTORY]?: true;
|
|
24
|
+
[POLISHED_EDITOR_BASE_FACTORY]?: EditorFactory;
|
|
25
|
+
[POLISHED_EDITOR_OWNER]?: symbol;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -27,11 +31,11 @@ export type ZentuiEditorFactory = EditorFactory & {
|
|
|
27
31
|
* @param factory Candidate host editor factory.
|
|
28
32
|
* @returns Whether the factory carries the Editor layout marker.
|
|
29
33
|
*/
|
|
30
|
-
export function
|
|
34
|
+
export function isPolishedEditorFactory(
|
|
31
35
|
factory: EditorFactory | undefined,
|
|
32
36
|
): boolean {
|
|
33
37
|
return Boolean(
|
|
34
|
-
(factory as
|
|
38
|
+
(factory as PolishedEditorFactory | undefined)?.[POLISHED_EDITOR_FACTORY],
|
|
35
39
|
);
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -41,11 +45,11 @@ export function isZentuiEditorFactory(
|
|
|
41
45
|
* @param factory Candidate host editor factory.
|
|
42
46
|
* @returns The retained base factory, when present.
|
|
43
47
|
*/
|
|
44
|
-
export function
|
|
48
|
+
export function getPolishedEditorBaseFactory(
|
|
45
49
|
factory: EditorFactory | undefined,
|
|
46
50
|
): EditorFactory | undefined {
|
|
47
|
-
return (factory as
|
|
48
|
-
|
|
51
|
+
return (factory as PolishedEditorFactory | undefined)?.[
|
|
52
|
+
POLISHED_EDITOR_BASE_FACTORY
|
|
49
53
|
];
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -61,7 +65,7 @@ export function isOwnedEditorFactory(
|
|
|
61
65
|
ownerToken: symbol,
|
|
62
66
|
): boolean {
|
|
63
67
|
return (
|
|
64
|
-
(factory as
|
|
68
|
+
(factory as PolishedEditorFactory | undefined)?.[POLISHED_EDITOR_OWNER] ===
|
|
65
69
|
ownerToken
|
|
66
70
|
);
|
|
67
71
|
}
|
|
@@ -77,9 +81,9 @@ export function markEditorFactory<T extends EditorFactory>(
|
|
|
77
81
|
factory: T,
|
|
78
82
|
ownerToken: symbol,
|
|
79
83
|
): T {
|
|
80
|
-
const marked = factory as T &
|
|
81
|
-
marked[
|
|
82
|
-
marked[
|
|
84
|
+
const marked = factory as T & PolishedEditorFactory;
|
|
85
|
+
marked[POLISHED_EDITOR_FACTORY] = true;
|
|
86
|
+
marked[POLISHED_EDITOR_OWNER] = ownerToken;
|
|
83
87
|
return factory;
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -97,7 +101,7 @@ export function markWrappedEditorFactory<T extends EditorFactory>(
|
|
|
97
101
|
ownerToken: symbol,
|
|
98
102
|
): T {
|
|
99
103
|
const marked = markEditorFactory(factory, ownerToken) as T &
|
|
100
|
-
|
|
101
|
-
marked[
|
|
104
|
+
PolishedEditorFactory;
|
|
105
|
+
marked[POLISHED_EDITOR_BASE_FACTORY] = baseFactory;
|
|
102
106
|
return factory;
|
|
103
107
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
truncateToWidth,
|
|
13
13
|
visibleWidth,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
|
-
import type {
|
|
15
|
+
import type { PolishedTuiConfig } from "../../app/config/shell.ts";
|
|
16
16
|
import {
|
|
17
17
|
EDITOR_ACCENT_FALLBACK,
|
|
18
18
|
EDITOR_BORDER_FALLBACK,
|
|
@@ -25,8 +25,6 @@ import {
|
|
|
25
25
|
renderMinimalistFrame,
|
|
26
26
|
} from "./minimalist-editor.ts";
|
|
27
27
|
|
|
28
|
-
const LEGACY_SPLIT_POLISHED_FRAME = Symbol.for("pi-zentui.polished-frame");
|
|
29
|
-
|
|
30
28
|
export type ViewportCounts = {
|
|
31
29
|
above?: string;
|
|
32
30
|
below?: string;
|
|
@@ -101,7 +99,7 @@ type MinimalistFrameAdapterOptions = {
|
|
|
101
99
|
autocompleteSource: AutocompleteEditorInternals;
|
|
102
100
|
autocompleteCapture?: AutocompleteCapture;
|
|
103
101
|
uiTheme: Theme;
|
|
104
|
-
config:
|
|
102
|
+
config: PolishedTuiConfig;
|
|
105
103
|
inputText: string;
|
|
106
104
|
metadata: MinimalistEditorMetadata;
|
|
107
105
|
ownedFrame?: PolishedFrameSplit;
|
|
@@ -293,7 +291,7 @@ export function renderWithAutocompleteCapture<T>(
|
|
|
293
291
|
* (保持 Pi 原生 effort/主题变色不动)。
|
|
294
292
|
*/
|
|
295
293
|
export function offEditorBorderColor(
|
|
296
|
-
config:
|
|
294
|
+
config: PolishedTuiConfig,
|
|
297
295
|
uiTheme: Theme,
|
|
298
296
|
): ((text: string) => string) | undefined {
|
|
299
297
|
const editor = config.components.editor;
|
|
@@ -316,7 +314,7 @@ function fillLine(content: string, width: number): string {
|
|
|
316
314
|
}
|
|
317
315
|
|
|
318
316
|
function getEditorChromeWidths(
|
|
319
|
-
config:
|
|
317
|
+
config: PolishedTuiConfig,
|
|
320
318
|
uiTheme: Theme,
|
|
321
319
|
reset: string,
|
|
322
320
|
) {
|
|
@@ -353,7 +351,7 @@ function parseEditorBorder(
|
|
|
353
351
|
|
|
354
352
|
function unwrapPolishedFrameOnly(
|
|
355
353
|
lines: string[],
|
|
356
|
-
config:
|
|
354
|
+
config: PolishedTuiConfig,
|
|
357
355
|
uiTheme: Theme,
|
|
358
356
|
): { editorLines: string[]; viewport: ViewportCounts } | undefined {
|
|
359
357
|
if (lines.length < 5) return undefined;
|
|
@@ -379,7 +377,7 @@ function unwrapPolishedFrameOnly(
|
|
|
379
377
|
|
|
380
378
|
function splitPolishedFrame(
|
|
381
379
|
lines: string[],
|
|
382
|
-
config:
|
|
380
|
+
config: PolishedTuiConfig,
|
|
383
381
|
uiTheme: Theme,
|
|
384
382
|
): PolishedFrameSplit | undefined {
|
|
385
383
|
if (!parseEditorBorder(lines[0] ?? "", "above")) return undefined;
|
|
@@ -397,9 +395,8 @@ function splitPolishedFrame(
|
|
|
397
395
|
}
|
|
398
396
|
|
|
399
397
|
function inspectPolishedFrameProvenance(
|
|
400
|
-
base: WrappedEditor,
|
|
401
398
|
rendered: string[],
|
|
402
|
-
config:
|
|
399
|
+
config: PolishedTuiConfig,
|
|
403
400
|
uiTheme: Theme,
|
|
404
401
|
): { safe: boolean; ownedFrame?: PolishedFrameSplit } {
|
|
405
402
|
const provenance = POLISHED_FRAME_SPLITS.get(rendered);
|
|
@@ -411,7 +408,6 @@ function inspectPolishedFrameProvenance(
|
|
|
411
408
|
const ownedFrame = provenanceMatches ? provenance?.split : undefined;
|
|
412
409
|
const unsafe =
|
|
413
410
|
Boolean(provenance && !provenanceMatches) ||
|
|
414
|
-
LEGACY_SPLIT_POLISHED_FRAME in base ||
|
|
415
411
|
(!ownedFrame && Boolean(splitPolishedFrame(rendered, config, uiTheme)));
|
|
416
412
|
return { safe: !unsafe, ownedFrame };
|
|
417
413
|
}
|
|
@@ -485,7 +481,7 @@ export class PolishedEditor extends CustomEditor {
|
|
|
485
481
|
private readonly getThinkingLevel: () => string | undefined;
|
|
486
482
|
private readonly getMinimalistMetadata: () => MinimalistEditorMetadata;
|
|
487
483
|
private readonly onMinimalistDecorationChange: (active: boolean) => void;
|
|
488
|
-
private readonly getConfig: () =>
|
|
484
|
+
private readonly getConfig: () => PolishedTuiConfig;
|
|
489
485
|
private readonly uiTheme: Theme;
|
|
490
486
|
|
|
491
487
|
constructor(
|
|
@@ -493,7 +489,7 @@ export class PolishedEditor extends CustomEditor {
|
|
|
493
489
|
theme: EditorTheme,
|
|
494
490
|
keybindings: KeybindingsManager,
|
|
495
491
|
uiTheme: Theme,
|
|
496
|
-
getConfig: () =>
|
|
492
|
+
getConfig: () => PolishedTuiConfig,
|
|
497
493
|
getModelMeta: () => EditorMeta,
|
|
498
494
|
getThinkingLevel: () => string | undefined,
|
|
499
495
|
getMinimalistMetadata: () => MinimalistEditorMetadata = () => ({ cwd: "" }),
|
|
@@ -569,7 +565,7 @@ export class WrappedPolishedEditor implements EditorComponent {
|
|
|
569
565
|
constructor(
|
|
570
566
|
private readonly base: WrappedEditor,
|
|
571
567
|
private readonly uiTheme: Theme,
|
|
572
|
-
private readonly getConfig: () =>
|
|
568
|
+
private readonly getConfig: () => PolishedTuiConfig,
|
|
573
569
|
private readonly getModelMeta: () => EditorMeta,
|
|
574
570
|
private readonly getThinkingLevel: () => string | undefined,
|
|
575
571
|
private readonly getMinimalistMetadata: () => MinimalistEditorMetadata = () => ({
|
|
@@ -702,7 +698,6 @@ export class WrappedPolishedEditor implements EditorComponent {
|
|
|
702
698
|
}
|
|
703
699
|
try {
|
|
704
700
|
const provenance = inspectPolishedFrameProvenance(
|
|
705
|
-
this.base,
|
|
706
701
|
captured.value,
|
|
707
702
|
config,
|
|
708
703
|
this.uiTheme,
|