Files
halo/ui/src/components/codemirror/Codemirror.vue

141 lines
3.0 KiB
Vue

<script lang="ts" setup>
import type { LanguageSupport } from "@codemirror/language";
import { Compartment, EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { basicSetup } from "codemirror";
import { onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
import { presetLanguages, type CodemirrorProps } from "./supports";
const props = withDefaults(defineProps<CodemirrorProps>(), {
modelValue: "",
height: "auto",
language: "yaml",
extensions: () => [],
});
const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
(e: "change", value: string): void;
}>();
const wrapper = shallowRef<HTMLDivElement>();
const cmState = shallowRef<EditorState>();
const cmView = shallowRef<EditorView>();
const themeCompartment = new Compartment();
const languageCompartment = new Compartment();
const createCustomTheme = (height: string) => {
return EditorView.theme({
"&": {
height,
width: "100%",
},
});
};
const createCmEditor = () => {
let extensions = [
basicSetup,
EditorView.lineWrapping,
themeCompartment.of(createCustomTheme(props.height)),
languageCompartment.of([]),
EditorView.updateListener.of((viewUpdate) => {
if (viewUpdate.docChanged) {
const doc = viewUpdate.state.doc.toString();
emit("update:modelValue", doc);
emit("change", doc);
}
}),
];
if (props.extensions) {
extensions = extensions.concat(props.extensions);
}
cmState.value = EditorState.create({
doc: props.modelValue,
extensions,
});
cmView.value = new EditorView({
state: cmState.value,
parent: wrapper.value,
});
loadLanguage();
};
async function loadLanguage() {
let language: LanguageSupport;
if (!props.language) {
return;
}
if (typeof props.language === "string") {
const loader = presetLanguages[props.language];
if (!loader) {
throw new Error(`Language ${props.language} not found`);
}
language = await loader();
} else {
language = props.language;
}
cmView.value?.dispatch({
effects: languageCompartment.reconfigure(language),
});
}
onMounted(() => {
createCmEditor();
});
// Update the codemirror editor doc when the model value changes.
watch(
() => props.modelValue,
(newValue) => {
if (!cmView.value) {
return;
}
if (newValue !== cmView.value.state.doc.toString()) {
cmView.value.dispatch({
changes: {
from: 0,
to: cmView.value.state.doc.length,
insert: newValue,
},
});
}
}
);
watch(
() => props.height,
(newHeight) => {
if (cmView.value) {
cmView.value.dispatch({
effects: themeCompartment.reconfigure(createCustomTheme(newHeight)),
});
}
}
);
watch(
() => props.language,
() => {
loadLanguage();
}
);
// Destroy codemirror editor when component unmounts
onBeforeUnmount(() => {
cmView.value?.destroy();
});
</script>
<template>
<div ref="wrapper" class="codemirror-wrapper contents"></div>
</template>