pi-ui-extend 0.1.13 → 0.1.17

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.
Files changed (111) hide show
  1. package/README.md +1 -1
  2. package/dist/app/app.d.ts +7 -0
  3. package/dist/app/app.js +102 -17
  4. package/dist/app/commands/command-controller.js +2 -0
  5. package/dist/app/commands/command-host.d.ts +5 -0
  6. package/dist/app/commands/command-model-actions.d.ts +2 -0
  7. package/dist/app/commands/command-model-actions.js +40 -4
  8. package/dist/app/commands/command-navigation-actions.d.ts +9 -0
  9. package/dist/app/commands/command-navigation-actions.js +62 -0
  10. package/dist/app/commands/command-registry.d.ts +2 -0
  11. package/dist/app/commands/command-registry.js +16 -0
  12. package/dist/app/constants.d.ts +0 -1
  13. package/dist/app/constants.js +0 -1
  14. package/dist/app/extensions/extension-ui-controller.d.ts +16 -5
  15. package/dist/app/extensions/extension-ui-controller.js +99 -61
  16. package/dist/app/icons.d.ts +1 -0
  17. package/dist/app/icons.js +2 -0
  18. package/dist/app/input/input-action-controller.d.ts +2 -0
  19. package/dist/app/input/input-action-controller.js +8 -1
  20. package/dist/app/logger.d.ts +25 -0
  21. package/dist/app/logger.js +90 -0
  22. package/dist/app/model/model-usage-status.js +30 -15
  23. package/dist/app/popup/menu-items-controller.d.ts +4 -0
  24. package/dist/app/popup/menu-items-controller.js +68 -6
  25. package/dist/app/popup/popup-action-controller.d.ts +2 -1
  26. package/dist/app/popup/popup-action-controller.js +7 -4
  27. package/dist/app/popup/popup-menu-controller.d.ts +36 -23
  28. package/dist/app/popup/popup-menu-controller.js +97 -326
  29. package/dist/app/rendering/conversation-entry-renderer.js +3 -3
  30. package/dist/app/rendering/conversation-viewport.d.ts +10 -2
  31. package/dist/app/rendering/conversation-viewport.js +157 -16
  32. package/dist/app/rendering/editor-panels.js +22 -9
  33. package/dist/app/rendering/popup-menu-renderer.d.ts +62 -0
  34. package/dist/app/rendering/popup-menu-renderer.js +405 -0
  35. package/dist/app/rendering/render-controller.js +30 -28
  36. package/dist/app/rendering/render-text.js +5 -2
  37. package/dist/app/rendering/status-line-renderer.d.ts +8 -1
  38. package/dist/app/rendering/status-line-renderer.js +217 -117
  39. package/dist/app/rendering/toast-controller.d.ts +12 -3
  40. package/dist/app/rendering/toast-controller.js +70 -12
  41. package/dist/app/runtime.d.ts +2 -1
  42. package/dist/app/runtime.js +20 -10
  43. package/dist/app/screen/mouse-controller.d.ts +2 -2
  44. package/dist/app/screen/mouse-controller.js +27 -48
  45. package/dist/app/screen/screen-styler.d.ts +1 -1
  46. package/dist/app/screen/screen-styler.js +9 -7
  47. package/dist/app/screen/scroll-controller.d.ts +12 -9
  48. package/dist/app/screen/scroll-controller.js +56 -45
  49. package/dist/app/screen/status-controller.js +2 -1
  50. package/dist/app/session/lazy-session-manager.d.ts +11 -0
  51. package/dist/app/session/lazy-session-manager.js +539 -0
  52. package/dist/app/session/pix-system-message.d.ts +16 -0
  53. package/dist/app/session/pix-system-message.js +64 -0
  54. package/dist/app/session/request-history.d.ts +4 -0
  55. package/dist/app/session/request-history.js +11 -0
  56. package/dist/app/session/session-event-controller.d.ts +11 -0
  57. package/dist/app/session/session-event-controller.js +58 -2
  58. package/dist/app/session/session-history.d.ts +18 -0
  59. package/dist/app/session/session-history.js +72 -3
  60. package/dist/app/session/session-lifecycle-controller.d.ts +6 -2
  61. package/dist/app/session/session-lifecycle-controller.js +7 -2
  62. package/dist/app/session/session-search.js +10 -0
  63. package/dist/app/session/tabs-controller.d.ts +17 -5
  64. package/dist/app/session/tabs-controller.js +308 -29
  65. package/dist/app/todo/todo-model.d.ts +4 -2
  66. package/dist/app/todo/todo-model.js +23 -13
  67. package/dist/app/types.d.ts +17 -6
  68. package/dist/app/workspace/workspace-actions-controller.d.ts +2 -0
  69. package/dist/app/workspace/workspace-actions-controller.js +12 -0
  70. package/dist/config.d.ts +6 -1
  71. package/dist/config.js +82 -25
  72. package/dist/default-pix-config.js +4 -0
  73. package/dist/fuzzy.d.ts +2 -0
  74. package/dist/fuzzy.js +27 -7
  75. package/dist/input-editor.d.ts +9 -0
  76. package/dist/input-editor.js +52 -0
  77. package/dist/schemas/pi-tools-suite-schema.d.ts +1 -0
  78. package/dist/schemas/pi-tools-suite-schema.js +1 -0
  79. package/dist/schemas/pix-schema.d.ts +3 -1
  80. package/dist/schemas/pix-schema.js +6 -4
  81. package/dist/terminal-width.d.ts +2 -0
  82. package/dist/terminal-width.js +64 -3
  83. package/dist/theme.js +6 -6
  84. package/dist/ui.d.ts +8 -0
  85. package/external/pi-tools-suite/README.md +3 -2
  86. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +52 -8
  87. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +3 -41
  88. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
  89. package/external/pi-tools-suite/src/antigravity-auth/index.ts +11 -18
  90. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +129 -61
  91. package/external/pi-tools-suite/src/antigravity-auth/status.ts +82 -3
  92. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +20 -7
  93. package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
  94. package/external/pi-tools-suite/src/config.ts +8 -0
  95. package/external/pi-tools-suite/src/dcp/index.ts +16 -1
  96. package/external/pi-tools-suite/src/dcp/state.ts +35 -0
  97. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +3 -0
  98. package/external/pi-tools-suite/src/todo/index.ts +123 -14
  99. package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
  100. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +26 -43
  101. package/external/pi-tools-suite/src/todo/todo.ts +12 -23
  102. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +34 -16
  103. package/external/pi-tools-suite/src/todo/tool/types.ts +7 -28
  104. package/external/pi-tools-suite/src/todo/view/format.ts +2 -3
  105. package/external/pi-tools-suite/src/tool-descriptions.ts +6 -4
  106. package/external/pi-tools-suite/src/usage/index.ts +5 -2
  107. package/external/pi-tools-suite/src/usage/lib/google.ts +53 -40
  108. package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
  109. package/package.json +1 -1
  110. package/schemas/pi-tools-suite.json +4 -0
  111. package/schemas/pix.json +11 -2
