Compare commits

...
Author SHA1 Message Date
netcatty-bot 739b0731ee fix: address Codex review on PR #3204 2026-08-29 07:02:31 +00:00
3 changed files with 53 additions and 1 deletions

No files matched your search

@@ -584,6 +584,18 @@ export function resolveAutocompletePopupAnchorInViewport(
);
}
/** Whether the clamp bounds moved since the last reposition (layout-driven change). */
export function didAutocompleteClampViewportChange(
prev: PopupClampViewport | null,
next: PopupClampViewport,
): boolean {
if (!prev) return false;
return prev.left !== next.left
|| prev.top !== next.top
|| prev.width !== next.width
|| prev.height !== next.height;
}
/**
* Next stored popup viewport when the command-start / cursor anchor moves.
* Returns `prev` when nothing changed so callers can skip a React update.
@@ -31,11 +31,14 @@ import { isWindowsShellLineInput, shouldWriteAutocompleteLivePreview } from "./l
import {
areSubDirPanelsEqual,
areSuggestionsEqual,
didAutocompleteClampViewportChange,
nextAutocompletePopupAnchorViewport,
resolveAutocompleteClampViewport,
resolveAutocompleteCursorCell,
resolveAutocompleteCwdWithSource,
resolveAutocompletePopupAnchorInViewport,
resolvePreservedSuggestionIndex,
type PopupClampViewport,
} from "./terminalAutocompleteLayout";
import { handleTerminalAutocompleteInput } from "./terminalAutocompleteInput";
import { handleTerminalAutocompleteKeyEvent } from "./terminalAutocompleteKeyEvent";
@@ -358,6 +361,13 @@ export function useTerminalAutocomplete(
const previewBaselineRef = useRef<string>("");
/** Whether a popup candidate is currently rendered into the command line (#1005). */
const previewActiveRef = useRef(false);
/**
* Clamp viewport measured at the last reposition. The popup derives its
* clamp bounds at render time, so a layout-only resize (e.g. pixel shrink
* from opening a side panel) must force a re-render even when the anchor
* did not move (#3204).
*/
const lastClampViewportRef = useRef<PopupClampViewport | null>(null);
/** Monotonic counter to invalidate stale async sub-dir fetches */
const subDirFetchVersionRef = useRef(0);
/**
@@ -477,6 +487,7 @@ export function useTerminalAutocomplete(
}
echoValidationTypedRef.current = null;
echoValidationStartedAtRef.current = null;
lastClampViewportRef.current = null;
ghostAddonRef.current?.hide();
completionAbortRef.current?.abort();
completionAbortRef.current = null;
@@ -509,6 +520,16 @@ export function useTerminalAutocomplete(
const term = termRef.current;
if (!term) return;
// Track clamp bounds so a layout-driven resize (anchor unchanged) still
// forces a render — the popup recomputes resolveAutocompleteClampViewport
// only when it re-renders (#3204).
const clampViewport = resolveAutocompleteClampViewport(containerRef.current);
const clampChanged = didAutocompleteClampViewportChange(
lastClampViewportRef.current,
clampViewport,
);
lastClampViewportRef.current = clampViewport;
setState((prev) => {
if (!prev.popupVisible || prev.suggestions.length === 0) return prev;
const { prompt } = getAlignedPrompt(term, typedInputBufferRef.current, typedBufferReliableRef.current);
@@ -542,7 +563,11 @@ export function useTerminalAutocomplete(
prev.expandUpward,
anchor,
);
if (!nextAnchor) return prev;
if (!nextAnchor) {
// Anchor unchanged but the clamp viewport moved (container resized);
// re-render so the popup can re-clamp its width/position.
return clampChanged ? { ...prev } : prev;
}
return {
...prev,
@@ -4,6 +4,7 @@ import assert from "node:assert/strict";
import {
clampAutocompletePopupGeometry,
computeAutocompletePopupPlacement,
didAutocompleteClampViewportChange,
nextAutocompletePopupAnchorViewport,
resolveAutocompleteAnchorInViewport,
resolveAutocompleteClampViewport,
@@ -874,3 +875,17 @@ test("nextAutocompletePopupAnchorViewport is a no-op when the stored viewport is
null,
);
});
test("didAutocompleteClampViewportChange detects layout-only viewport changes", () => {
const viewport = { left: 0, top: 0, width: 800, height: 600 };
assert.equal(didAutocompleteClampViewportChange(null, viewport), false);
assert.equal(didAutocompleteClampViewportChange(viewport, { ...viewport }), false);
assert.equal(
didAutocompleteClampViewportChange(viewport, { ...viewport, width: 700 }),
true,
);
assert.equal(
didAutocompleteClampViewportChange(viewport, { ...viewport, left: 12 }),
true,
);
});