Merge pull request #26583 from overleaf/td-editor-scope-values-to-context

Move scope values starting with `editor.` to contexts

GitOrigin-RevId: 7ca349ceff002228cf4e931c644c8c386eb6c597
This commit is contained in:
Tim Down
2025-07-09 08:05:15 +00:00
committed by Copybot
parent b2fb70c2b6
commit 132ccbc4cc
40 changed files with 593 additions and 292 deletions
@@ -1,14 +1,15 @@
import { ChangeEvent, FC, memo, useCallback } from 'react'
import useScopeValue from '@/shared/hooks/use-scope-value'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { sendMB } from '../../../infrastructure/event-tracking'
import { isValidTeXFile } from '../../../main/is-valid-tex-file'
import { useTranslation } from 'react-i18next'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
function EditorSwitch() {
const { t } = useTranslation()
const [visual, setVisual] = useScopeValue('editor.showVisual')
const { showVisual: visual, setShowVisual: setVisual } =
useEditorPropertiesContext()
const { openDocName } = useEditorOpenDocContext()
const richTextAvailable = openDocName ? isValidTeXFile(openDocName) : false
@@ -15,6 +15,7 @@ import { withinFormattingCommand } from '@/features/source-editor/utils/tree-ope
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { isMac } from '@/shared/utils/os'
import { useProjectContext } from '@/shared/context/project-context'
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
export const ToolbarItems: FC<{
state: EditorState
@@ -30,8 +31,9 @@ export const ToolbarItems: FC<{
listDepth,
}) {
const { t } = useTranslation()
const { toggleSymbolPalette, showSymbolPalette, writefullInstance } =
useEditorContext()
const { showSymbolPalette, toggleSymbolPalette } =
useEditorPropertiesContext()
const { writefullInstance } = useEditorContext()
const { features } = useProjectContext()
const isActive = withinFormattingCommand(state)
@@ -0,0 +1,3 @@
import { StateEffect } from '@codemirror/state'
export const beforeChangeDocEffect = StateEffect.define()
@@ -139,7 +139,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
cursorHighlights(),
autoPair(options.settings),
editable(),
search(),
search(options.initialSearchQuery),
phrases(options.phrases),
spelling(options.spelling),
shortcuts,
@@ -15,6 +15,7 @@ import {
KeyBinding,
keymap,
ViewPlugin,
ViewUpdate,
} from '@codemirror/view'
import {
Annotation,
@@ -29,6 +30,7 @@ import {
} from '@codemirror/state'
import { sendSearchEvent } from '@/features/event-tracking/search-events'
import { isVisual } from '@/features/source-editor/extensions/visual/visual'
import { beforeChangeDocEffect } from '@/features/source-editor/extensions/before-change-doc'
const restoreSearchQueryAnnotation = Annotation.define<boolean>()
@@ -110,10 +112,6 @@ const highlightSelectionMatchesExtension = highlightSelectionMatches({
wholeWords: true,
})
// store the search query for use when switching between files
// TODO: move this into EditorContext?
let searchQuery: SearchQuery | null
const scrollToMatch = (range: SelectionRange, view: EditorView) => {
const coords = {
from: view.coordsAtPos(range.from),
@@ -153,7 +151,7 @@ const searchEventKeymap: KeyBinding[] = [
/**
* A collection of extensions related to the search feature.
*/
export const search = () => {
export const search = (initialSearchQuery: SearchQuery | null) => {
let open = false
return [
@@ -195,9 +193,7 @@ export const search = () => {
}
},
destroy() {
window.setTimeout(() => {
open = false // in a timeout, so the view plugin below can run its destroy method first
}, 0)
open = false
},
}
},
@@ -205,8 +201,8 @@ export const search = () => {
// restore a stored search and re-open the search panel
ViewPlugin.define(view => {
if (searchQuery) {
const _searchQuery = searchQuery
if (initialSearchQuery) {
const _searchQuery = initialSearchQuery
window.setTimeout(() => {
openSearchPanel(view)
view.dispatch({
@@ -217,9 +213,21 @@ export const search = () => {
}
return {
destroy() {
// persist the current search query if the panel is open
searchQuery = open ? getSearchQuery(view.state) : null
// Fire an event containing the search query before a document change
// so that it can be persisted for the next document
update(update: ViewUpdate) {
for (const tr of update.transactions) {
for (const effect of tr.effects) {
if (effect.is(beforeChangeDocEffect)) {
const searchQuery = open ? getSearchQuery(view.state) : null
window.dispatchEvent(
new CustomEvent('search-panel-before-doc-change', {
detail: searchQuery,
})
)
}
}
}
},
}
}),
@@ -51,14 +51,14 @@ import { updateRanges } from '@/features/source-editor/extensions/ranges'
import { useThreadsContext } from '@/features/review-panel-new/context/threads-context'
import { useHunspell } from '@/features/source-editor/hooks/use-hunspell'
import { Permissions } from '@/features/ide-react/types/permissions'
import {
GotoOffsetOptions,
useEditorManagerContext,
} from '@/features/ide-react/context/editor-manager-context'
import { GotoOffsetOptions } from '@/features/ide-react/context/editor-manager-context'
import { GotoLineOptions } from '@/features/ide-react/types/goto-line-options'
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { useEditorPropertiesContext } from '@/features/ide-react/context/editor-properties-context'
import { SearchQuery } from '@codemirror/search'
import { beforeChangeDocEffect } from '@/features/source-editor/extensions/before-change-doc'
function useCodeMirrorScope(view: EditorView) {
const { fileTreeData } = useFileTreeData()
@@ -71,7 +71,6 @@ function useCodeMirrorScope(view: EditorView) {
useCompileContext()
const { openDocName, currentDocument } = useEditorOpenDocContext()
const { trackChanges } = useEditorManagerContext()
const metadata = useMetadataContext()
const { id: userId } = useUserContext()
@@ -106,7 +105,7 @@ function useCodeMirrorScope(view: EditorView) {
const hunspellManager = useHunspell(spellCheckLanguage)
const [visual] = useScopeValue<boolean>('editor.showVisual')
const { showVisual: visual, trackChanges } = useEditorPropertiesContext()
const { referenceKeys } = useReferencesContext()
@@ -271,6 +270,16 @@ function useCodeMirrorScope(view: EditorView) {
visual: showVisual,
})
// Persist the search query in this hook when the document changes by keeping
// a reference to the search query in sync with the editor state
const searchQueryRef = useRef<SearchQuery | null>(null)
useEventListener(
'search-panel-before-doc-change',
useCallback((event: CustomEvent) => {
searchQueryRef.current = event.detail
}, [])
)
const { showBoundary } = useErrorBoundary()
const handleException = useCallback((exception: any) => {
@@ -295,6 +304,13 @@ function useCodeMirrorScope(view: EditorView) {
if (currentDocument) {
debugConsole.log('creating new editor state')
// Warn any interested extension that the document is about to change,
// allowing it to perform any necessary actions before creating the new
// state. destroy() is too late because the new state is already created
view.dispatch({
effects: beforeChangeDocEffect.of(null),
})
const state = EditorState.create({
doc: currentDocument.getSnapshot(),
extensions: createExtensions({
@@ -310,6 +326,7 @@ function useCodeMirrorScope(view: EditorView) {
spelling: spellingRef.current,
visual: visualRef.current,
projectFeatures: projectFeaturesRef.current,
initialSearchQuery: searchQueryRef.current,
showBoundary,
handleException,
}),