367367 :file-name =" currentFileName "
368368 @close =" showDiff = false " />
369369
370+ <!-- 与剪贴板比较 -->
371+ <DiffView v-if =" clipboardDiff "
372+ :original =" clipboardDiff .original "
373+ :modified =" code "
374+ :file-name =" currentFileName "
375+ :title =" t (' diff.clipboardTitle' )"
376+ @close =" clipboardDiff = null " />
377+
370378 <!-- 应用 AI 代码前的差异预览 -->
371379 <DiffView v-if =" applyPreview "
372380 :original =" code "
@@ -540,6 +548,7 @@ import {useGitStatus} from './composables/useGitStatus'
540548import {useSessionTabs } from ' ./composables/useSessionTabs'
541549import {useEditorContextMenu } from ' ./composables/useEditorContextMenu'
542550import {useRunConfig } from ' ./composables/useRunConfig'
551+ import {useGlobalShortcuts } from ' ./composables/useGlobalShortcuts'
543552import EditorTabs from ' ./components/EditorTabs.vue'
544553import IndentControl from ' ./components/IndentControl.vue'
545554import Sidebar from ' ./components/Sidebar.vue'
@@ -1108,6 +1117,25 @@ const editorView = shallowRef<any>(null)
11081117// ===== 文本变换命令(排序行/大小写/去重/去行尾空白)=====
11091118const {transformSelectionOrLine, sortLines, removeDuplicateLines, trimTrailingWhitespace} = useTextCommands (editorView )
11101119
1120+ // 复制为 Markdown 代码块(选区或全文,带语言围栏)
1121+ const copyAsMarkdown = async () => {
1122+ const view = editorView .value
1123+ if (! view ) {
1124+ return
1125+ }
1126+ const sel = view .state .selection .main
1127+ const text = sel .empty ? view .state .doc .toString () : view .state .doc .sliceString (sel .from , sel .to )
1128+ const lang = (currentLanguage .value || ' ' ).toLowerCase ().replace (/ \d + $ / , ' ' )
1129+ const fence = ' ```' + lang + ' \n ' + text .replace (/ \n $ / , ' ' ) + ' \n ```'
1130+ try {
1131+ await navigator .clipboard .writeText (fence )
1132+ toast .success (t (' app.copiedMarkdown' ))
1133+ }
1134+ catch (error ) {
1135+ toast .error (t (' app.copyFailed' ) + error )
1136+ }
1137+ }
1138+
11111139// AI 自然语言生成 / 选区改写
11121140const showGenerate = ref (false )
11131141const generateSelection = ref (' ' )
@@ -1542,6 +1570,21 @@ const openDiff = () => {
15421570 }
15431571 showDiff .value = true
15441572}
1573+ // 与剪贴板内容比较(剪贴板为原始,当前编辑内容为修改)
1574+ const clipboardDiff = ref <{ original: string } | null >(null )
1575+ const compareWithClipboard = async () => {
1576+ try {
1577+ const text = await navigator .clipboard .readText ()
1578+ if (! text ) {
1579+ toast .info (t (' app.clipboardEmpty' ))
1580+ return
1581+ }
1582+ clipboardDiff .value = {original: text }
1583+ }
1584+ catch (error ) {
1585+ toast .error (t (' app.clipboardReadFailed' ) + error )
1586+ }
1587+ }
15451588const togglePreview = () => {
15461589 showPreview .value = ! showPreview .value
15471590}
@@ -2096,7 +2139,7 @@ const isOverlayOpen = () =>
20962139 || showHistory .value || showViewer .value || showRunPrompt .value
20972140 || showQuickOpen .value || showGenerate .value || showSearch .value
20982141 || showCommandPalette .value || showDiff .value || showGoToLine .value || showOutline .value || showSnippets .value
2099- || applyPreview .value != null
2142+ || applyPreview .value != null || clipboardDiff . value != null
21002143
21012144// 全局快捷键(绑定可在设置中自定义)
21022145const {matchAction : matchShortcut, reload : reloadShortcuts, getBinding, formatCombo} = useShortcuts ()
@@ -2171,6 +2214,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
21712214 {id: ' formatWithAi' , label: t (' command.formatWithAi' ), icon: Sparkles , run : () => formatWithAi ()},
21722215 {id: ' history' , label: t (' command.history' ), icon: History , run : () => { showHistory .value = true }},
21732216 {id: ' diff' , label: t (' command.diff' ), icon: GitCompare , run : () => openDiff ()},
2217+ {id: ' compareClipboard' , label: t (' command.compareClipboard' ), icon: GitCompare , run : () => compareWithClipboard ()},
21742218 {id: ' preview' , label: t (' command.preview' ), icon: Eye , run : () => togglePreview ()},
21752219 {id: ' git' , label: t (' command.git' ), icon: GitBranch , run : () => openGit ()},
21762220 {id: ' tasks' , label: t (' command.tasks' ), icon: ListChecks , run : () => openTasks ()},
@@ -2187,6 +2231,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
21872231 {id: ' toLowerCase' , label: t (' command.toLowerCase' ), group: t (' command.groupText' ), icon: CaseLower , run : () => transformSelectionOrLine (s => s .toLowerCase ())},
21882232 {id: ' removeDuplicateLines' , label: t (' command.removeDuplicateLines' ), group: t (' command.groupText' ), icon: ListChecks , run : () => removeDuplicateLines ()},
21892233 {id: ' trimTrailingWhitespace' , label: t (' command.trimTrailingWhitespace' ), group: t (' command.groupText' ), icon: Eraser , run : () => trimTrailingWhitespace ()},
2234+ {id: ' copyAsMarkdown' , label: t (' command.copyAsMarkdown' ), group: t (' command.groupText' ), icon: Code2 , run : () => copyAsMarkdown ()},
21902235 {id: ' toggleAutoReveal' , label: t (' command.toggleAutoReveal' ), icon: FolderOpen , run : () => toggleAutoReveal ()},
21912236 {id: ' toggleSidebar' , label: t (' command.toggleSidebar' ), icon: PanelLeft , hint: hintOf (' toggleSidebar' ), run : () => toggleSidebar ()},
21922237 {id: ' toggleZen' , label: t (' command.toggleZen' ), icon: Minimize2 , run : () => toggleZen ()},
@@ -2200,18 +2245,8 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
22002245 {id: ' settings' , label: t (' command.settings' ), icon: SettingsIcon , run : () => { showSettings .value = true }}
22012246])
22022247
2203- const onGlobalKeydown = (e : KeyboardEvent ) => {
2204- if (isOverlayOpen ()) {
2205- return
2206- }
2207- const action = matchShortcut (e )
2208- if (action && shortcutDispatch [action ]) {
2209- // 捕获阶段拦截:阻止事件到达编辑器(避免 Cmd+Enter 等被插入换行)
2210- e .preventDefault ()
2211- e .stopPropagation ()
2212- shortcutDispatch [action ]()
2213- }
2214- }
2248+ // 全局快捷键(捕获拦截 + 派发)抽离到 useGlobalShortcuts
2249+ useGlobalShortcuts (matchShortcut , shortcutDispatch , isOverlayOpen )
22152250
22162251const {init : initTheme, setTheme : setAppTheme} = useTheme ()
22172252
@@ -2236,7 +2271,6 @@ onMounted(async () => {
22362271 // 恢复上次打开的文件标签
22372272 await restoreSession ()
22382273
2239- window .addEventListener (' keydown' , onGlobalKeydown , true )
22402274 window .addEventListener (' lsp:open-location' , onLspOpenLocation )
22412275 window .addEventListener (' lsp:code-actions' , onLspCodeActions )
22422276
@@ -2246,7 +2280,6 @@ onMounted(async () => {
22462280
22472281onUnmounted (() => {
22482282 cleanupEventListeners ()
2249- window .removeEventListener (' keydown' , onGlobalKeydown , true )
22502283 window .removeEventListener (' lsp:open-location' , onLspOpenLocation )
22512284 window .removeEventListener (' lsp:code-actions' , onLspCodeActions )
22522285})
0 commit comments