2024-01-12 05:09:29 -05:00
|
|
|
import { ChangeSet, EditorState, StateField } from '@codemirror/state'
|
2023-04-13 04:21:25 -04:00
|
|
|
import {
|
|
|
|
ProjectionItem,
|
|
|
|
ProjectionResult,
|
|
|
|
getUpdatedProjection,
|
|
|
|
EnterNodeFn,
|
|
|
|
ProjectionStatus,
|
|
|
|
} from './tree-operations/projection'
|
2024-01-12 05:09:29 -05:00
|
|
|
import { languageLoadedEffect } from '@/features/source-editor/extensions/language'
|
2023-04-13 04:21:25 -04:00
|
|
|
|
|
|
|
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>> {
|
2024-01-12 05:09:29 -05:00
|
|
|
const initialiseProjection = (state: EditorState) =>
|
|
|
|
getUpdatedProjection(
|
|
|
|
state,
|
|
|
|
0,
|
|
|
|
state.doc.length,
|
|
|
|
0,
|
|
|
|
state.doc.length,
|
|
|
|
true,
|
|
|
|
enterNode
|
|
|
|
)
|
|
|
|
|
2023-04-13 04:21:25 -04:00
|
|
|
const field = StateField.define<ProjectionResult<T>>({
|
|
|
|
create(state) {
|
2024-01-12 05:09:29 -05:00
|
|
|
return initialiseProjection(state)
|
2023-04-13 04:21:25 -04:00
|
|
|
},
|
|
|
|
update(currentProjection, transaction) {
|
2024-01-12 05:09:29 -05:00
|
|
|
if (transaction.effects.some(effect => effect.is(languageLoadedEffect))) {
|
|
|
|
return initialiseProjection(transaction.state)
|
|
|
|
}
|
|
|
|
|
2023-04-13 04:21:25 -04:00
|
|
|
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
|
|
|
|
}
|