projection-state-field.ts 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { ChangeSet, EditorState, StateField } from '@codemirror/state'
  2. import {
  3. ProjectionItem,
  4. ProjectionResult,
  5. getUpdatedProjection,
  6. EnterNodeFn,
  7. ProjectionStatus,
  8. } from './tree-operations/projection'
  9. import { languageLoadedEffect } from '@/features/source-editor/extensions/language'
  10. export function mergeChangeRanges(changes: ChangeSet) {
  11. let fromA = Number.MAX_VALUE
  12. let fromB = Number.MAX_VALUE
  13. let toA = Number.MIN_VALUE
  14. let toB = Number.MIN_VALUE
  15. changes.iterChangedRanges(
  16. (changeFromA, changeToA, changeFromB, changeToB) => {
  17. fromA = Math.min(changeFromA, fromA)
  18. fromB = Math.min(changeFromB, fromB)
  19. toA = Math.max(changeToA, toA)
  20. toB = Math.max(changeToB, toB)
  21. }
  22. )
  23. return { fromA, toA, fromB, toB }
  24. }
  25. /**
  26. * Creates a StateField to manage a 'projection' of the document. Type T is the subclass of
  27. * ProjectionItem that we will extract from the document.
  28. *
  29. * @param enterNode A function to call when 'enter'ing a node while traversing the syntax tree,
  30. * Used to identify nodes we are interested in, and create instances of T.
  31. */
  32. export function makeProjectionStateField<T extends ProjectionItem>(
  33. enterNode: EnterNodeFn<T>
  34. ): StateField<ProjectionResult<T>> {
  35. const initialiseProjection = (state: EditorState) =>
  36. getUpdatedProjection(
  37. state,
  38. 0,
  39. state.doc.length,
  40. 0,
  41. state.doc.length,
  42. true,
  43. enterNode
  44. )
  45. const field = StateField.define<ProjectionResult<T>>({
  46. create(state) {
  47. return initialiseProjection(state)
  48. },
  49. update(currentProjection, transaction) {
  50. if (transaction.effects.some(effect => effect.is(languageLoadedEffect))) {
  51. return initialiseProjection(transaction.state)
  52. }
  53. if (
  54. transaction.docChanged ||
  55. currentProjection.status !== ProjectionStatus.Complete
  56. ) {
  57. const { fromA, toA, fromB, toB } = mergeChangeRanges(
  58. transaction.changes
  59. )
  60. const list = getUpdatedProjection<T>(
  61. transaction.state,
  62. fromA,
  63. toA,
  64. fromB,
  65. toB,
  66. false,
  67. enterNode,
  68. transaction,
  69. currentProjection
  70. )
  71. return list
  72. }
  73. return currentProjection
  74. },
  75. })
  76. return field
  77. }