diff --git a/packages/app/e2e/file-editing.spec.ts b/packages/app/e2e/file-editing.spec.ts index 2a536c30e..8045b15e9 100644 --- a/packages/app/e2e/file-editing.spec.ts +++ b/packages/app/e2e/file-editing.spec.ts @@ -18,6 +18,14 @@ function editor(page: Page) { return page.getByTestId("file-source-editor").filter({ visible: true }).locator(".cm-content"); } +function hasHorizontalOverflow(element: HTMLElement): boolean { + return element.scrollWidth > element.clientWidth; +} + +function fitsViewportWidth(element: HTMLElement): boolean { + return element.scrollWidth === element.clientWidth; +} + async function replaceEditorText(page: Page, content: string): Promise { const contentElement = editor(page); await contentElement.click(); @@ -202,6 +210,36 @@ test.describe("CodeMirror workspace file editing", () => { ).toHaveCSS("font-family", "monospace"); }); + test("wraps Markdown while source code remains horizontally scrollable", async ({ + page, + withWorkspace, + }) => { + const workspace = await withWorkspace({ prefix: "file-editing-wrap-" }); + const longLine = "word ".repeat(300); + await writeFile(path.join(workspace.repoPath, "notes.md"), `${longLine}\n`, "utf8"); + await writeFile( + path.join(workspace.repoPath, "source.ts"), + `const value = "${longLine}";\n`, + "utf8", + ); + await workspace.navigateTo(); + await openWorkspaceFile(page, "notes.md"); + await page.getByTestId("file-mode-source").click(); + + const markdownScroller = page + .getByTestId("file-source-editor") + .filter({ visible: true }) + .locator(".cm-scroller"); + await expect.poll(() => markdownScroller.evaluate(fitsViewportWidth)).toBe(true); + + await openWorkspaceFile(page, "source.ts"); + const sourceScroller = page + .getByTestId("file-source-editor") + .filter({ visible: true }) + .locator(".cm-scroller"); + await expect.poll(() => sourceScroller.evaluate(hasHorizontalOverflow)).toBe(true); + }); + test("autosaves, saves immediately, resolves conflicts, and restores live updates after reconnect", async ({ page, withWorkspace, diff --git a/packages/app/src/file-pane/editor/view.web.tsx b/packages/app/src/file-pane/editor/view.web.tsx index abf79f4ed..03ab27a77 100644 --- a/packages/app/src/file-pane/editor/view.web.tsx +++ b/packages/app/src/file-pane/editor/view.web.tsx @@ -3,6 +3,7 @@ import { Annotation, Compartment, EditorState, Transaction } from "@codemirror/s import { EditorView } from "@codemirror/view"; import { getLanguageForFile } from "@getpaseo/highlight"; import { getCM, vim } from "@replit/codemirror-vim"; +import { isRenderedMarkdownFile } from "@/components/file-pane-render-mode"; import type { WorkspaceFileLocation } from "@/workspace/file-open"; import type { FileEditorModel } from "./model"; import { editorBaseExtensions, editorTheme, type EditorVisualTheme } from "./extensions.web"; @@ -19,9 +20,14 @@ interface FileEditorViewProps { } const languageCompartment = new Compartment(); +const wrappingCompartment = new Compartment(); const themeCompartment = new Compartment(); const vimCompartment = new Compartment(); +function wrappingForFile(filename: string) { + return isRenderedMarkdownFile(filename) ? EditorView.lineWrapping : []; +} + export function FileEditorView({ model, filename, @@ -50,6 +56,7 @@ export function FileEditorView({ vimCompartment.of(values.vimEnabled ? vim() : []), ...editorBaseExtensions(() => void values.model.save()), languageCompartment.of(getLanguageForFile(values.filename)?.extension ?? []), + wrappingCompartment.of(wrappingForFile(values.filename)), themeCompartment.of(editorTheme(values.theme)), EditorView.updateListener.of((update) => { if ( @@ -104,7 +111,10 @@ export function FileEditorView({ useEffect(() => { viewRef.current?.dispatch({ - effects: languageCompartment.reconfigure(getLanguageForFile(filename)?.extension ?? []), + effects: [ + languageCompartment.reconfigure(getLanguageForFile(filename)?.extension ?? []), + wrappingCompartment.reconfigure(wrappingForFile(filename)), + ], }); }, [filename]);