From ec5ab3fc4d88c2b7bf227459840ecc226a08f753 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 9 Sep 2026 19:39:14 +0200 Subject: [PATCH] revert(app): undo file Find merge pending maintainer review Reverts 69510ac72f41ed50bcc596a8513866dafb1ea8b5. The feature must return to an open pull request for maintainer review. --- packages/app/e2e/browser/pane-find.spec.ts | 293 ------------------ .../src/file-pane/editor/extensions.web.ts | 9 +- .../app/src/file-pane/editor/view.web.tsx | 31 +- packages/app/src/file-pane/find/index.web.tsx | 122 -------- packages/app/src/file-pane/find/model.web.ts | 158 ---------- .../app/src/file-pane/source/view.web.tsx | 25 +- packages/app/src/i18n/resources/ar.ts | 15 - packages/app/src/i18n/resources/en.ts | 15 - packages/app/src/i18n/resources/es.ts | 15 - packages/app/src/i18n/resources/fr.ts | 15 - packages/app/src/i18n/resources/ja.ts | 15 - packages/app/src/i18n/resources/ko.ts | 15 - packages/app/src/i18n/resources/pt-BR.ts | 15 - packages/app/src/i18n/resources/ru.ts | 15 - packages/app/src/i18n/resources/zh-CN.ts | 15 - packages/app/src/pane-find/index.tsx | 260 ---------------- 16 files changed, 14 insertions(+), 1019 deletions(-) delete mode 100644 packages/app/e2e/browser/pane-find.spec.ts delete mode 100644 packages/app/src/file-pane/find/index.web.tsx delete mode 100644 packages/app/src/file-pane/find/model.web.ts delete mode 100644 packages/app/src/pane-find/index.tsx diff --git a/packages/app/e2e/browser/pane-find.spec.ts b/packages/app/e2e/browser/pane-find.spec.ts deleted file mode 100644 index 48d0d58a5c..0000000000 --- a/packages/app/e2e/browser/pane-find.spec.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { expect, test, type Page } from "../support/fixtures"; -import { openFileExplorer, openFileFromExplorer } from "../support/helpers/file-explorer"; -import { runWorkspaceActionFromCommandCenter } from "../support/helpers/command-center-workspace-actions"; -import { installDaemonWebSocketGate } from "../support/helpers/daemon-websocket-gate"; - -function source(page: Page) { - return page.getByTestId("file-source-editor").filter({ visible: true }).locator(".cm-content"); -} -function query(page: Page) { - return page.getByRole("textbox", { name: "Find in pane", exact: true }); -} -function status(page: Page) { - return page.getByRole("status", { name: "Find matches" }); -} -async function openSource(page: Page, filename: string) { - await openFileExplorer(page); - await openFileFromExplorer(page, filename); - await expect(source(page)).toBeVisible(); -} -async function findInSource(page: Page, text: string) { - await source(page).focus(); - await source(page).press("ControlOrMeta+f"); - await expect(query(page)).toBeFocused(); - await query(page).fill(text); -} -async function expectQuerySelected(page: Page, text: string) { - await expect(query(page)).toBeFocused(); - await expect(query(page)).toHaveValue(text); - await expect - .poll(() => - query(page).evaluate((input: HTMLInputElement) => [input.selectionStart, input.selectionEnd]), - ) - .toEqual([0, text.length]); -} -async function closeFind(page: Page) { - await query(page).press("Escape"); - await expect(source(page)).toBeFocused(); -} -async function expectFindAtTopRight(page: Page) { - const pane = await page - .getByTestId("workspace-file-pane") - .filter({ visible: true }) - .boundingBox(); - const widget = await page - .getByLabel("Find", { exact: true }) - .filter({ visible: true }) - .boundingBox(); - expect(widget!.x + widget!.width).toBeLessThanOrEqual(pane!.x + pane!.width); - expect(widget!.x).toBeGreaterThanOrEqual(pane!.x); - expect(widget!.y).toBeLessThan(pane!.y + 70); -} - -test("finds literal text at the top of editable source and returns focus at the inspected match", async ({ - page, - withWorkspace, -}, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-" }); - const file = path.join(workspace.repoPath, "source.txt"); - await writeFile(file, "first a.b\nsecond axb\nthird a.b\n"); - await workspace.navigateTo(); - await openSource(page, "source.txt"); - await findInSource(page, "a.b"); - await expect(status(page)).toHaveText("1 of 2"); - await query(page).fill("A.B"); - await expect(status(page)).toHaveText("1 of 2"); - await query(page).fill("a.b"); - await expectFindAtTopRight(page); - await query(page).press("Enter"); - await expect(status(page)).toHaveText("2 of 2"); - await query(page).press("Shift+Enter"); - await expect(status(page)).toHaveText("1 of 2"); - await page.screenshot({ path: testInfo.outputPath("editable-find.png") }); - await closeFind(page); - await expect(query(page)).toBeHidden(); - await expect(page.getByLabel("Line 1, column 10")).toBeVisible(); - - await test.step("reopen from touch action, preserve replace and Undo/save", async () => { - await page.getByRole("button", { name: "Find", exact: true }).click(); - await expect(query(page)).toBeFocused(); - await page.getByRole("button", { name: "Toggle replace" }).click(); - await page.getByRole("textbox", { name: "Replace with" }).fill("literal"); - await page.getByRole("button", { name: "Replace", exact: true }).click(); - await expect(source(page)).toContainText("first literal"); - await expect(status(page)).toHaveText("1 of 1"); - await page.screenshot({ path: testInfo.outputPath("replace.png") }); - await closeFind(page); - await source(page).press("ControlOrMeta+z"); - await expect(source(page)).toContainText("first a.b"); - await source(page).press("ControlOrMeta+s"); - await expect.poll(() => readFile(file, "utf8")).toBe("first a.b\nsecond axb\nthird a.b\n"); - await findInSource(page, "a.b"); - await page.getByRole("button", { name: "Toggle replace" }).click(); - await page.getByRole("textbox", { name: "Replace with" }).fill("all"); - await page.getByRole("button", { name: "Replace all", exact: true }).click(); - await expect(status(page)).toHaveText("No matches"); - await closeFind(page); - await source(page).press("ControlOrMeta+s"); - await expect.poll(() => readFile(file, "utf8")).toBe("first all\nsecond axb\nthird all\n"); - }); -}); - -test("searches the unsaved buffer while disconnected", async ({ page, withWorkspace }) => { - const gate = await installDaemonWebSocketGate(page); - const workspace = await withWorkspace({ prefix: "pane-find-draft-" }); - const file = path.join(workspace.repoPath, "draft.txt"); - await writeFile(file, "saved on disk\n"); - await workspace.navigateTo(); - await openSource(page, "draft.txt"); - await gate.drop(); - await source(page).fill("unsaved needle\nsecond needle\n"); - await findInSource(page, "needle"); - await expect(status(page)).toHaveText("1 of 2"); - expect(await readFile(file, "utf8")).toBe("saved on disk\n"); - gate.restore(); -}); - -test("searches read-only source beyond the viewport without replace controls", async ({ - page, - withWorkspace, -}, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-readonly-" }); - await writeFile( - path.join(workspace.repoPath, "large.txt"), - "needle first\n" + "plain source line\n".repeat(70_000) + "needle last\n", - ); - await workspace.navigateTo(); - await openSource(page, "large.txt"); - await findInSource(page, "needle"); - await expect(source(page)).toHaveAttribute("contenteditable", "false"); - await expect(status(page)).toHaveText("1 of 2"); - await expect(page.getByRole("button", { name: "Toggle replace" })).toHaveCount(0); - await query(page).press("Enter"); - await expect(status(page)).toHaveText("2 of 2"); - await expect(source(page)).toContainText("needle last"); - await page.screenshot({ path: testInfo.outputPath("readonly-find.png") }); - await closeFind(page); - await source(page).press("ControlOrMeta+f"); - await expect(query(page)).toBeFocused(); - await query(page).fill("plain"); - await expect(status(page)).toHaveText("1 of 10000+"); -}); - -test("targets the focused source in split panes and refocuses an open query", async ({ - page, - withWorkspace, -}, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-split-" }); - await writeFile(path.join(workspace.repoPath, "left.txt"), "left needle\n"); - await writeFile(path.join(workspace.repoPath, "right.txt"), "right needle\nright needle\n"); - await workspace.navigateTo(); - await openSource(page, "left.txt"); - await runWorkspaceActionFromCommandCenter(page, "Split pane right"); - await openFileFromExplorer(page, "right.txt"); - const left = source(page).filter({ hasText: "left needle" }); - const right = source(page).filter({ hasText: "right needle" }); - await expect(left).toBeVisible(); - await expect(right).toBeVisible(); - await left.click(); - await left.press("ControlOrMeta+f"); - await query(page).fill("needle"); - await expect(status(page)).toHaveText("1 of 1"); - await query(page).press("Escape"); - await right.click(); - await right.press("ControlOrMeta+f"); - await query(page).fill("needle"); - await expect(status(page)).toHaveText("1 of 2"); - await right.click(); - await right.press("ControlOrMeta+f"); - await expect(query(page)).toBeFocused(); - await expect(query(page)).toHaveValue("needle"); - await page.screenshot({ path: testInfo.outputPath("split-find.png") }); -}); - -test.describe("narrow touch browser", () => { - test.use({ hasTouch: true }); - test("opens Find by touch and keeps controls inside the source pane", async ({ - page, - withWorkspace, - }, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-touch-" }); - await writeFile(path.join(workspace.repoPath, "touch.txt"), "touch needle\nnext needle\n"); - await workspace.navigateTo(); - await openSource(page, "touch.txt"); - await page.setViewportSize({ width: 390, height: 844 }); - await page.getByRole("button", { name: "Find", exact: true }).tap(); - await expect(query(page)).toBeFocused(); - await query(page).fill("needle"); - await page.getByRole("button", { name: "Next match" }).tap(); - await expect(status(page)).toHaveText("2 of 2"); - await expectFindAtTopRight(page); - await page.screenshot({ path: testInfo.outputPath("touch-find.png") }); - await page.getByRole("button", { name: "Close Find" }).tap(); - await expect(source(page)).toBeFocused(); - }); -}); - -async function selectFirstTwoLines(page: Page) { - await source(page).focus(); - await source(page).press("ControlOrMeta+Home"); - await source(page).press("Shift+ArrowDown"); - await source(page).press("Shift+End"); -} - -async function selectLastWord(page: Page) { - await source(page).focus(); - await source(page).press("ControlOrMeta+End"); - await source(page).press("ArrowUp"); - await source(page).press("Home"); - await source(page).press("Shift+End"); -} - -test("declines multiline selection seeds and keeps replacement aligned with the visible query", async ({ - page, - withWorkspace, -}, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-single-line-" }); - const file = path.join(workspace.repoPath, "selection.txt"); - await writeFile(file, "alpha\nbeta\nalphabeta\n"); - await workspace.navigateTo(); - await openSource(page, "selection.txt"); - - await selectFirstTwoLines(page); - await source(page).press("ControlOrMeta+f"); - await expect(query(page)).toBeFocused(); - await expect(query(page)).toHaveValue(""); - await expect(page.getByRole("button", { name: "Next match" })).toBeDisabled(); - await query(page).press("Enter"); - await expect(query(page)).toHaveValue(""); - await closeFind(page); - - await selectFirstTwoLines(page); - await source(page).press("ControlOrMeta+g"); - await expect(query(page)).toHaveValue(""); - await expect(page.getByRole("button", { name: "Next match" })).toBeDisabled(); - await closeFind(page); - - await selectLastWord(page); - await source(page).press("ControlOrMeta+f"); - await expect(query(page)).toHaveValue("alphabeta"); - await query(page).press("Enter"); - await expect(status(page)).toHaveText("1 of 1"); - await closeFind(page); - await expect(page.getByLabel("Line 3, column 10")).toBeVisible(); - - await selectFirstTwoLines(page); - await page.getByRole("button", { name: "Find", exact: true }).click(); - await expect(query(page)).toHaveValue("alphabeta"); - await query(page).press("Enter"); - await expect(status(page)).toHaveText("1 of 1"); - await page.getByRole("button", { name: "Toggle replace" }).click(); - await page.getByRole("textbox", { name: "Replace with" }).fill("replaced"); - await page.getByRole("button", { name: "Replace", exact: true }).click(); - await expect(source(page)).toContainText("alpha"); - await expect(source(page)).toContainText("beta"); - await expect(status(page)).toHaveText("No matches"); - await page.screenshot({ path: testInfo.outputPath("single-line-query-replacement.png") }); - await closeFind(page); - await source(page).press("ControlOrMeta+s"); - await expect.poll(() => readFile(file, "utf8")).toBe("alpha\nbeta\nreplaced\n"); -}); - -for (const startingField of ["Find in pane", "Replace with"] as const) { - test(`repeated Find from ${startingField} selects the query for new typing`, async ({ - page, - withWorkspace, - }, testInfo) => { - const workspace = await withWorkspace({ prefix: "pane-find-repeat-input-" }); - await writeFile(path.join(workspace.repoPath, "source.txt"), "needle first\nneedle second\n"); - await workspace.navigateTo(); - await openSource(page, "source.txt"); - await findInSource(page, "needle"); - await page.getByRole("button", { name: "Toggle replace" }).click(); - const replacement = page.getByRole("textbox", { name: "Replace with" }); - await replacement.fill("replacement"); - - await test.step("repeat Find from the input and type a fresh query", async () => { - const startingInput = page.getByRole("textbox", { name: startingField, exact: true }); - await startingInput.press("End"); - await startingInput.press("ControlOrMeta+f"); - await expectQuerySelected(page, "needle"); - await page.keyboard.type("first"); - await expect(query(page)).toHaveValue("first"); - await expect(replacement).toHaveValue("replacement"); - await expect(status(page)).toHaveText("1 of 1"); - }); - - await page.screenshot({ path: testInfo.outputPath("repeat-find-input.png") }); - await closeFind(page); - await expect(page.getByLabel("Line 1, column 13")).toBeVisible(); - }); -} diff --git a/packages/app/src/file-pane/editor/extensions.web.ts b/packages/app/src/file-pane/editor/extensions.web.ts index e9310bd554..47fc0ab3c5 100644 --- a/packages/app/src/file-pane/editor/extensions.web.ts +++ b/packages/app/src/file-pane/editor/extensions.web.ts @@ -5,6 +5,7 @@ import { indentOnInput, syntaxHighlighting, } from "@codemirror/language"; +import { searchKeymap } from "@codemirror/search"; import { EditorView, drawSelection, @@ -41,6 +42,7 @@ export function editorBaseExtensions(onSave: () => void) { indentWithTab, ...defaultKeymap, ...historyKeymap, + ...searchKeymap, ]), ]; } @@ -61,13 +63,6 @@ export function editorTheme(theme: EditorVisualTheme) { fontFamily: theme.monoFont, lineHeight: "1.45", }, - ".cm-panels": { backgroundColor: theme.background, color: theme.foreground }, - ".cm-panels-top": { borderBottom: "none" }, - ".cm-searchMatch": { backgroundColor: theme.selection }, - ".cm-searchMatch-selected": { - backgroundColor: theme.selection, - outline: `1px solid ${theme.cursor}`, - }, ".cm-content": { caretColor: theme.foreground, padding: "16px 0" }, ".cm-cursor, .cm-dropCursor": { borderLeftColor: theme.cursor }, ".cm-gutters": { diff --git a/packages/app/src/file-pane/editor/view.web.tsx b/packages/app/src/file-pane/editor/view.web.tsx index d396697c41..03ab27a778 100644 --- a/packages/app/src/file-pane/editor/view.web.tsx +++ b/packages/app/src/file-pane/editor/view.web.tsx @@ -1,5 +1,4 @@ -import { useEffect, useRef, useState, useSyncExternalStore } from "react"; -import { FileFind, FileFindModel } from "../find/index.web"; +import { useEffect, useRef, useSyncExternalStore } from "react"; import { Annotation, Compartment, EditorState, Transaction } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { getLanguageForFile } from "@getpaseo/highlight"; @@ -39,7 +38,6 @@ export function FileEditorView({ onCursorChange, onVimModeChange, }: FileEditorViewProps) { - const [find] = useState(() => new FileFindModel()); const hostRef = useRef(null); const viewRef = useRef(null); const snapshot = useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot); @@ -56,7 +54,6 @@ export function FileEditorView({ doc: values.content, extensions: [ vimCompartment.of(values.vimEnabled ? vim() : []), - find.extension, ...editorBaseExtensions(() => void values.model.save()), languageCompartment.of(getLanguageForFile(values.filename)?.extension ?? []), wrappingCompartment.of(wrappingForFile(values.filename)), @@ -84,7 +81,7 @@ export function FileEditorView({ view.destroy(); viewRef.current = null; }; - }, [find]); + }, []); useEffect(() => { const view = viewRef.current; @@ -144,25 +141,15 @@ export function FileEditorView({ }, [onVimModeChange, vimEnabled]); return ( -
-
- -
+
); } const remoteUpdate = Annotation.define(); -const FRAME_STYLE = { - display: "flex", - position: "relative", - flex: 1, - minHeight: 0, - minWidth: 0, -} as const; const HOST_STYLE = { flex: 1, minHeight: 0, overflow: "hidden" } as const; diff --git a/packages/app/src/file-pane/find/index.web.tsx b/packages/app/src/file-pane/find/index.web.tsx deleted file mode 100644 index 70801aa0ca..0000000000 --- a/packages/app/src/file-pane/find/index.web.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { - useCallback, - useMemo, - useEffect, - useRef, - useSyncExternalStore, - type RefObject, -} from "react"; -import { createPortal } from "react-dom"; -import { EditorView } from "@codemirror/view"; -import { Search } from "lucide-react-native"; -import { View } from "react-native"; -import { StyleSheet } from "react-native-unistyles"; -import { useTranslation } from "react-i18next"; -import { Button } from "@/components/ui/button"; -import { PaneFind, type PaneFindHandle } from "@/pane-find"; -import { usePaneFocus } from "@/panels/pane-context"; -import { hasActiveWebOverlay } from "@/lib/overlay-root"; -import { useRetainedPanelActive } from "@/components/retained-panel"; -import { isImeComposingKeyboardEvent } from "@/utils/keyboard-ime"; -import { FileFindModel } from "./model.web"; - -export { FileFindModel } from "./model.web"; - -export function FileFind({ - model, - editor, -}: { - model: FileFindModel; - editor: RefObject; -}) { - const { t } = useTranslation(); - const state = useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot); - const widget = useRef(null); - const { isInteractive, focusPane } = usePaneFocus(); - const active = useRetainedPanelActive(); - const open = useCallback(() => { - focusPane(); - model.open(editor.current); - widget.current?.focus(); - }, [editor, focusPane, model]); - - useEffect(() => { - if (!isInteractive || !active) return; - function onKeyDown(event: KeyboardEvent) { - if (event.defaultPrevented || hasActiveWebOverlay() || isImeComposingKeyboardEvent(event)) - return; - if ( - (event.metaKey || event.ctrlKey) && - !event.altKey && - !event.shiftKey && - event.key.toLowerCase() === "f" - ) { - event.preventDefault(); - model.open(editor.current); - widget.current?.focus(); - } - } - document.addEventListener("keydown", onKeyDown); - return () => document.removeEventListener("keydown", onKeyDown); - }, [active, editor, isInteractive, model]); - - const replace = useMemo( - () => - state.readOnly - ? undefined - : { - value: state.replacement, - onChange: model.setReplacement, - onReplace: model.replace, - onReplaceAll: model.replaceAll, - }, - [model, state.readOnly, state.replacement], - ); - if (!state.panel) - return ( - - - - - - ) : null} - - ); -}); - -const styles = StyleSheet.create((theme) => ({ - widget: { - width: 480, - maxWidth: "100%", - padding: theme.spacing[2], - gap: theme.spacing[1], - backgroundColor: theme.colors.surface1, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - borderRadius: theme.borderRadius.md, - }, - findRow: { flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: theme.spacing[1] }, - queryRow: { - flex: 1, - minWidth: 140, - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[1], - }, - row: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1] }, - replacement: { gap: theme.spacing[1] }, - input: { - flex: 1, - minWidth: 0, - minHeight: CONTROL_HEIGHTS.compact, - paddingHorizontal: theme.spacing[2], - paddingVertical: theme.spacing[1], - fontSize: theme.fontSize.base, - color: theme.colors.foreground, - backgroundColor: theme.colors.surface0, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - borderRadius: theme.borderRadius.sm, - }, - status: { - flex: 1, - color: theme.colors.foregroundMuted, - fontSize: theme.fontSize.sm, - paddingHorizontal: theme.spacing[2], - }, -}));