2023-04-13 04:21:25 -04:00
|
|
|
import {
|
|
|
|
Compartment,
|
|
|
|
StateEffect,
|
|
|
|
StateField,
|
|
|
|
TransactionSpec,
|
|
|
|
} from '@codemirror/state'
|
|
|
|
import { languages } from '../languages'
|
|
|
|
import { ViewPlugin } from '@codemirror/view'
|
|
|
|
import { indentUnit, LanguageDescription } from '@codemirror/language'
|
|
|
|
import { Metadata } from '../../../../../types/metadata'
|
|
|
|
import { updateHasEffect } from '../utils/effects'
|
|
|
|
|
|
|
|
export const languageLoadedEffect = StateEffect.define()
|
|
|
|
export const hasLanguageLoadedEffect = updateHasEffect(languageLoadedEffect)
|
|
|
|
|
|
|
|
const languageConf = new Compartment()
|
|
|
|
|
|
|
|
type Options = {
|
|
|
|
syntaxValidation: boolean
|
|
|
|
}
|
|
|
|
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* A state field that stores the metadata parsed from a project on the server.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
export const metadataState = StateField.define<Metadata | undefined>({
|
|
|
|
create: () => undefined,
|
|
|
|
update: (value, transaction) => {
|
|
|
|
for (const effect of transaction.effects) {
|
|
|
|
if (effect.is(setMetadataEffect)) {
|
|
|
|
return effect.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2024-01-12 05:09:29 -05:00
|
|
|
const languageCompartment = new Compartment()
|
|
|
|
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* The parser and support extensions for each supported language,
|
|
|
|
* which are loaded dynamically as needed.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
export const language = (
|
2024-01-12 05:09:29 -05:00
|
|
|
docName: string,
|
2023-04-13 04:21:25 -04:00
|
|
|
metadata: Metadata,
|
|
|
|
{ syntaxValidation }: Options
|
2024-01-12 05:09:29 -05:00
|
|
|
) => languageCompartment.of(buildExtension(docName, metadata, syntaxValidation))
|
|
|
|
|
|
|
|
const buildExtension = (
|
|
|
|
docName: string,
|
|
|
|
metadata: Metadata,
|
|
|
|
syntaxValidation: boolean
|
2023-04-13 04:21:25 -04:00
|
|
|
) => {
|
|
|
|
const languageDescription = LanguageDescription.matchFilename(
|
|
|
|
languages,
|
2024-01-12 05:09:29 -05:00
|
|
|
docName
|
2023-04-13 04:21:25 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
if (!languageDescription) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
return [
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* Default to four-space indentation and set the configuration in advance,
|
|
|
|
* to prevent a shift in line indentation markers when the LaTeX language loads.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
languageConf.of(indentUnit.of(' ')),
|
|
|
|
metadataState,
|
2023-06-08 04:35:51 -04:00
|
|
|
/**
|
|
|
|
* A view plugin which loads the appropriate language for the current file extension,
|
|
|
|
* then dispatches an effect so other extensions can update accordingly.
|
|
|
|
*/
|
2023-04-13 04:21:25 -04:00
|
|
|
ViewPlugin.define(view => {
|
|
|
|
// load the language asynchronously
|
|
|
|
languageDescription.load().then(support => {
|
|
|
|
view.dispatch({
|
|
|
|
effects: [
|
|
|
|
languageConf.reconfigure(support),
|
|
|
|
languageLoadedEffect.of(null),
|
|
|
|
],
|
|
|
|
})
|
|
|
|
// Wait until the previous effects have been processed
|
|
|
|
view.dispatch({
|
|
|
|
effects: [
|
|
|
|
setMetadataEffect.of(metadata),
|
|
|
|
setSyntaxValidationEffect.of(syntaxValidation),
|
|
|
|
],
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return {}
|
|
|
|
}),
|
|
|
|
metadataState,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2024-01-12 05:09:29 -05:00
|
|
|
export const setLanguage = (
|
|
|
|
docName: string,
|
|
|
|
metadata: Metadata,
|
|
|
|
syntaxValidation: boolean
|
|
|
|
) => {
|
|
|
|
return {
|
|
|
|
effects: languageCompartment.reconfigure(
|
|
|
|
buildExtension(docName, metadata, syntaxValidation)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 04:21:25 -04:00
|
|
|
export const setMetadataEffect = StateEffect.define<Metadata>()
|
|
|
|
|
|
|
|
export const setMetadata = (values: Metadata): TransactionSpec => {
|
|
|
|
return {
|
|
|
|
effects: setMetadataEffect.of(values),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const setSyntaxValidationEffect = StateEffect.define<boolean>()
|
|
|
|
|
|
|
|
export const setSyntaxValidation = (value: boolean): TransactionSpec => {
|
|
|
|
return {
|
|
|
|
effects: setSyntaxValidationEffect.of(value),
|
|
|
|
}
|
|
|
|
}
|