mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-28 09:44:37 +08:00
fix(coding-agent): keep open extension dialog visible when the editor component is replaced (#11466)
`ctx.ui.setEditorComponent()` always cleared the editor slot and put the new editor there with focus. If an extension dialog (`confirm`/`select`/`input`/`editor`, or an inline `custom()` component) was open at that moment, the editor replaced it on screen. The dialog was still on the dialog stack and its promise still pending, but nobody could see or answer it. It hung until its timeout, or forever without one. Now `setCustomEditorComponent` leaves the editor slot and focus alone while a dialog is open. The new editor is still created and wired up, and `showTopExtensionDialog` shows it when the last dialog closes. Without an open dialog nothing changes. Testing: - Added a regression test to `test/interactive-mode-extension-dialogs.test.ts`. It opens a confirm, replaces the editor, then checks that the confirm stays visible and answerable and that the new editor is focused afterwards. It fails without the fix and passes with it. - `npm run check` passes. - Ran the reproduction from the issue in tmux (`pi -e ./a.ts -e ./b.ts`, where a.ts opens a non-blocking `confirm` in `session_start` and b.ts calls `setEditorComponent`). - Before: only the editor shows, and the confirm times out. - After: the confirm shows, and Enter gives `answer: true`. Fixes #11431 Requested by @badlogic Co-authored-by: triagent <triagent@localhost>
This commit is contained in:
1 parent
fecc19e4e9
commit
7853186d14
2 files changed
+31
-4
No files matched your search
@@ -2839,8 +2839,12 @@ export class InteractiveMode {
|
||||
// Save text from current editor before switching
|
||||
const currentText = this.editor.getText();
|
||||
|
||||
this.disposeActiveSelector();
|
||||
this.editorContainer.clear();
|
||||
// An open extension dialog keeps the editor slot; the new editor is shown when the last dialog closes.
|
||||
const dialogOpen = this.extensionDialogs.length > 0;
|
||||
if (!dialogOpen) {
|
||||
this.disposeActiveSelector();
|
||||
this.editorContainer.clear();
|
||||
}
|
||||
|
||||
if (factory) {
|
||||
// Create the custom editor with tui, theme, and keybindings
|
||||
@@ -2898,7 +2902,7 @@ export class InteractiveMode {
|
||||
this.editor = this.defaultEditor;
|
||||
}
|
||||
|
||||
this.editorContainer.addChild(this.editor as Component);
|
||||
if (!dialogOpen) this.editorContainer.addChild(this.editor as Component);
|
||||
if (this.activeStatusIndicator) {
|
||||
this.statusContainer.clear();
|
||||
this.activeWorkingIndicatorEmbedded = this.setEditorWorkingStatusIndicator(this.activeStatusIndicator);
|
||||
@@ -2906,7 +2910,7 @@ export class InteractiveMode {
|
||||
this.statusContainer.addChild(this.activeStatusIndicator);
|
||||
}
|
||||
}
|
||||
this.ui.setFocus(this.editor as Component);
|
||||
if (!dialogOpen) this.ui.setFocus(this.editor as Component);
|
||||
this.ui.requestRender();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ type DialogMethods = {
|
||||
showExtensionCustom<T>(
|
||||
factory: (tui: unknown, theme: unknown, keybindings: unknown, done: (result: T) => void) => Component,
|
||||
): Promise<T>;
|
||||
setCustomEditorComponent(
|
||||
factory: ((tui: unknown, theme: unknown, keybindings: unknown) => Component) | undefined,
|
||||
): void;
|
||||
};
|
||||
|
||||
function createContext(): DialogContext & DialogMethods {
|
||||
@@ -149,4 +152,24 @@ describe("InteractiveMode extension dialogs", () => {
|
||||
await expect(custom).resolves.toBe("kept");
|
||||
expect(context.focused).toBe(context.editor);
|
||||
});
|
||||
|
||||
// Regression for #11431: replacing the editor while a dialog was open hid the dialog and left it unanswerable.
|
||||
it("keeps an open dialog visible when the editor component is replaced", async () => {
|
||||
const context = createContext();
|
||||
(context as unknown as { defaultEditor: unknown }).defaultEditor = context.editor;
|
||||
const confirmed = context.showExtensionConfirm("Title", "Continue?");
|
||||
const customEditor: Component & { getText(): string; setText(text: string): void } = {
|
||||
render: () => ["custom editor"],
|
||||
invalidate: () => {},
|
||||
getText: () => "",
|
||||
setText: () => {},
|
||||
};
|
||||
context.setCustomEditorComponent(() => customEditor);
|
||||
|
||||
expect(visible(context)).toContain("Title");
|
||||
context.focused?.handleInput?.("\r");
|
||||
await expect(confirmed).resolves.toBe(true);
|
||||
expect(context.focused).toBe(customEditor);
|
||||
expect(context.editorContainer.children).toEqual([customEditor]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user