Skip to content

Commit 7207044

Browse files
authored
Merge pull request #26 from qianmoQ/dev-25.0.1
feat (core): 切换编辑器为 CodeMirror
2 parents 7d35065 + 37f48e9 commit 7207044

14 files changed

Lines changed: 614 additions & 877 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-tauri/src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,21 @@ use tauri::{AppHandle, Manager, command};
1010

1111
static CONFIG_MANAGER: Mutex<Option<ConfigManager>> = Mutex::new(None);
1212

13+
#[derive(Debug, Clone, Serialize, Deserialize)]
14+
pub struct EditorConfig {
15+
pub indent_with_tab: Option<bool>, // 是否使用 tab 缩进
16+
pub tab_size: Option<u32>, // tab 缩进, 空格数,默认为 2
17+
pub theme: Option<String>, // 编辑器主题
18+
}
19+
1320
#[derive(Debug, Clone, Serialize, Deserialize)]
1421
pub struct AppConfig {
1522
pub log_directory: Option<String>,
1623
pub auto_clear_logs: Option<bool>,
1724
pub keep_log_days: Option<u32>,
1825
pub theme: Option<String>,
1926
pub plugins: Option<Vec<PluginConfig>>,
27+
pub editor: Option<EditorConfig>,
2028
}
2129

2230
impl Default for AppConfig {
@@ -27,6 +35,11 @@ impl Default for AppConfig {
2735
keep_log_days: Some(30),
2836
theme: Some("system".to_string()),
2937
plugins: Some(vec![]),
38+
editor: Some(EditorConfig {
39+
indent_with_tab: Some(true),
40+
tab_size: Some(2),
41+
theme: Some("githubLight".to_string()),
42+
}),
3043
}
3144
}
3245
}
@@ -75,6 +88,16 @@ impl ConfigManager {
7588
// 合并插件配置(现有配置 + 默认配置中缺失的插件)
7689
config.plugins = Self::merge_plugins_config(config.plugins, app_handle);
7790

91+
// 检查并设置 editor 默认配置
92+
if config.editor.is_none() {
93+
config.editor = Some(EditorConfig {
94+
indent_with_tab: Some(true),
95+
tab_size: Some(2),
96+
theme: Some("githubLight".to_string()),
97+
});
98+
println!("读取配置 -> 添加默认 editor 配置");
99+
}
100+
78101
Ok(config)
79102
}
80103
Err(e) => {
@@ -170,6 +193,11 @@ impl ConfigManager {
170193
keep_log_days: Some(30),
171194
theme: Some("system".to_string()),
172195
plugins: Self::get_default_plugins_config(app_handle),
196+
editor: Some(EditorConfig {
197+
indent_with_tab: Some(true),
198+
tab_size: Some(2),
199+
theme: Some("githubLight".to_string()),
200+
}),
173201
}
174202
}
175203

src/components/CodeEditor.vue

Lines changed: 23 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,19 @@
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+
:indent-with-tab="editorConfig?.indent_with_tab"
8+
:tab-size="editorConfig?.tab_size"
9+
@change="handleInput"/>
3210
</div>
3311
</template>
3412

3513
<script setup lang="ts">
36-
import { computed, nextTick, ref } from 'vue'
37-
import { highlightCode } from '../utils/highlighter'
14+
import { onMounted } from 'vue'
15+
import { Codemirror } from 'vue-codemirror'
16+
import { useCodeMirrorEditor } from '../composables/useCodeMirrorEditor'
3817
3918
const props = defineProps<{
4019
modelValue: string
@@ -45,46 +24,18 @@ const emit = defineEmits<{
4524
'update:modelValue': [value: string]
4625
}>()
4726
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-
})
27+
const {
28+
isReady,
29+
extensions,
30+
editorConfig,
31+
initializeEditor
32+
} = useCodeMirrorEditor(props, emit)
6033
61-
const handleInput = (e: Event) => {
62-
const target = e.target as HTMLTextAreaElement
63-
emit('update:modelValue', target.value)
34+
const handleInput = (value: string) => {
35+
emit('update:modelValue', value)
6436
}
6537
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)
74-
75-
await nextTick()
76-
target.selectionStart = target.selectionEnd = start + 4
77-
}
78-
}
79-
80-
const handleScroll = (e: Event) => {
81-
const target = e.target as HTMLTextAreaElement
82-
if (lineNumbersRef.value) {
83-
lineNumbersRef.value.scrollTop = target.scrollTop
84-
}
85-
if (highlightRef.value) {
86-
highlightRef.value.scrollTop = target.scrollTop
87-
highlightRef.value.scrollLeft = target.scrollLeft
88-
}
89-
}
90-
</script>
38+
onMounted(async () => {
39+
await initializeEditor()
40+
})
41+
</script>

