language.ts 3.4 KB

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