290 lines
6.5 KiB
Vue
290 lines
6.5 KiB
Vue
<template>
|
|
<div class="codemirror-editor">
|
|
<div ref="editorContainer" class="editor-container"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { EditorView, keymap, highlightActiveLine, highlightActiveLineGutter, lineNumbers, scrollPastEnd } from '@codemirror/view'
|
|
import { EditorState, Compartment } from '@codemirror/state'
|
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
|
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'
|
|
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'
|
|
import { foldGutter, indentOnInput, indentUnit, bracketMatching } from '@codemirror/language'
|
|
import { highlightSelectionMatches as highlightSelection } from '@codemirror/search'
|
|
import { javascript } from '@codemirror/lang-javascript'
|
|
import { php } from '@codemirror/lang-php'
|
|
import { oneDark } from '@codemirror/theme-one-dark'
|
|
import { env } from '../../lang-env.js'
|
|
|
|
export default {
|
|
name: 'CodeEditor',
|
|
props: {
|
|
modelValue: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
language: {
|
|
type: String,
|
|
default: 'env' // 'env', 'javascript', 'php'
|
|
},
|
|
theme: {
|
|
type: String,
|
|
default: 'light' // 'light', 'dark'
|
|
},
|
|
readonly: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
},
|
|
emits: ['update:modelValue', 'change'],
|
|
data() {
|
|
return {
|
|
editor: null,
|
|
languageCompartment: new Compartment(),
|
|
themeCompartment: new Compartment()
|
|
}
|
|
},
|
|
mounted() {
|
|
this.initEditor();
|
|
},
|
|
watch: {
|
|
modelValue(newValue) {
|
|
if (this.editor && newValue !== this.editor.state.doc.toString()) {
|
|
this.editor.dispatch({
|
|
changes: {
|
|
from: 0,
|
|
to: this.editor.state.doc.length,
|
|
insert: newValue || ''
|
|
}
|
|
});
|
|
}
|
|
},
|
|
language() {
|
|
this.updateLanguage();
|
|
},
|
|
theme() {
|
|
this.updateTheme();
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
if (this.editor) {
|
|
this.editor.destroy();
|
|
}
|
|
},
|
|
methods: {
|
|
getLanguageExtension() {
|
|
switch (this.language) {
|
|
case 'javascript':
|
|
return javascript();
|
|
case 'php':
|
|
return php();
|
|
case 'env':
|
|
return env();
|
|
default:
|
|
return [];
|
|
}
|
|
},
|
|
|
|
getThemeExtension() {
|
|
return this.theme === 'dark' ? oneDark : [];
|
|
},
|
|
|
|
initEditor() {
|
|
const extensions = [
|
|
lineNumbers(),
|
|
highlightActiveLineGutter(),
|
|
highlightActiveLine(),
|
|
foldGutter(),
|
|
indentOnInput(),
|
|
indentUnit.of(' '),
|
|
bracketMatching(),
|
|
closeBrackets(),
|
|
autocompletion(),
|
|
highlightSelectionMatches(),
|
|
history(),
|
|
scrollPastEnd(),
|
|
keymap.of([
|
|
...closeBracketsKeymap,
|
|
...defaultKeymap,
|
|
...searchKeymap,
|
|
...historyKeymap,
|
|
...completionKeymap,
|
|
]),
|
|
this.languageCompartment.of(this.getLanguageExtension()),
|
|
this.themeCompartment.of(this.getThemeExtension()),
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged) {
|
|
const content = update.state.doc.toString();
|
|
this.$emit('update:modelValue', content);
|
|
this.$emit('change', content);
|
|
}
|
|
}),
|
|
// 确保编辑器可以滚动
|
|
EditorView.theme({
|
|
'&': {
|
|
height: '100%'
|
|
},
|
|
'.cm-scroller': {
|
|
fontFamily: 'inherit',
|
|
overflow: 'auto'
|
|
},
|
|
'.cm-content': {
|
|
padding: '12px',
|
|
minHeight: '100%'
|
|
},
|
|
'.cm-editor': {
|
|
height: '100%'
|
|
},
|
|
'.cm-focused': {
|
|
outline: 'none'
|
|
}
|
|
})
|
|
];
|
|
|
|
// 添加只读模式
|
|
if (this.readonly) {
|
|
extensions.push(EditorView.editable.of(false));
|
|
}
|
|
|
|
const state = EditorState.create({
|
|
doc: this.modelValue || '',
|
|
extensions
|
|
});
|
|
|
|
this.editor = new EditorView({
|
|
state,
|
|
parent: this.$refs.editorContainer
|
|
});
|
|
},
|
|
|
|
updateLanguage() {
|
|
if (!this.editor) return;
|
|
|
|
this.editor.dispatch({
|
|
effects: this.languageCompartment.reconfigure(this.getLanguageExtension())
|
|
});
|
|
},
|
|
|
|
updateTheme() {
|
|
if (!this.editor) return;
|
|
|
|
this.editor.dispatch({
|
|
effects: this.themeCompartment.reconfigure(this.getThemeExtension())
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.codemirror-editor {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 8px;
|
|
border: 1px solid #e5e7eb;
|
|
background: #ffffff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.editor-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* CodeMirror 自定义样式 */
|
|
.codemirror-editor :deep(.cm-editor) {
|
|
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Consolas', monospace;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
height: 100%;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-scroller) {
|
|
overflow: auto !important;
|
|
max-height: 100%;
|
|
}
|
|
|
|
/* 基本语法高亮样式 */
|
|
.codemirror-editor :deep(.cm-variableName) {
|
|
color: #0969da;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-string) {
|
|
color: #0a3069;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-number) {
|
|
color: #0550ae;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-keyword) {
|
|
color: #8250df;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-operator) {
|
|
color: #cf222e;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-comment) {
|
|
color: #6a737d;
|
|
font-style: italic;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-content) {
|
|
padding: 12px;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-focused) {
|
|
outline: none;
|
|
}
|
|
|
|
/* 自定义滚动条样式 */
|
|
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-thumb {
|
|
background: #c1c1c1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.codemirror-editor :deep(.cm-scroller)::-webkit-scrollbar-thumb:hover {
|
|
background: #a8a8a8;
|
|
}
|
|
|
|
/* 深色主题样式 */
|
|
.codemirror-editor.dark :deep(.cm-editor) {
|
|
background: #1e1e1e;
|
|
color: #d4d4d4;
|
|
}
|
|
|
|
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-track {
|
|
background: #2d2d2d;
|
|
}
|
|
|
|
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-thumb {
|
|
background: #555;
|
|
}
|
|
|
|
.codemirror-editor.dark :deep(.cm-scroller)::-webkit-scrollbar-thumb:hover {
|
|
background: #777;
|
|
}
|
|
</style>
|