@@ -7,6 +7,7 @@ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentS
7
7
  import { loadPixConfig, resolveDefaultModelRef } from "../config.js";
8
8
  import { PI_FAVORITE_MODEL_REFS } from "./constants.js";
9
9
  import { isThinkingLevel, parseModelRef, parseScopedModelRef } from "./model/model-ref.js";
10
+ import { openLazySessionManager } from "./session/lazy-session-manager.js";
10
11
  const BUNDLED_QUESTION_EXTENSION_NAME = "question";
11
12
  const PI_TOOLS_SUITE_EXTENSION_NAME = "pi-tools-suite";
12
13
  const BUNDLED_EXTENSIONS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..", "extensions");
@@ -140,6 +141,11 @@ export function resolvePixRuntimeModelRef(options, sessionManager, config = load
140
141
  return resolveSessionModelRefFromTail(sessionManager.getBranch());
141
142
  return resolveDefaultModelRef(config);
142
143
  }
144
+ export function resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config) {
145
+ const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
146
+ const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
147
+ return parsedModel?.thinkingLevel;
148
+ }
143
149
  export function resolveSessionModelRefFromTail(entries) {
144
150
  let modelRef;
145
151
  let thinkingLevel;
@@ -166,8 +172,10 @@ export function resolveSessionModelRefFromTail(entries) {
166
172
  export async function createPixRuntime(options, runtimeOptions = {}) {
167
173
  const agentDir = getAgentDir();
168
174
  const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
169
- const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager);
175
+ const config = loadPixConfig(cwd);
176
+ const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
170
177
  const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
178
+ const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
171
179
  await ensureBundledSkillsInstalled();
172
180
  await ensurePiToolsSuiteExtensionInstalled({ agentDir });
173
181
  const bundledExtensionPaths = getBundledExtensionPaths();
@@ -175,6 +183,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
175
183
  cwd,
176
184
  agentDir,
177
185
  resourceLoaderOptions: {
186
+ ...(config.ignoreContextFiles ? { noContextFiles: true } : {}),
178
187
  ...(runtimeOptions.eventBus === undefined ? {} : { eventBus: runtimeOptions.eventBus }),
179
188
  ...(bundledExtensionPaths.length === 0 ? {} : {
180
189
  additionalExtensionPaths: bundledExtensionPaths,
@@ -203,15 +212,16 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
203
212
  },
204
213
  ];
205
214
  });
215
+ const created = await createAgentSessionFromServices({
216
+ services,
217
+ sessionManager,
218
+ ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
219
+ ...(model === undefined ? {} : { model }),
220
+ ...(initialThinkingLevel === undefined ? {} : { thinkingLevel: initialThinkingLevel }),
221
+ ...(scopedModels.length === 0 ? {} : { scopedModels }),
222
+ });
206
223
  return {
207
- ...(await createAgentSessionFromServices({
208
- services,
209
- sessionManager,
210
- ...(sessionStartEvent === undefined ? {} : { sessionStartEvent }),
211
- ...(model === undefined ? {} : { model }),
212
- ...(parsedModel?.thinkingLevel === undefined ? {} : { thinkingLevel: parsedModel.thinkingLevel }),
213
- ...(scopedModels.length === 0 ? {} : { scopedModels }),
214
- })),
224
+ ...created,
215
225
  services,
216
226
  diagnostics: services.diagnostics,
217
227
  };
@@ -222,7 +232,7 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
222
232
  sessionManager: options.noSession
223
233
  ? SessionManager.inMemory(options.cwd)
224
234
  : options.sessionPath
225
- ? SessionManager.open(options.sessionPath, undefined, options.cwd)
235
+ ? openLazySessionManager(options.sessionPath, { cwdOverride: options.cwd })
226
236
  : SessionManager.create(options.cwd),
227
237
  });
