重构:CodeMirror 架构优化
核心优化: - 新增统一导出避免多实例问题 - 语言加载器从动态改为静态导入 - 使用 Compartment 实现主题/语言动态切换 依赖清理: - 移除废弃的 @codemirror/highlight - 移除不再使用的 @codemirror/legacy-modes 组件优化: - CodeEditor 添加内容更新防抖 - 改进亮色主题样式 - 移除不必要的编辑器重建逻辑 构建配置: - 简化 Vite manualChunks 配置 - 优化依赖预加载列表 文档清理: - 删除过期的代码审查文档 - 更新版本号 0.3.0 → 0.3.2
This commit is contained in:
@@ -4,14 +4,30 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch, onBeforeUnmount, computed, nextTick } from 'vue'
|
||||
import { EditorView, lineNumbers, highlightActiveLineGutter, keymap } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { defaultKeymap, history } from '@codemirror/commands'
|
||||
import { bracketMatching } from '@codemirror/language'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import {
|
||||
EditorView, lineNumbers, highlightActiveLineGutter, keymap,
|
||||
EditorState, Compartment,
|
||||
defaultKeymap, history,
|
||||
bracketMatching, defaultHighlightStyle, syntaxHighlighting,
|
||||
oneDark
|
||||
} from '@/utils/codemirrorExports'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { loadLanguageExtension, getLanguageFromExtension } from '@/utils/codeMirrorLoader'
|
||||
|
||||
// ==================== 主题定义 ====================
|
||||
|
||||
// 亮色主题的基础样式
|
||||
const lightTheme = EditorView.theme({
|
||||
'&': { backgroundColor: '#ffffff' },
|
||||
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
|
||||
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
|
||||
'.cm-line': { caretColor: '#000' },
|
||||
'.cm-selection': { backgroundColor: '#d9d9d9' },
|
||||
'.cm-cursor': { borderLeftColor: '#000' }
|
||||
})
|
||||
|
||||
// ==================== Props & Emits ====================
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, required: true },
|
||||
fileExtension: { type: String, default: '' }
|
||||
@@ -19,69 +35,144 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
// ==================== 状态管理 ====================
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const editorContainer = ref(null)
|
||||
let view = null
|
||||
|
||||
const createExtensions = async () => {
|
||||
// 使用 Compartment 实现动态切换,避免重建编辑器
|
||||
const themeCompartment = new Compartment()
|
||||
const languageCompartment = new Compartment()
|
||||
|
||||
// ==================== 防抖处理 ====================
|
||||
|
||||
let emitTimeout = null
|
||||
const debouncedEmit = (value) => {
|
||||
if (emitTimeout) {
|
||||
clearTimeout(emitTimeout)
|
||||
}
|
||||
emitTimeout = setTimeout(() => {
|
||||
emit('update:modelValue', value)
|
||||
}, 150)
|
||||
}
|
||||
|
||||
// 获取当前主题扩展
|
||||
const getThemeExtension = () => {
|
||||
if (themeStore.isDark) {
|
||||
return [oneDark]
|
||||
} else {
|
||||
// 亮色主题:使用默认语法高亮样式
|
||||
return [
|
||||
EditorView.theme({
|
||||
'&': { backgroundColor: '#ffffff' },
|
||||
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
|
||||
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
|
||||
'.cm-line': { caretColor: '#000' },
|
||||
'.cm-selection': { backgroundColor: '#d9d9d9' },
|
||||
'.cm-cursor': { borderLeftColor: '#000' }
|
||||
}),
|
||||
syntaxHighlighting(defaultHighlightStyle)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 扩展配置 ====================
|
||||
|
||||
const createExtensions = () => {
|
||||
const extensions = [
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
history(),
|
||||
keymap.of(defaultKeymap),
|
||||
bracketMatching(),
|
||||
|
||||
// 内容更新监听(带防抖)
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
emit('update:modelValue', update.state.doc.toString())
|
||||
debouncedEmit(update.state.doc.toString())
|
||||
}
|
||||
}),
|
||||
|
||||
// 基础样式
|
||||
EditorView.theme({
|
||||
'&': { height: '100%', fontSize: '13px' },
|
||||
'.cm-scroller': { overflow: 'auto', fontFamily: 'Consolas, Monaco, Courier New, monospace' },
|
||||
'.cm-content': { padding: '8px', minHeight: '100%' },
|
||||
'.cm-line': { padding: '0 0' },
|
||||
'&.cm-focused': { outline: 'none' }
|
||||
})
|
||||
}),
|
||||
|
||||
// 使用 Compartment 支持动态切换主题
|
||||
themeCompartment.of(getThemeExtension()),
|
||||
|
||||
// 使用 Compartment 支持动态切换语言
|
||||
languageCompartment.of([])
|
||||
]
|
||||
|
||||
if (themeStore.isDark) {
|
||||
extensions.push(oneDark)
|
||||
}
|
||||
|
||||
const language = getLanguageFromExtension(props.fileExtension)
|
||||
if (language !== 'text') {
|
||||
const langExtension = await loadLanguageExtension(language)
|
||||
if (langExtension) {
|
||||
extensions.push(langExtension)
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
const createEditor = async (docContent = '') => {
|
||||
// ==================== 语言管理 ====================
|
||||
|
||||
const initLanguage = async () => {
|
||||
const language = getLanguageFromExtension(props.fileExtension)
|
||||
if (language === 'text') return
|
||||
|
||||
try {
|
||||
const langExtension = await loadLanguageExtension(language)
|
||||
if (langExtension && view) {
|
||||
view.dispatch({
|
||||
effects: languageCompartment.reconfigure(langExtension)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[CodeEditor] 加载语言包失败: ${language}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 编辑器创建 ====================
|
||||
|
||||
const createEditor = (docContent = '') => {
|
||||
if (!editorContainer.value) return
|
||||
|
||||
const extensions = await createExtensions()
|
||||
const state = EditorState.create({ doc: docContent, extensions })
|
||||
const state = EditorState.create({
|
||||
doc: docContent,
|
||||
extensions: createExtensions()
|
||||
})
|
||||
|
||||
view = new EditorView({ state, parent: editorContainer.value })
|
||||
|
||||
// 初始化语言
|
||||
initLanguage()
|
||||
}
|
||||
|
||||
const recreateEditor = async () => {
|
||||
if (!view) return
|
||||
const currentDoc = view.state.doc.toString()
|
||||
view.destroy()
|
||||
await createEditor(currentDoc)
|
||||
}
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
onMounted(async () => {
|
||||
await createEditor(props.modelValue || '')
|
||||
onMounted(() => {
|
||||
createEditor(props.modelValue || '')
|
||||
|
||||
// 确保主题正确应用(在下一 tick)
|
||||
nextTick(() => {
|
||||
if (view) {
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(getThemeExtension())
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (emitTimeout) {
|
||||
clearTimeout(emitTimeout)
|
||||
}
|
||||
view?.destroy()
|
||||
view = null
|
||||
})
|
||||
|
||||
// ==================== 监听器 ====================
|
||||
|
||||
// 监听外部内容变化
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (view && newValue !== view.state.doc.toString()) {
|
||||
view.dispatch({
|
||||
@@ -90,24 +181,39 @@ watch(() => props.modelValue, (newValue) => {
|
||||
}
|
||||
})
|
||||
|
||||
const isDark = computed(() => themeStore.isDark)
|
||||
watch([isDark, () => props.fileExtension], async () => {
|
||||
await nextTick()
|
||||
await recreateEditor()
|
||||
// 监听主题变化(使用 Compartment 重建,不丢失状态)
|
||||
watch(() => themeStore.isDark, () => {
|
||||
if (view) {
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(getThemeExtension())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 监听文件扩展名变化(重新加载语言)
|
||||
watch(() => props.fileExtension, () => {
|
||||
initLanguage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.codemirror-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-editor) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-scroller) {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.codemirror-editor :deep(.cm-content) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user