From 9958ffdf6803246e9e30c24f13bbf1e83b23e99c Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 28 Jul 2025 10:53:29 +0100 Subject: [PATCH] Merge pull request #27123 from overleaf/ae-goto-scroll-select-text Highlight double-clicked word when syncing position from PDF to code GitOrigin-RevId: da120af9dec203346cb85c6aa7e403f4e585c748 --- .../pdf-preview/components/pdf-js-viewer.tsx | 5 +++- .../features/pdf-preview/hooks/use-synctex.ts | 12 +++++----- .../extensions/cursor-position.ts | 23 +++++++++++++++---- .../features/source-editor/utils/position.ts | 2 +- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx index 3f9cfb4513..ff6363ea08 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx @@ -268,7 +268,10 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) { window.dispatchEvent( new CustomEvent('synctex:sync-to-position', { - detail: clickPosition, + detail: { + position: clickPosition, + selectText: window.getSelection()?.toString(), + }, }) ) } diff --git a/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts b/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts index 51c8eefc7c..328c9bfe37 100644 --- a/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts +++ b/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts @@ -89,12 +89,13 @@ export default function useSynctex(): { }, [dirname, getCurrentDocumentId, pathInFolder, rootDocId]) const goToCodeLine = useCallback( - (file?: string, line?: number) => { + (file?: string, line?: number, selectText?: string) => { if (file) { const doc = findEntityByPath(file)?.entity if (doc) { openDocWithId(doc._id, { gotoLine: line, + selectText, }) return } @@ -186,9 +187,11 @@ export default function useSynctex(): { const _syncToCode = useCallback( ({ position = positionRef.current, + selectText, visualOffset = 0, }: { position?: PdfScrollPosition + selectText?: string visualOffset?: number }) => { if (!position) { @@ -231,7 +234,7 @@ export default function useSynctex(): { getJSON(`/project/${projectId}/sync/pdf?${params}`, { signal }) .then(data => { const [{ file, line }] = data.code - goToCodeLine(file, line) + goToCodeLine(file, line, selectText) if (data.downloadedFromCache) { sendMB('synctex-downloaded-from-cache', { projectId, @@ -266,10 +269,7 @@ export default function useSynctex(): { useEventListener( 'synctex:sync-to-position', - useCallback( - (event: CustomEvent) => syncToCode({ position: event.detail }), - [syncToCode] - ) + useCallback((event: CustomEvent) => syncToCode(event.detail), [syncToCode]) ) const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState( diff --git a/services/web/frontend/js/features/source-editor/extensions/cursor-position.ts b/services/web/frontend/js/features/source-editor/extensions/cursor-position.ts index 7246432ed7..0397f26123 100644 --- a/services/web/frontend/js/features/source-editor/extensions/cursor-position.ts +++ b/services/web/frontend/js/features/source-editor/extensions/cursor-position.ts @@ -143,7 +143,7 @@ const dispatchSelectionAndScroll = ( export const setCursorLineAndScroll = ( view: EditorView, lineNumber: number, - columnNumber = 0, + columnNumber?: number, selectText?: string ) => { // TODO: map the position through any changes since the previous compile? @@ -153,10 +153,23 @@ export const setCursorLineAndScroll = ( const from = findValidPosition(doc, lineNumber, columnNumber) if (selectText) { - const to = from + selectText.length - if (doc.sliceString(from, to) === selectText) { - dispatchSelectionAndScroll(view, from, to) - return + if (columnNumber === undefined) { + // somewhere on this line + const line = doc.lineAt(from) + const index = line.text.indexOf(selectText) + if (index > -1 && index === line.text.lastIndexOf(selectText)) { + const from = line.from + index + const to = from + selectText.length + dispatchSelectionAndScroll(view, from, to) + return + } + } else { + // at this exact position + const to = from + selectText.length + if (doc.sliceString(from, to) === selectText) { + dispatchSelectionAndScroll(view, from, to) + return + } } } diff --git a/services/web/frontend/js/features/source-editor/utils/position.ts b/services/web/frontend/js/features/source-editor/utils/position.ts index 22f9e7041e..da1f745a4c 100644 --- a/services/web/frontend/js/features/source-editor/utils/position.ts +++ b/services/web/frontend/js/features/source-editor/utils/position.ts @@ -3,7 +3,7 @@ import { Text } from '@codemirror/state' export const findValidPosition = ( doc: Text, lineNumber: number, // 1-indexed - columnNumber: number // 0-indexed + columnNumber = 0 // 0-indexed ): number => { const lines = doc.lines