overleaf/services/web/frontend/js/features/source-editor/utils/projection-state-field.ts
Alf Eaton 4a7a24b44d Update the CodeMirror language when the current file is renamed (#16342)
GitOrigin-RevId: 8b51df0d1acfeeb8b0323cebf6de78572c8cb95c
2024-01-15 09:04:27 +00:00

81 lines
2.3 KiB
TypeScript

import { ChangeSet, EditorState, StateField } from '@codemirror/state'
import {
ProjectionItem,
ProjectionResult,
getUpdatedProjection,
EnterNodeFn,
ProjectionStatus,
} from './tree-operations/projection'
import { languageLoadedEffect } from '@/features/source-editor/extensions/language'
export function mergeChangeRanges(changes: ChangeSet) {
let fromA = Number.MAX_VALUE
let fromB = Number.MAX_VALUE
let toA = Number.MIN_VALUE
let toB = Number.MIN_VALUE
changes.iterChangedRanges(
(changeFromA, changeToA, changeFromB, changeToB) => {
fromA = Math.min(changeFromA, fromA)
fromB = Math.min(changeFromB, fromB)
toA = Math.max(changeToA, toA)
toB = Math.max(changeToB, toB)
}
)
return { fromA, toA, fromB, toB }
}
/**
* Creates a StateField to manage a 'projection' of the document. Type T is the subclass of
* ProjectionItem that we will extract from the document.
*
* @param enterNode A function to call when 'enter'ing a node while traversing the syntax tree,
* Used to identify nodes we are interested in, and create instances of T.
*/
export function makeProjectionStateField<T extends ProjectionItem>(
enterNode: EnterNodeFn<T>
): StateField<ProjectionResult<T>> {
const initialiseProjection = (state: EditorState) =>
getUpdatedProjection(
state,
0,
state.doc.length,
0,
state.doc.length,
true,
enterNode
)
const field = StateField.define<ProjectionResult<T>>({
create(state) {
return initialiseProjection(state)
},
update(currentProjection, transaction) {
if (transaction.effects.some(effect => effect.is(languageLoadedEffect))) {
return initialiseProjection(transaction.state)
}
if (
transaction.docChanged ||
currentProjection.status !== ProjectionStatus.Complete
) {
const { fromA, toA, fromB, toB } = mergeChangeRanges(
transaction.changes
)
const list = getUpdatedProjection<T>(
transaction.state,
fromA,
toA,
fromB,
toB,
false,
enterNode,
transaction,
currentProjection
)
return list
}
return currentProjection
},
})
return field
}