Skip to content

Commit 42aa116

Browse files
committed
feat (core): 切换编辑器为 CodeMirror
1 parent 3d8abca commit 42aa116

9 files changed

Lines changed: 66 additions & 870 deletions

File tree

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,23 @@
1010
"tauri": "tauri"
1111
},
1212
"dependencies": {
13+
"@babel/runtime": "^7.28.2",
14+
"@codemirror/lang-go": "^6.0.1",
15+
"@codemirror/lang-javascript": "^6.2.4",
16+
"@codemirror/lang-python": "^6.2.1",
17+
"@codemirror/state": "^6.5.2",
18+
"@codemirror/view": "^6.38.1",
1319
"@tauri-apps/api": "^2",
1420
"@tauri-apps/plugin-dialog": "^2.3.2",
1521
"@tauri-apps/plugin-opener": "^2",
1622
"@tauri-apps/plugin-shell": "^2.3.0",
23+
"@uiw/codemirror-themes-all": "^4.24.2",
1724
"@vueuse/core": "^13.6.0",
25+
"codemirror": "^6.0.2",
1826
"lodash-es": "^4.17.21",
1927
"lucide-vue-next": "^0.539.0",
2028
"vue": "^3.5.13",
29+
"vue-codemirror": "^6.1.1",
2130
"vue3-markdown-it": "^1.0.10"
2231
},
2332
"devDependencies": {

src/components/CodeEditor.vue

Lines changed: 57 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
11
<template>
2-
<div class="flex bg-white h-full relative overflow-hidden">
3-
<!-- 行号 -->
4-
<div ref="lineNumbersRef"
5-
class="bg-gray-50 text-gray-400 text-sm font-mono px-3 pb-4 select-none border-r border-gray-200 overflow-hidden flex-shrink-0 z-10"
6-
style="padding-top: 0;">
7-
<div v-for="(num, index) in lineNumbers" :key="index" class="h-6 leading-6 text-right">
8-
{{ num }}
9-
</div>
10-
</div>
11-
12-
<!-- 语法高亮容器 -->
13-
<div class="flex-1 relative overflow-hidden">
14-
<!-- 高亮显示层 -->
15-
<pre ref="highlightRef"
16-
class="absolute inset-0 px-4 pb-4 font-mono text-sm leading-6 bg-transparent pointer-events-none overflow-auto whitespace-pre-wrap z-0"
17-
style="margin: 0; border: 0; padding-top: 0; word-break: break-word; white-space: pre-wrap;"
18-
v-html="highlightedCode"></pre>
19-
20-
<!-- 代码输入框 -->
21-
<textarea ref="textareaRef"
22-
:value="modelValue"
23-
@input="handleInput"
24-
@keydown="handleKeyDown"
25-
@scroll="handleScroll"
26-
class="absolute inset-0 px-4 pb-4 font-mono text-sm leading-6 resize-none outline-none bg-transparent z-10 overflow-auto"
27-
style="color: transparent; caret-color: #374151; margin: 0; border: 0; padding-top: 0; word-break: break-word; white-space: pre-wrap;"
28-
placeholder="在此输入代码..."
29-
spellcheck="false">
30-
</textarea>
31-
</div>
2+
<div class="flex bg-white h-full relative">
3+
<Codemirror v-if="isReady"
4+
style="width: 100%; height: 100%"
5+
:model-value="modelValue"
6+
:extensions="extensions"
7+
@change="handleInput"/>
328
</div>
339
</template>
3410

3511
<script setup lang="ts">
36-
import { computed, nextTick, ref } from 'vue'
37-
import { highlightCode } from '../utils/highlighter'
12+
import { nextTick, onMounted, ref, watch } from 'vue'
13+
import { Codemirror } from 'vue-codemirror'
14+
import { python } from '@codemirror/lang-python'
15+
import { javascript } from '@codemirror/lang-javascript'
16+
import { go } from '@codemirror/lang-go'
17+
import { githubLight } from '@uiw/codemirror-themes-all'
3818
3919
const props = defineProps<{
4020
modelValue: string
@@ -45,46 +25,57 @@ const emit = defineEmits<{
4525
'update:modelValue': [value: string]
4626
}>()
4727
48-
const textareaRef = ref<HTMLTextAreaElement>()
49-
const lineNumbersRef = ref<HTMLElement>()
50-
const highlightRef = ref<HTMLPreElement>()
51-
52-
const lineNumbers = computed(() => {
53-
const lines = props.modelValue.split('\n')
54-
return lines.map((_, index) => String(index + 1))
55-
})
56-
57-
const highlightedCode = computed(() => {
58-
return highlightCode(props.modelValue, props.language || 'python3')
59-
})
60-
61-
const handleInput = (e: Event) => {
62-
const target = e.target as HTMLTextAreaElement
63-
emit('update:modelValue', target.value)
28+
const handleInput = (value: string) => {
29+
emit('update:modelValue', value)
6430
}
6531
66-
const handleKeyDown = async (e: KeyboardEvent) => {
67-
if (e.key === 'Tab') {
68-
e.preventDefault()
69-
const target = e.target as HTMLTextAreaElement
70-
const start = target.selectionStart
71-
const end = target.selectionEnd
72-
const newValue = props.modelValue.substring(0, start) + ' ' + props.modelValue.substring(end)
73-
emit('update:modelValue', newValue)
32+
const isReady = ref(false)
33+
const extensions = ref<[]>([])
7434
75-
await nextTick()
76-
target.selectionStart = target.selectionEnd = start + 4
35+
// 获取语言扩展
36+
const getLanguageExtension = (language: string): any | null => {
37+
switch (language) {
38+
case 'python2':
39+
case 'python3':
40+
return python()
41+
case 'nodejs':
42+
return javascript()
43+
case 'go':
44+
return go()
45+
default:
46+
return null
7747
}
7848
}
7949
80-
const handleScroll = (e: Event) => {
81-
const target = e.target as HTMLTextAreaElement
82-
if (lineNumbersRef.value) {
83-
lineNumbersRef.value.scrollTop = target.scrollTop
50+
// 更新扩展的函数
51+
const updateExtensions = async () => {
52+
const result = [githubLight]
53+
54+
if (props.language) {
55+
const langExtension = getLanguageExtension(props.language)
56+
if (langExtension) {
57+
result.push(langExtension)
58+
}
8459
}
85-
if (highlightRef.value) {
86-
highlightRef.value.scrollTop = target.scrollTop
87-
highlightRef.value.scrollLeft = target.scrollLeft
60+
61+
extensions.value = result as any
62+
63+
// 如果组件还没准备好,等待下一个 tick 后设置为准备好
64+
if (!isReady.value) {
65+
await nextTick()
66+
isReady.value = true
8867
}
8968
}
90-
</script>
69+
70+
// 监听语言变化
71+
watch(() => props.language, async () => {
72+
isReady.value = false
73+
await nextTick()
74+
await updateExtensions()
75+
}, { immediate: false })
76+
77+
// 组件挂载时初始化
78+
onMounted(async () => {
79+
await updateExtensions()
80+
})
81+
</script>

src/utils/highlighter/index.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/utils/highlighter/languages/go.ts

Lines changed: 0 additions & 176 deletions
This file was deleted.

0 commit comments

Comments
 (0)