pi-zentui 0.2.2 → 0.2.5

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.
@@ -32,11 +32,13 @@ import { PolishedEditor, WrappedPolishedEditor } from "./ui";
32
32
  import { installUserMessageStyle } from "./user-message";
33
33
 
34
34
  const ZENTUI_EDITOR_FACTORY = Symbol.for("pi-zentui.editor-factory");
35
+ const ZENTUI_EDITOR_BASE_FACTORY = Symbol.for("pi-zentui.editor-base-factory");
35
36
 
36
37
  type EditorFactory = NonNullable<Parameters<ExtensionContext["ui"]["setEditorComponent"]>[0]>;
37
38
 
38
39
  type ZentuiEditorFactory = EditorFactory & {
39
40
  [ZENTUI_EDITOR_FACTORY]?: true;
41
+ [ZENTUI_EDITOR_BASE_FACTORY]?: EditorFactory;
40
42
  };
41
43
 
42
44
  type ApplyUiResult = {
@@ -49,6 +51,15 @@ function isZentuiEditorFactory(factory: EditorFactory | undefined): boolean {
49
51
  return Boolean((factory as ZentuiEditorFactory | undefined)?.[ZENTUI_EDITOR_FACTORY]);
50
52
  }
51
53
 
54
+ function getZentuiEditorBaseFactory(factory: EditorFactory | undefined): EditorFactory | undefined {
55
+ return (factory as ZentuiEditorFactory | undefined)?.[ZENTUI_EDITOR_BASE_FACTORY];
56
+ }
57
+
58
+ function isTuiContext(ctx: ExtensionContext): boolean {
59
+ const mode = (ctx as ExtensionContext & { mode?: string }).mode;
60
+ return ctx.hasUI && (mode === undefined || mode === "tui");
61
+ }
62
+
52
63
  export default function (pi: ExtensionAPI) {
53
64
  const state: FooterState = createInitialState(emptyGitStatus());
54
65
 
@@ -61,6 +72,7 @@ export default function (pi: ExtensionAPI) {
61
72
  let footerInstalled = false;
62
73
  let editorInstalled = false;
63
74
  let editorInstallMode: EditorInstallMode = "none";
75
+ let installedEditorFactory: EditorFactory | undefined;
64
76
  let wrappedEditorFactory: EditorFactory | undefined;
65
77
  let prototypePatchesInstalled = false;
66
78
 
@@ -148,24 +160,38 @@ export default function (pi: ExtensionAPI) {
148
160
  getThinkingLevel,
149
161
  )) as ZentuiEditorFactory;
150
162
  factory[ZENTUI_EDITOR_FACTORY] = true;
163
+ factory[ZENTUI_EDITOR_BASE_FACTORY] = baseFactory;
151
164
  return factory;
152
165
  };
153
166
 
154
167
  const installEditor = (ctx: ExtensionContext): boolean => {
155
168
  const currentFactory = ctx.ui.getEditorComponent();
156
- if (currentFactory && isZentuiEditorFactory(currentFactory)) {
169
+ if (currentFactory && currentFactory === installedEditorFactory) {
157
170
  editorInstalled = true;
158
171
  return true;
159
172
  }
160
173
 
161
174
  installPrototypePatches();
162
- if (currentFactory) {
175
+ const currentZentuiBaseFactory = getZentuiEditorBaseFactory(currentFactory);
176
+ if (currentFactory && isZentuiEditorFactory(currentFactory)) {
177
+ wrappedEditorFactory = currentZentuiBaseFactory;
178
+ const nextFactory = currentZentuiBaseFactory
179
+ ? makeWrappedEditorFactory(ctx, currentZentuiBaseFactory)
180
+ : makeEditorFactory(ctx);
181
+ ctx.ui.setEditorComponent(nextFactory);
182
+ installedEditorFactory = nextFactory;
183
+ editorInstallMode = currentZentuiBaseFactory ? "wrapper" : "standalone";
184
+ } else if (currentFactory) {
163
185
  wrappedEditorFactory = currentFactory;
164
- ctx.ui.setEditorComponent(makeWrappedEditorFactory(ctx, currentFactory));
186
+ const nextFactory = makeWrappedEditorFactory(ctx, currentFactory);
187
+ ctx.ui.setEditorComponent(nextFactory);
188
+ installedEditorFactory = nextFactory;
165
189
  editorInstallMode = "wrapper";
166
190
  } else {
167
191
  wrappedEditorFactory = undefined;
168
- ctx.ui.setEditorComponent(makeEditorFactory(ctx));
192
+ const nextFactory = makeEditorFactory(ctx);
193
+ ctx.ui.setEditorComponent(nextFactory);
194
+ installedEditorFactory = nextFactory;
169
195
  editorInstallMode = "standalone";
170
196
  }
171
197
  editorInstalled = true;
@@ -181,6 +207,7 @@ export default function (pi: ExtensionAPI) {
181
207
  editorInstallMode === "wrapper" && wrappedEditorFactory ? wrappedEditorFactory : undefined,
182
208
  );
183
209
  wrappedEditorFactory = undefined;
210
+ installedEditorFactory = undefined;
184
211
  editorInstallMode = "none";
185
212
  editorInstalled = false;
186
213
  return true;
@@ -216,7 +243,7 @@ export default function (pi: ExtensionAPI) {
216
243
 
217
244
  const applyConfiguredUi = (ctx: ExtensionContext): ApplyUiResult => {
218
245
  const result: ApplyUiResult = { editorBlocked: false };
219
- if (!ctx.hasUI) return result;
246
+ if (!isTuiContext(ctx)) return result;
220
247
  activeTheme = ctx.ui.theme;
221
248
  if (currentConfig.features.editor) {
222
249
  const currentFactory = ctx.ui.getEditorComponent();
@@ -235,11 +262,12 @@ export default function (pi: ExtensionAPI) {
235
262
  };
236
263
 
237
264
  const installUi = (ctx: ExtensionContext) => {
238
- if (!ctx.hasUI) return;
265
+ if (!isTuiContext(ctx)) return;
239
266
  activeTheme = ctx.ui.theme;
240
267
  uninstallPrototypePatches();
241
268
  footerInstalled = false;
242
269
  editorInstalled = false;
270
+ installedEditorFactory = undefined;
243
271
  ensureConfigExists();
244
272
  currentConfig = loadConfig();
245
273
  syncFooterState(ctx);
@@ -250,9 +278,9 @@ export default function (pi: ExtensionAPI) {
250
278
 
251
279
  const scheduleEditorReconciliation = (ctx: ExtensionContext) => {
252
280
  setTimeout(() => {
253
- if (!ctx.hasUI || !currentConfig.features.editor) return;
281
+ if (!isTuiContext(ctx) || !currentConfig.features.editor) return;
254
282
  const currentFactory = ctx.ui.getEditorComponent();
255
- if (currentFactory && !isZentuiEditorFactory(currentFactory)) {
283
+ if (currentFactory && currentFactory !== installedEditorFactory) {
256
284
  applyConfiguredUi(ctx);
257
285
  refresh();
258
286
  }
@@ -264,18 +292,20 @@ export default function (pi: ExtensionAPI) {
264
292
  stopProjectRefresh();
265
293
  requestFooterRender = undefined;
266
294
  getActiveExtensionStatuses = () => new Map();
267
- if (ctx?.hasUI) {
295
+ if (ctx && isTuiContext(ctx)) {
268
296
  ctx.ui.setFooter(undefined);
269
297
  const currentFactory = ctx.ui.getEditorComponent();
270
298
  if (!currentFactory || isZentuiEditorFactory(currentFactory)) {
271
299
  ctx.ui.setEditorComponent(
272
- editorInstallMode === "wrapper" && wrappedEditorFactory
273
- ? wrappedEditorFactory
274
- : undefined,
300
+ getZentuiEditorBaseFactory(currentFactory) ??
301
+ (editorInstallMode === "wrapper" && wrappedEditorFactory
302
+ ? wrappedEditorFactory
303
+ : undefined),
275
304
  );
276
305
  }
277
306
  }
278
307
  wrappedEditorFactory = undefined;
308
+ installedEditorFactory = undefined;
279
309
  editorInstallMode = "none";
280
310
  footerInstalled = false;
281
311
  editorInstalled = false;
@@ -305,7 +305,8 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
305
305
  return;
306
306
  }
307
307
 
308
- if (!ctx.hasUI) return;
308
+ const mode = (ctx as typeof ctx & { mode?: string }).mode;
309
+ if (!ctx.hasUI || (mode !== undefined && mode !== "tui")) return;
309
310
 
310
311
  await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
311
312
  const settingsListTheme = deps.settingsListTheme ?? getSettingsListTheme();
@@ -132,6 +132,40 @@ function composeMetadataLine(left: string, right: string | undefined, width: num
132
132
  return `${leftText}${gap}${right}`;
133
133
  }
134
134
 
135
+ function plainRenderedText(line: string): string {
136
+ return line
137
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
138
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
139
+ .replace(/\[[/?][^\]]+\]/g, "");
140
+ }
141
+
142
+ function isHorizontalBorder(line: string): boolean {
143
+ const plain = plainRenderedText(line).trim();
144
+ return plain.length > 0 && /^─+$/.test(plain);
145
+ }
146
+
147
+ function isRenderedModelMetaLine(line: string, modelMeta: EditorMeta): boolean {
148
+ const plain = plainRenderedText(line);
149
+ return plain.includes(modelMeta.modelLabel) && plain.includes(modelMeta.providerLabel);
150
+ }
151
+
152
+ function removeRenderedModelMetaLines(lines: string[], modelMeta: EditorMeta): string[] {
153
+ const result: string[] = [];
154
+ for (let index = 0; index < lines.length; index++) {
155
+ const line = lines[index] ?? "";
156
+ if (isRenderedModelMetaLine(line, modelMeta)) continue;
157
+
158
+ const plain = plainRenderedText(line).trim();
159
+ const previousWasMeta = index > 0 && isRenderedModelMetaLine(lines[index - 1] ?? "", modelMeta);
160
+ const nextIsMeta =
161
+ index < lines.length - 1 && isRenderedModelMetaLine(lines[index + 1] ?? "", modelMeta);
162
+ if (!plain && (previousWasMeta || nextIsMeta)) continue;
163
+
164
+ result.push(line);
165
+ }
166
+ return result;
167
+ }
168
+
135
169
  function vimModeColor(mode: string): string {
136
170
  switch (mode.toLowerCase()) {
137
171
  case "insert":
@@ -195,10 +229,9 @@ function renderPolishedFrame({
195
229
  autocompleteCount > 0 && autocompleteCount < baseRendered.length
196
230
  ? baseRendered.slice(-autocompleteCount)
197
231
  : [];
198
-
199
232
  if (editorFrame.length < 2) return clampRenderedLines(baseRendered, width);
200
233
 
201
- const editorLines = editorFrame.slice(1, -1);
234
+ const editorLines = removeRenderedModelMetaLines(editorFrame.slice(1, -1), modelMeta);
202
235
  const model = renderStyleForSourceOrFallback(
203
236
  uiTheme,
204
237
  colorSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-zentui",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
4
4
  "description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
5
5
  "type": "module",
6
6
  "license": "MIT",