language.ts 2.8 KB

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