language.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import {
  2. Compartment,
  3. StateEffect,
  4. StateField,
  5. TransactionSpec,
  6. } from '@codemirror/state'
  7. import { languages } from '../languages'
  8. import { ViewPlugin } from '@codemirror/view'
  9. import { indentUnit, LanguageDescription } from '@codemirror/language'
  10. import { updateHasEffect } from '../utils/effects'
  11. import { Folder } from '../../../../../types/folder'
  12. import { Command } from '@/features/ide-react/context/metadata-context'
  13. import { AdvancedReferenceSearchResult } from '@/features/ide-react/references/types'
  14. export const languageLoadedEffect = StateEffect.define()
  15. export const hasLanguageLoadedEffect = updateHasEffect(languageLoadedEffect)
  16. const languageConf = new Compartment()
  17. type Options = {
  18. syntaxValidation: boolean
  19. }
  20. export type Metadata = {
  21. labels: Set<string>
  22. packageNames: Set<string>
  23. commands: Command[]
  24. referenceKeys: Set<string>
  25. searchLocalReferences: (
  26. query: string
  27. ) => Promise<AdvancedReferenceSearchResult>
  28. fileTreeData: Folder
  29. }
  30. /**
  31. * A state field that stores the metadata parsed from a project on the server.
  32. */
  33. export const metadataState = StateField.define<Metadata | undefined>({
  34. create: () => undefined,
  35. update: (value, transaction) => {
  36. for (const effect of transaction.effects) {
  37. if (effect.is(setMetadataEffect)) {
  38. return effect.value
  39. }
  40. }
  41. return value
  42. },
  43. })
  44. const languageCompartment = new Compartment()
  45. /**
  46. * The parser and support extensions for each supported language,
  47. * which are loaded dynamically as needed.
  48. */
  49. export const language = (
  50. docName: string,
  51. metadata: Metadata,
  52. { syntaxValidation }: Options
  53. ) => languageCompartment.of(buildExtension(docName, metadata, syntaxValidation))
  54. const buildExtension = (
  55. docName: string,
  56. metadata: Metadata,
  57. syntaxValidation: boolean
  58. ) => {
  59. const languageDescription = LanguageDescription.matchFilename(
  60. languages,
  61. docName
  62. )
  63. if (!languageDescription) {
  64. return []
  65. }
  66. return [
  67. /**
  68. * Default to four-space indentation and set the configuration in advance,
  69. * to prevent a shift in line indentation markers when the LaTeX language loads.
  70. */
  71. languageConf.of(indentUnit.of(' ')),
  72. metadataState,
  73. /**
  74. * A view plugin which loads the appropriate language for the current file extension,
  75. * then dispatches an effect so other extensions can update accordingly.
  76. */
  77. ViewPlugin.define(view => {
  78. // load the language asynchronously
  79. languageDescription.load().then(support => {
  80. view.dispatch({
  81. effects: [
  82. languageConf.reconfigure(support),
  83. languageLoadedEffect.of(null),
  84. ],
  85. })
  86. // Wait until the previous effects have been processed
  87. view.dispatch({
  88. effects: [
  89. setMetadataEffect.of(metadata),
  90. setSyntaxValidationEffect.of(syntaxValidation),
  91. ],
  92. })
  93. })
  94. return {}
  95. }),
  96. metadataState,
  97. ]
  98. }
  99. export const setLanguage = (
  100. docName: string,
  101. metadata: Metadata,
  102. syntaxValidation: boolean
  103. ) => {
  104. return {
  105. effects: languageCompartment.reconfigure(
  106. buildExtension(docName, metadata, syntaxValidation)
  107. ),
  108. }
  109. }
  110. export const setMetadataEffect = StateEffect.define<Metadata>()
  111. export const setMetadata = (values: Metadata): TransactionSpec => {
  112. return {
  113. effects: setMetadataEffect.of(values),
  114. }
  115. }
  116. export const setSyntaxValidationEffect = StateEffect.define<boolean>()
  117. export const setSyntaxValidation = (value: boolean): TransactionSpec => {
  118. return {
  119. effects: setSyntaxValidationEffect.of(value),
  120. }
  121. }