language.ts 3.1 KB

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