src/components/Settings.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
<General/>
1212
</template>
1313

14+
<!-- 编辑器配置 -->
15+
<template #editor>
16+
<Editor v-if="activeTab === 'editor'"/>
17+
</template>
18+
1419
<!-- 语言配置 -->
1520
<template #language>
1621
<Language v-if="activeTab === 'language'"/>
@@ -21,16 +26,18 @@
2126

2227
<script setup lang="ts">
2328
import { nextTick, onMounted, ref } from 'vue'
24-
import { BracesIcon, ShieldIcon } from 'lucide-vue-next'
29+
import { BracesIcon, CodeIcon, ShieldIcon } from 'lucide-vue-next'
2530
import Modal from '../ui/Modal.vue'
2631
import Tabs from '../ui/Tabs.vue'
2732
import General from './setting/General.vue'
2833
import Language from './setting/Language.vue'
34+
import Editor from './setting/Editor.vue'
2935
3036
const isVisible = ref(false)
3137
const activeTab = ref('general')
3238
const tabsData = [
3339
{ key: 'general', label: '通用', icon: ShieldIcon },
40+
{ key: 'editor', label: '编辑器', icon: CodeIcon },
3441
{ key: 'language', label: '语言', icon: BracesIcon }
3542
]
3643

src/components/setting/Editor.vue

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<template>
2+
<div v-if="editorConfig" class="space-y-2">
3+
<div>
4+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
5+
是否使用 tab 缩进
6+
</label>
7+
<div class="flex gap-2">
8+
<input v-model="editorConfig.indent_with_tab"
9+
type="checkbox"
10+
placeholder="超时时间(秒),默认 30 秒"
11+
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-transparent text-sm"/>
12+
</div>
13+
</div>
14+
15+
<div>
16+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
17+
缩进空格数
18+
</label>
19+
<div class="flex gap-2">
20+
<input v-model="editorConfig.tab_size"
21+
type="number"
22+
:disabled="!editorConfig.indent_with_tab"
23+
placeholder="缩进空格数,默认 2 秒"
24+
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-transparent text-sm"
25+
:class="[!editorConfig.indent_with_tab ? 'opacity-50 cursor-not-allowed' : '']"/>
26+
</div>
27+
</div>
28+
29+
<div>
30+
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
31+
编辑器主题
32+
</label>
33+
<div class="flex gap-2">
34+
<Select v-model="editorConfig.theme"
35+
class="w-1/4"
36+
placeholder="选择编辑器主题"
37+
:options="themeOptions"/>
38+
</div>
39+
</div>
40+
</div>
41+
</template>
42+
43+
<script setup lang="ts">
44+
import { onMounted } from 'vue'
45+
import Select from '../../ui/Select.vue'
46+
import { useEditorConfig } from '../../composables/useEditorConfig'
47+
48+
const emit = defineEmits<{
49+
'settings-changed': [config: any]
50+
'error': [message: string]
51+
}>()
52+
53+
const {
54+
editorConfig,
55+
themeOptions,
56+
loadConfig
57+
} = useEditorConfig(emit)
58+
59+
onMounted(async () => {
60+
await loadConfig()
61+
})
62+
</script>

0 commit comments

Comments
 (0)