228
238
  }
@@ -56,6 +56,7 @@ export type AppMouseControllerHost = {
56
56
  }): void;
57
57
  dismissToast(toastId: number): void;
58
58
  refreshModelUsageStatus(): void | Promise<void>;
59
+ refreshUserMessageJumpMenuItems?(): Promise<void>;
59
60
  queueInputFromStatus?(): void | Promise<void>;
60
61
  toggleAllThinkingExpanded?(): void;
61
62
  toggleSuperCompactTools?(): void;
@@ -111,7 +112,6 @@ export declare class AppMouseController {
111
112
  statusVoiceLanguageTarget: StatusVoiceLanguageTarget | undefined;
112
113
  readonly tabLineTargets: TabLineMouseTarget[];
113
114
  mouseSelection: MouseSelection | undefined;
114
- private scrollBarDragActive;
115
115
  private inputScrollBarDragActive;
116
116
  private autoScrollTimer;
117
117
  private autoScrollDirection;
@@ -142,7 +142,6 @@ export declare class AppMouseController {
142
142
  private statusTargetAt;
143
143
  private handleImageClick;
144
144
  private handleFileLinkClick;
145
- private handleConversationScrollBar;
146
145
  private handleInputScrollBar;
147
146
  private finishInputScrollBarDrag;
148
147
  private handleInputWheel;
@@ -155,6 +154,7 @@ export declare class AppMouseController {
155
154
  private handleStatusContextClick;
156
155
  private handleStatusModelUsageClick;
157
156
  private handleStatusUserJumpClick;
157
+ private openStatusUserJumpMenu;
158
158
  private handleStatusDraftQueueClick;
159
159
  private handleStatusThinkingExpandClick;
160
160
  private handleStatusCompactToolsClick;
@@ -3,7 +3,7 @@ import { stringifyUnknown } from "../rendering/message-content.js";
3
3
  import { horizontalPaddingLayout } from "../rendering/render-text.js";
4
4
  import { orderedSelection, samePoint } from "./screen-selection.js";
5
5
  import { openImageContent as openSystemImageContent } from "./image-opener.js";
6
- import { stringDisplayWidth } from "../../terminal-width.js";
6
+ import { sliceByDisplayColumns, stringDisplayWidth } from "../../terminal-width.js";
7
7
  import { formatDcpStatsToast } from "../rendering/dcp-stats.js";
8
8
  import { detectFileLinks } from "./file-links.js";
9
9
  import { openFileLink as openDetectedFileLink } from "./file-link-opener.js";
@@ -34,7 +34,6 @@ export class AppMouseController {
34
34
  statusVoiceLanguageTarget;
35
35
  tabLineTargets = [];
36
36
  mouseSelection;
37
- scrollBarDragActive = false;
38
37
  inputScrollBarDragActive = false;
39
38
  autoScrollTimer;
40
39
  autoScrollDirection;
@@ -57,8 +56,6 @@ export class AppMouseController {
57
56
  handleMouse(event) {
58
57
  if (this.handleInputScrollBar(event))
59
58
  return;
60
- if (this.handleConversationScrollBar(event))
61
- return;
62
59
  this.showClickFlashOnPress(event);
63
60
  if (this.handleMouseSelection(event))
64
61
  return;
@@ -339,39 +336,6 @@ export class AppMouseController {
339
336
  this.host.showToast("Could not open file link. Install the Zed CLI or set ZED_CLI.", "warning");
340
337
  return true;
341
338
  }
342
- handleConversationScrollBar(event) {
343
- if (!this.scrollBarDragActive && this.tabLineTargetAt(event))
344
- return false;
345
- const columns = this.host.terminalColumns();
346
- const terminalRows = this.host.terminalRows();
347
- const tabPanelRows = this.host.tabPanelRows(terminalRows);
348
- const rows = editorLayoutRows(terminalRows, tabPanelRows);
349
- const topOffset = editorLayoutTopOffset(tabPanelRows);
350
- if (columns <= 0 || rows <= 0)
351
- return false;
352
- const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
353
- const localY = event.y - topOffset;
354
- const insideBody = localY >= 1 && localY <= bodyHeight;
355
- const onScrollBar = insideBody && event.x === columns && !!this.scrollController.scrollBarMetrics(columns, bodyHeight);
356
- const baseButton = event.button & 3;
357
- const draggingLeftButton = (event.button & 32) !== 0 && baseButton === 0;
358
- if (event.released) {
359
- if (!this.scrollBarDragActive)
360
- return false;
361
- this.scrollBarDragActive = false;
362
- return true;
363
- }
364
- if (event.button === 0 && onScrollBar) {
365
- this.scrollBarDragActive = true;
366
- this.scrollController.scrollToScrollbarPosition(localY - 1);
367
- return true;
368
- }
369
- if (!draggingLeftButton || !this.scrollBarDragActive)
370
- return false;
371
- const bodyRow = Math.max(0, Math.min(Math.max(0, bodyHeight - 1), localY - 1));
372
- this.scrollController.scrollToScrollbarPosition(bodyRow);
373
- return true;
374
- }
375
339
  handleInputScrollBar(event) {
376
340
  if (!this.inputScrollBarDragActive && this.tabLineTargetAt(event))
377
341
  return false;
@@ -509,10 +473,24 @@ export class AppMouseController {
509
473
  return false;
510
474
  if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
511
475
  return false;
512
- this.popupMenus.openDirectPopupMenu("user-message-jump", { preserveStatus: true });
513
- this.host.render();
476
+ void this.openStatusUserJumpMenu();
514
477
  return true;
515
478
  }
479
+ async openStatusUserJumpMenu() {
480
+ try {
481
+ const refreshPromise = this.host.refreshUserMessageJumpMenuItems?.();
482
+ this.popupMenus.openDirectPopupMenu("user-message-jump", { preserveStatus: true });
483
+ this.host.render();
484
+ if (this.host.refreshUserMessageJumpMenuItems) {
485
+ await refreshPromise;
486
+ this.host.render();
487
+ }
488
+ }
489
+ catch (error) {
490
+ this.host.showToast(`Could not load jump messages: ${error instanceof Error ? error.message : stringifyUnknown(error)}`, "error");
491
+ this.host.render();
492
+ }
493
+ }
516
494
  handleStatusDraftQueueClick(event) {
517
495
  const target = this.statusDraftQueueTarget;
518
496
  if (!target)
@@ -829,9 +807,7 @@ export class AppMouseController {
829
807
  const line = range.start.line + index;
830
808
  const startColumn = line === range.start.line ? range.start.x : 1;
831
809
  const endColumn = line === range.end.line ? range.end.x : text.length + 1;
832
- const startIndex = Math.max(0, Math.min(text.length, startColumn - 1));
833
- const endIndex = Math.max(startIndex, Math.min(text.length, endColumn - 1));
834
- lines.push(text.slice(startIndex, endIndex).trimEnd());
810
+ lines.push(sliceByDisplayColumns(text, startColumn, endColumn).trimEnd());
835
811
  }
836
812
  return lines.join("\n").replace(/\s+$/u, "");
837
813
  }
@@ -839,10 +815,10 @@ export class AppMouseController {
839
815
  const area = this.conversationArea();
840
816
  if (!area || area.bodyHeight <= 0)
841
817
  return undefined;
842
- if (!clampToViewport && (event.y < area.topRow || event.y > area.bottomRow || event.x === this.host.terminalColumns()))
818
+ if (!clampToViewport && (event.y < area.topRow || event.y > area.bottomRow))
843
819
  return undefined;
844
820
  const screenY = Math.max(area.topRow, Math.min(area.bottomRow, event.y));
845
- const screenX = Math.max(1, Math.min(area.viewportColumns + 1, event.x));
821
+ const screenX = viewportSelectionColumn(event.x, area.viewportColumns);
846
822
  return {
847
823
  conversation: {
848
824
  line: area.metrics.start + (screenY - area.topRow),
@@ -945,7 +921,7 @@ export class AppMouseController {
945
921
  if (!area)
946
922
  return;
947
923
  const screenY = this.autoScrollDirection < 0 ? area.topRow : area.bottomRow;
948
- const screenX = Math.max(1, Math.min(area.viewportColumns + 1, this.autoScrollCursorX));
924
+ const screenX = viewportSelectionColumn(this.autoScrollCursorX, area.viewportColumns);
949
925
  selection.current = { x: screenX, y: screenY };
950
926
  selection.conversationCurrent = {
951
927
  line: area.metrics.start + (screenY - area.topRow),
@@ -971,13 +947,16 @@ export function screenSelectionLineText(row, text, startColumn, endColumn, input
971
947
  copyStartColumn = Math.max(copyStartColumn, inputFrame.contentStartColumn);
972
948
  copyEndColumn = Math.min(copyEndColumn, inputFrame.contentEndColumn);
973
949
  }
974
- const startIndex = Math.max(0, Math.min(text.length, copyStartColumn - 1));
975
- const endIndex = Math.max(startIndex, Math.min(text.length, copyEndColumn - 1));
976
- return text.slice(startIndex, endIndex);
950
+ return sliceByDisplayColumns(text, copyStartColumn, copyEndColumn);
977
951
  }
978
952
  function sameConversationPoint(left, right) {
979
953
  return !!left && left.line === right.line && left.x === right.x;
980
954
  }
955
+ function viewportSelectionColumn(mouseX, viewportColumns) {
956
+ if (mouseX >= viewportColumns)
957
+ return viewportColumns + 1;
958
+ return Math.max(1, Math.min(viewportColumns + 1, mouseX));
959
+ }
981
960
  function toastTargetContainsEvent(target, event) {
982
961
  if (target.startColumn === undefined || target.endColumn === undefined)
983
962
  return true;
@@ -29,7 +29,7 @@ export declare class ScreenStyler {
29
29
  end: number;
30
30
  }[] | undefined, width: number, tagColor: string, suggestionColor: string, frameColor?: string): string;
31
31
  private styleAnsiLine;
32
- selectionRangeForRow(row: number, width: number): {
32
+ selectionRangeForRow(row: number, width: number, text?: string): {
33
33
  startIndex: number;
34
34
  endIndex: number;
35
35
  } | undefined;
@@ -1,6 +1,7 @@
1
1
  import { ANSI_RESET, colorize } from "../../theme.js";
2
2
  import { renderMarkdownLine } from "../../markdown-format.js";
3
3
  import { syntaxHighlightSegmentsForLine } from "../../syntax-highlight.js";
4
+ import { displayIndexForColumn } from "../../terminal-width.js";
4
5
  import { padOrTrimPlain } from "../rendering/render-text.js";
5
6
  import { orderedSelection } from "./screen-selection.js";
6
7
  export class ScreenStyler {
@@ -19,7 +20,7 @@ export class ScreenStyler {
19
20
  ? renderMarkdownDisplayLine(line.text, width, line.syntaxHighlight.start)
20
21
  : undefined;
21
22
  const text = markdownLine?.text ?? line?.text ?? "";
22
- if (line?.syntaxHighlight && !this.selectionRangeForRow(row, width)) {
23
+ if (line?.syntaxHighlight && !this.selectionRangeForRow(row, width, text)) {
23
24
  const syntaxHighlight = markdownLine ? { ...line.syntaxHighlight, start: Math.min(line.syntaxHighlight.start, markdownLine.text.length) } : line.syntaxHighlight;
24
25
  const segments = [
25
26
  ...syntaxHighlightSegmentsForLine(text, syntaxHighlight, colors),
@@ -35,7 +36,7 @@ export class ScreenStyler {
35
36
  return this.styleLine(row, text, width, options);
36
37
  }
37
38
  styleLineSegments(row, text, width, baseOptions, segments) {
38
- if (this.selectionRangeForRow(row, width))
39
+ if (this.selectionRangeForRow(row, width, text))
39
40
  return this.styleLine(row, text, width, baseOptions);
40
41
  const plain = padOrTrimPlain(text, width);
41
42
  const chunks = [];
@@ -56,7 +57,7 @@ export class ScreenStyler {
56
57
  }
57
58
  styleLine(row, text, width, options) {
58
59
  const plain = padOrTrimPlain(text, width);
59
- const range = this.selectionRangeForRow(row, width);
60
+ const range = this.selectionRangeForRow(row, width, plain);
60
61
  if (!range)
61
62
  return colorize(plain, options);
62
63
  const before = plain.slice(0, range.startIndex);
@@ -75,7 +76,7 @@ export class ScreenStyler {
75
76
  styleInputLine(row, text, tagSpans, suggestionSpans, width, tagColor, suggestionColor, frameColor) {
76
77
  const colors = this.host.theme.colors;
77
78
  const baseOptions = { foreground: colors.inputForeground };
78
- if (this.selectionRangeForRow(row, width))
79
+ if (this.selectionRangeForRow(row, width, text))
79
80
  return this.styleLine(row, text, width, baseOptions);
80
81
  const plain = padOrTrimPlain(text, width);
81
82
  const frameSpans = inputFrameSpans(plain, width, frameColor);
@@ -109,7 +110,7 @@ export class ScreenStyler {
109
110
  return text;
110
111
  return `${prefix}${text.replaceAll(ANSI_RESET, `${ANSI_RESET}${prefix}`)}${ANSI_RESET}`;
111
112
  }
112
- selectionRangeForRow(row, width) {
113
+ selectionRangeForRow(row, width, text) {
113
114
  if (!this.host.mouseSelection)
114
115
  return undefined;
115
116
  const anchor = this.host.mouseSelection.screenAnchor ?? this.host.mouseSelection.anchor;
@@ -119,8 +120,9 @@ export class ScreenStyler {
119
120
  return undefined;
120
121
  const startColumn = row === start.y ? start.x : 1;
121
122
  const endColumn = row === end.y ? end.x : width + 1;
122
- const startIndex = Math.max(0, Math.min(width, startColumn - 1));
123
- const endIndex = Math.max(startIndex, Math.min(width, endColumn - 1));
123
+ const plain = text ?? " ".repeat(Math.max(0, width));
124
+ const startIndex = Math.max(0, Math.min(plain.length, displayIndexForColumn(plain, startColumn)));
125
+ const endIndex = Math.max(startIndex, Math.min(plain.length, displayIndexForColumn(plain, endColumn)));
124
126
  return endIndex > startIndex ? { startIndex, endIndex } : undefined;
125
127
  }
126
128
  baseLineForeground(variant) {
@@ -1,6 +1,6 @@
1
1
  import type { ConversationViewport } from "../rendering/conversation-viewport.js";
2
2
  import type { EditorLayoutRenderer } from "../rendering/editor-layout-renderer.js";
3
- import type { RenderedLine } from "../types.js";
3
+ import type { Entry, RenderedLine } from "../types.js";
4
4
  export type AppScrollMetrics = {
5
5
  bodyHeight: number;
6
6
  viewportColumns: number;
@@ -8,10 +8,6 @@ export type AppScrollMetrics = {
8
8
  maxScroll: number;
9
9
  start: number;
10
10
  };
11
- export type AppScrollBarMetrics = {
12
- thumbStartRow: number;
13
- thumbEndRow: number;
14
- };
15
11
  export type ConversationTextScrollTarget = {
16
12
  entryId?: string;
17
13
  needles: readonly string[];
@@ -22,30 +18,37 @@ export type AppScrollControllerHost = {
22
18
  terminalColumns(): number;
23
19
  terminalRows(): number;
24
20
  tabPanelRows(terminalRows: number): number;
21
+ hasOlderSessionHistory?(): boolean;
22
+ isLoadingOlderSessionHistory?(): boolean;
23
+ loadOlderSessionHistory?(options?: {
24
+ render?: boolean;
25
+ onPrependedEntries?: (entries: readonly Entry[]) => void;
26
+ }): Promise<boolean>;
25
27
  render(): void;
26
28
  };
27
29
  export declare class AppScrollController {
28
30
  private readonly host;
29
31
  private scrollFromBottom;
30
32
  private detachedScrollStart;
33
+ private readonly olderHistoryThresholdLines;
31
34
  constructor(host: AppScrollControllerHost);
32
35
  reset(): void;
36
+ scrollToBottom(): boolean;
33
37
  conversationView(columns: number, bodyHeight: number): {
34
38
  lines: RenderedLine[];
35
39
  metrics: AppScrollMetrics;
36
40
  };
37
41
  visibleConversationLines(columns: number, bodyHeight: number): RenderedLine[];
38
42
  scrollMetrics(columns: number, bodyHeight: number): AppScrollMetrics;
39
- scrollBarForMetrics(metrics: AppScrollMetrics): AppScrollBarMetrics | undefined;
40
- scrollBarMetrics(columns: number, bodyHeight: number): AppScrollBarMetrics | undefined;
41
43
  scrollByPage(direction: -1 | 1): void;
42
44
  scrollByLines(delta: number, options?: {
43
45
  render?: boolean;
44
46
  }): boolean;
45
- scrollToScrollbarPosition(bodyRow: number): boolean;
47
+ private shouldLoadOlderHistory;
48
+ private loadOlderHistoryAnchored;
49
+ adjustForHistoryWindowPrune(edge: "top" | "bottom", lineCount: number): void;
46
50
  scrollToConversationEntry(entryId: string): boolean;
47
51
  scrollToConversationText(target: ConversationTextScrollTarget): boolean;
48
- private scrollToStart;
49
52
  private setScrollStart;
50
53
  private viewportColumns;
51
54
  }
@@ -3,6 +3,7 @@ export class AppScrollController {
3
3
  host;
4
4
  scrollFromBottom = 0;
5
5
  detachedScrollStart;
6
+ olderHistoryThresholdLines = 8;
6
7
  constructor(host) {
7
8
  this.host = host;
8
9
  }
@@ -10,6 +11,12 @@ export class AppScrollController {
10
11
  this.scrollFromBottom = 0;
11
12
  this.detachedScrollStart = undefined;
12
13
  }
14
+ scrollToBottom() {
15
+ const changed = this.scrollFromBottom !== 0 || this.detachedScrollStart !== undefined;
16
+ this.scrollFromBottom = 0;
17
+ this.detachedScrollStart = undefined;
18
+ return changed;
19
+ }
13
20
  conversationView(columns, bodyHeight) {
14
21
  const metrics = this.scrollMetrics(columns, bodyHeight);
15
22
  return {
@@ -37,20 +44,6 @@ export class AppScrollController {
37
44
  }
38
45
  return { bodyHeight, viewportColumns, conversationLineCount, maxScroll, start };
39
46
  }
40
- scrollBarForMetrics(metrics) {
41
- if (metrics.bodyHeight <= 0 || metrics.maxScroll <= 0 || metrics.conversationLineCount <= metrics.bodyHeight)
42
- return undefined;
43
- const thumbSize = Math.max(1, Math.min(metrics.bodyHeight, Math.round((metrics.bodyHeight * metrics.bodyHeight) / metrics.conversationLineCount)));
44
- const travel = Math.max(0, metrics.bodyHeight - thumbSize);
45
- const thumbOffset = travel === 0 ? 0 : Math.round((metrics.start / metrics.maxScroll) * travel);
46
- return {
47
- thumbStartRow: thumbOffset + 1,
48
- thumbEndRow: thumbOffset + thumbSize,
49
- };
50
- }
51
- scrollBarMetrics(columns, bodyHeight) {
52
- return this.scrollBarForMetrics(this.scrollMetrics(columns, bodyHeight));
53
- }
54
47
  scrollByPage(direction) {
55
48
  const rows = this.host.terminalRows();
56
49
  this.scrollByLines(direction * Math.max(1, editorLayoutRows(rows, this.host.tabPanelRows(rows)) - 4));
@@ -62,37 +55,64 @@ export class AppScrollController {
62
55
  const rows = editorLayoutRows(terminalRows, this.host.tabPanelRows(terminalRows));
63
56
  const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
64
57
  const metrics = this.scrollMetrics(columns, bodyHeight);
58
+ const shouldLoadOlderHistory = this.shouldLoadOlderHistory(delta, metrics);
65
59
  const { conversationLineCount, maxScroll } = metrics;
66
60
  const nextScrollFromBottom = Math.max(0, Math.min(maxScroll, this.scrollFromBottom + -delta));
61
+ let changed = false;
67
62
  if (nextScrollFromBottom === this.scrollFromBottom) {
68
63
  if (nextScrollFromBottom === 0 && this.detachedScrollStart !== undefined && delta > 0) {
69
64
  this.detachedScrollStart = undefined;
70
- if (shouldRender)
71
- this.host.render();
72
- return true;
65
+ changed = true;
66
+ }
67
+ else if (!shouldLoadOlderHistory) {
68
+ return false;
73
69
  }
74
- return false;
75
70
  }
76
- this.scrollFromBottom = nextScrollFromBottom;
77
- this.detachedScrollStart = nextScrollFromBottom === 0
78
- ? undefined
79
- : Math.max(0, conversationLineCount - bodyHeight - nextScrollFromBottom);
71
+ else {
72
+ this.scrollFromBottom = nextScrollFromBottom;
73
+ this.detachedScrollStart = nextScrollFromBottom === 0
74
+ ? undefined
75
+ : Math.max(0, conversationLineCount - bodyHeight - nextScrollFromBottom);
76
+ changed = true;
77
+ }
78
+ if (shouldLoadOlderHistory)
79
+ this.loadOlderHistoryAnchored(metrics, { render: shouldRender });
80
80
  if (shouldRender)
81
81
  this.host.render();
82
- return true;
82
+ return changed || shouldLoadOlderHistory;
83
83
  }
84
- scrollToScrollbarPosition(bodyRow) {
85
- const columns = this.host.terminalColumns();
86
- const terminalRows = this.host.terminalRows();
87
- const rows = editorLayoutRows(terminalRows, this.host.tabPanelRows(terminalRows));
88
- const { bodyHeight } = this.host.editorLayoutRenderer().computeLayout(columns, rows);
89
- const metrics = this.scrollMetrics(columns, bodyHeight);
90
- if (!this.scrollBarForMetrics(metrics))
84
+ shouldLoadOlderHistory(delta, metrics) {
85
+ if (delta >= 0)
86
+ return false;
87
+ if (metrics.start > this.olderHistoryThresholdLines)
88
+ return false;
89
+ if (this.host.hasOlderSessionHistory?.() !== true)
91
90
  return false;
92
- const clampedRow = Math.max(0, Math.min(Math.max(0, bodyHeight - 1), bodyRow));
93
- const ratio = bodyHeight <= 1 ? 0 : clampedRow / (bodyHeight - 1);
94
- const start = Math.round(metrics.maxScroll * ratio);
95
- return this.scrollToStart(start, metrics);
91
+ if (this.host.isLoadingOlderSessionHistory?.() === true)
92
+ return false;
93
+ return true;
94
+ }
95
+ loadOlderHistoryAnchored(metrics, options) {
96
+ void this.host.loadOlderSessionHistory?.({
97
+ render: false,
98
+ onPrependedEntries: (entries) => {
99
+ const prependedLineCount = this.host.conversationViewport().measuredLineCountForEntries(metrics.viewportColumns, entries.map((entry) => entry.id));
100
+ if (prependedLineCount > 0 && this.detachedScrollStart !== undefined)
101
+ this.detachedScrollStart += prependedLineCount;
102
+ },
103
+ }).then((loaded) => {
104
+ if (loaded && options.render)
105
+ this.host.render();
106
+ });
107
+ }
108
+ adjustForHistoryWindowPrune(edge, lineCount) {
109
+ if (lineCount <= 0)
110
+ return;
111
+ if (edge !== "top")
112
+ return;
113
+ if (this.detachedScrollStart === undefined)
114
+ return;
115
+ this.detachedScrollStart = Math.max(0, this.detachedScrollStart - lineCount);
96
116
  }
97
117
  scrollToConversationEntry(entryId) {
98
118
  const columns = this.host.terminalColumns();
@@ -143,12 +163,6 @@ export class AppScrollController {
143
163
  }
144
164
  return false;
145
165
  }
146
- scrollToStart(start, metrics) {
147
- if (!this.setScrollStart(start, metrics))
148
- return false;
149
- this.host.render();
150
- return true;
151
- }
152
166
  setScrollStart(start, metrics) {
153
167
  const nextStart = Math.max(0, Math.min(metrics.maxScroll, start));
154
168
  const nextScrollFromBottom = Math.max(0, metrics.conversationLineCount - metrics.bodyHeight - nextStart);
@@ -158,12 +172,9 @@ export class AppScrollController {
158
172
  this.detachedScrollStart = nextDetachedScrollStart;
159
173
  return changed;
160
174
  }
161
- viewportColumns(columns, bodyHeight) {
175
+ viewportColumns(columns, _bodyHeight) {
162
176
  const safeColumns = Math.max(1, columns);
163
- if (safeColumns <= 1 || bodyHeight <= 0)
164
- return safeColumns;
165
- const lineCountWithoutScrollbar = this.host.conversationViewport().lineCount(safeColumns);
166
- return lineCountWithoutScrollbar > bodyHeight ? safeColumns - 1 : safeColumns;
177
+ return safeColumns;
167
178
  }
168
179
  }
169
180
  function editorLayoutRows(terminalRows, tabPanelRows) {
@@ -1,6 +1,7 @@
1
1
  import { basename } from "node:path";
2
2
  import { GIT_BRANCH_CACHE_MS } from "../constants.js";
3
3
  import { runProcess } from "../process.js";
4
+ import { APP_ICONS } from "../icons.js";
4
5
  const STATUS_DOT_BLINK_KEY = "status-dot";
5
6
  export class AppStatusController {
6
7
  host;
@@ -47,7 +48,7 @@ export class AppStatusController {
47
48
  });
48
49
  }
49
50
  formatSessionStatus(session) {
50
- return `${this.statusModelLabel(session)} ${this.statusThinkingLabel(session)} ${this.formatContextUsagePercent(session)}`;
51
+ return `${this.statusModelLabel(session)} ${APP_ICONS.lightbulb} ${this.statusThinkingLabel(session)} ${this.formatContextUsagePercent(session)}`;
51
52
  }
52
53
  statusModelLabel(session) {
53
54
  return session.model ? `${session.model.provider}/${session.model.id}` : "no model";
@@ -0,0 +1,11 @@
1
+ import { SessionManager, type SessionEntry } from "@earendil-works/pi-coding-agent";
2
+ export type LazySessionManagerOptions = {
3
+ cwdOverride?: string;
4
+ sessionDir?: string;
5
+ tailEntryCount?: number;
6
+ };
7
+ export type LazySessionHistoryReader = {
8
+ hasOlder(): boolean;
9
+ readOlder(limit: number): Promise<SessionEntry[]>;
10
+ };
11
+ export declare function openLazySessionManager(sessionPath: string, options?: LazySessionManagerOptions): SessionManager;