Skip to content

Commit 699b977

Browse files
authored
Merge pull request #117 from qianmoQ/dev-26.4.0
Fix CI workflow for docs deployment and enhance editor features
2 parents ea21d19 + 754b22e commit 699b977

9 files changed

Lines changed: 213 additions & 12 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: Deploy docs to GitHub Pages
22

33
on:
44
push:
5+
# 限定分支触发,排除标签:发布打的 tag 会触发部署,但 github-pages
6+
# 环境保护规则不允许从 tag 部署,导致 deploy 被拒。
7+
branches: [ '**' ]
58
paths: [ 'docs/**', '.github/workflows/docs.yml' ]
69
workflow_dispatch:
710

src/App.vue

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@
399399
<DebugPanel/>
400400

401401
<!-- AI 代码操作(解释/重构/生成测试) -->
402-
<AiCodeAction v-if="aiCodeCtx" :language="currentLanguage" :code="aiCodeCtx.code" :action="aiCodeCtx.action"
402+
<AiCodeAction v-if="aiCodeCtx" :language="currentLanguage" :code="aiCodeCtx.code" :action="aiCodeCtx.action" :diagnostics="aiCodeCtx.diagnostics"
403403
@replace="onAiReplace" @insert="onAiInsert" @close="aiCodeCtx = null"/>
404404

405405
<!-- .gitignore 模板 -->
@@ -463,6 +463,7 @@
463463
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="aiCodeAction('explain')">{{ t('aiCode.title.explain') }}</button>
464464
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="aiCodeAction('refactor')">{{ t('aiCode.title.refactor') }}</button>
465465
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="aiCodeAction('test')">{{ t('aiCode.title.test') }}</button>
466+
<button v-if="canBlame || editorCtx.lsp" class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="aiFixDiagnostics">{{ t('aiCode.title.fix') }}</button>
466467
<div class="border-t border-gray-100 dark:border-gray-700 my-1"></div>
467468
<button class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="sendToTerminal">
468469
{{ t('app.sendToTerminal') }}
@@ -510,7 +511,7 @@ import {debounce} from 'lodash-es'
510511
import {formatDocument, formatSelection, renameSymbol} from 'codemirror-languageserver'
511512
import {runGotoDefinition, lspSupportsLanguage, triggerCodeActions, applyCodeAction, formatDocumentAsync} from './editor/lspExtension'
512513
import {dapSupportsLanguage} from './debug/dapClient'
513-
import {ArrowDownAZ, ArrowUpAZ, CaseLower, CaseUpper, ChevronRight, Code2, CornerDownRight, Eraser, Eye, FolderOpen, GitBranch, GitCompare, History, ListChecks, ListTree, Maximize2, Minimize2, Monitor, Moon, PanelBottom, PanelLeft, PanelRight, Play, Plus, Save, Search, Settings as SettingsIcon, Sparkles, Sun, Terminal as TerminalIcon, WrapText, X} from 'lucide-vue-next'
514+
import {ArrowDownAZ, ArrowUpAZ, Bookmark, CaseLower, CaseUpper, ChevronRight, Code2, CornerDownRight, Eraser, Eye, FoldVertical, FolderOpen, GitBranch, GitCompare, History, ListChecks, ListTree, Maximize2, Minimize2, Monitor, Moon, PanelBottom, PanelLeft, PanelRight, Play, Plus, Save, Search, Settings as SettingsIcon, Sparkles, Sun, Terminal as TerminalIcon, UnfoldVertical, WrapText, X} from 'lucide-vue-next'
514515
import {ExecutionResult, LayoutMode, SplitDirection} from './types/app.ts'
515516
import AppHeader from './components/AppHeader.vue'
516517
import CodeEditor from './components/CodeEditor.vue'
@@ -541,6 +542,9 @@ import {useFileManager} from './composables/useFileManager'
541542
import {useLanguageRegistry} from './composables/useLanguageRegistry'
542543
import {useWorkspace} from './composables/useWorkspace'
543544
import {useTextCommands} from './composables/useTextCommands'
545+
import {useBookmarks} from './composables/useBookmarks'
546+
import {foldAll, unfoldAll, matchBrackets} from '@codemirror/language'
547+
import {diagnostics} from './editor/lspDiagnostics'
544548
import {useGitPermalink} from './composables/useGitPermalink'
545549
import {useRevealInTree} from './composables/useRevealInTree'
546550
import {useWorkspaceRoots} from './composables/useWorkspaceRoots'
@@ -1117,6 +1121,9 @@ const editorView = shallowRef<any>(null)
11171121
// ===== 文本变换命令(排序行/大小写/去重/去行尾空白)=====
11181122
const {transformSelectionOrLine, sortLines, removeDuplicateLines, trimTrailingWhitespace} = useTextCommands(editorView)
11191123
1124+
// ===== 行书签(切换/上一处/下一处/清空,按文件记忆)=====
1125+
const {toggleBookmark, nextBookmark, prevBookmark, clearBookmarks} = useBookmarks(editorView, currentFilePath)
1126+
11201127
// 复制为 Markdown 代码块(选区或全文,带语言围栏)
11211128
const copyAsMarkdown = async () => {
11221129
const view = editorView.value
@@ -1168,6 +1175,23 @@ const convertIndentation = (toTabs: boolean) => {
11681175
toast.success(t('app.indentConverted'))
11691176
}
11701177
1178+
// 转到匹配括号:取光标前后的括号,跳到其配对处
1179+
const goToMatchingBracket = () => {
1180+
const view = editorView.value
1181+
if (!view) {
1182+
return
1183+
}
1184+
const pos = view.state.selection.main.head
1185+
const m = matchBrackets(view.state, pos, -1) || matchBrackets(view.state, pos, 1)
1186+
if (m && m.matched && m.end) {
1187+
view.dispatch({selection: {anchor: m.end.from}, scrollIntoView: true})
1188+
view.focus()
1189+
}
1190+
else {
1191+
toast.info(t('app.noMatchingBracket'))
1192+
}
1193+
}
1194+
11711195
// AI 自然语言生成 / 选区改写
11721196
const showGenerate = ref(false)
11731197
const generateSelection = ref('')
@@ -1398,7 +1422,21 @@ const runTests = async () => {
13981422
}
13991423
14001424
// C2:对选区(无选区则整篇)执行 AI 操作:解释 / 重构 / 生成测试
1401-
const aiCodeCtx = ref<{action: 'explain' | 'refactor' | 'test'; code: string; from: number; to: number} | null>(null)
1425+
const aiCodeCtx = ref<{action: 'explain' | 'refactor' | 'test' | 'fix'; code: string; from: number; to: number; diagnostics?: string} | null>(null)
1426+
// AI 修复诊断:把当前文件的 LSP 诊断交给 AI 修复整篇
1427+
const aiFixDiagnostics = () => {
1428+
closeEditorCtx()
1429+
const view = editorView.value
1430+
if (!view) {
1431+
return
1432+
}
1433+
if (!diagnostics.value.length) {
1434+
toast.info(t('app.noDiagnostics'))
1435+
return
1436+
}
1437+
const diagText = diagnostics.value.map(d => `[${d.severity}] L${d.line}:${d.col} ${d.message}`).join('\n')
1438+
aiCodeCtx.value = {action: 'fix', code: view.state.doc.toString(), from: 0, to: view.state.doc.length, diagnostics: diagText}
1439+
}
14021440
const aiCodeAction = (action: 'explain' | 'refactor' | 'test') => {
14031441
closeEditorCtx()
14041442
const view = editorView.value
@@ -2154,7 +2192,8 @@ const shortcutDispatch: Record<string, () => void> = {
21542192
reopenClosed: () => handleReopenClosed(),
21552193
toggleSidebar: () => toggleSidebar(),
21562194
toggleTerminal: () => toggleTerminal(),
2157-
toggleWordWrap: () => toggleWordWrap()
2195+
toggleWordWrap: () => toggleWordWrap(),
2196+
toggleBookmark: () => toggleBookmark()
21582197
}
21592198
21602199
// 切换自动换行(即时生效并随编辑器配置持久化)
@@ -2205,6 +2244,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
22052244
{id: 'explainCode', label: t('command.explainCode'), icon: Sparkles, run: () => explainCode()},
22062245
{id: 'generateTests', label: t('command.generateTests'), icon: Sparkles, run: () => generateTests()},
22072246
{id: 'formatWithAi', label: t('command.formatWithAi'), icon: Sparkles, run: () => formatWithAi()},
2247+
{id: 'aiFixDiagnostics', label: t('command.aiFixDiagnostics'), icon: Sparkles, run: () => aiFixDiagnostics()},
22082248
{id: 'history', label: t('command.history'), icon: History, run: () => { showHistory.value = true }},
22092249
{id: 'diff', label: t('command.diff'), icon: GitCompare, run: () => openDiff()},
22102250
{id: 'compareClipboard', label: t('command.compareClipboard'), icon: GitCompare, run: () => compareWithClipboard()},
@@ -2227,6 +2267,13 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
22272267
{id: 'copyAsMarkdown', label: t('command.copyAsMarkdown'), group: t('command.groupText'), icon: Code2, run: () => copyAsMarkdown()},
22282268
{id: 'indentToSpaces', label: t('command.indentToSpaces'), group: t('command.groupText'), icon: Eraser, run: () => convertIndentation(false)},
22292269
{id: 'indentToTabs', label: t('command.indentToTabs'), group: t('command.groupText'), icon: Eraser, run: () => convertIndentation(true)},
2270+
{id: 'foldAll', label: t('command.foldAll'), group: t('command.groupCode'), icon: FoldVertical, run: () => { if (editorView.value) foldAll(editorView.value) }},
2271+
{id: 'unfoldAll', label: t('command.unfoldAll'), group: t('command.groupCode'), icon: UnfoldVertical, run: () => { if (editorView.value) unfoldAll(editorView.value) }},
2272+
{id: 'goToMatchingBracket', label: t('command.goToMatchingBracket'), group: t('command.groupCode'), icon: Code2, run: () => goToMatchingBracket()},
2273+
{id: 'toggleBookmark', label: t('command.toggleBookmark'), group: t('command.groupBookmark'), icon: Bookmark, hint: hintOf('toggleBookmark'), run: () => toggleBookmark()},
2274+
{id: 'nextBookmark', label: t('command.nextBookmark'), group: t('command.groupBookmark'), icon: Bookmark, run: () => nextBookmark()},
2275+
{id: 'prevBookmark', label: t('command.prevBookmark'), group: t('command.groupBookmark'), icon: Bookmark, run: () => prevBookmark()},
2276+
{id: 'clearBookmarks', label: t('command.clearBookmarks'), group: t('command.groupBookmark'), icon: Bookmark, run: () => clearBookmarks()},
22302277
{id: 'toggleAutoReveal', label: t('command.toggleAutoReveal'), icon: FolderOpen, run: () => toggleAutoReveal()},
22312278
{id: 'toggleSidebar', label: t('command.toggleSidebar'), icon: PanelLeft, hint: hintOf('toggleSidebar'), run: () => toggleSidebar()},
22322279
{id: 'toggleZen', label: t('command.toggleZen'), icon: Minimize2, run: () => toggleZen()},

src/components/AiCodeAction.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<div class="flex items-center justify-end gap-2 px-4 py-2.5 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
2323
<button class="text-xs px-3 py-1.5 rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="emit('close')">{{ t('aiCode.close') }}</button>
2424
<button v-if="!loading && result" class="text-xs px-3 py-1.5 rounded text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer" @click="copy">{{ t('aiCode.copy') }}</button>
25-
<button v-if="!loading && result && action === 'refactor'" class="text-xs px-3 py-1.5 rounded bg-blue-500 text-white hover:bg-blue-600 cursor-pointer" @click="apply('replace')">{{ t('aiCode.replace') }}</button>
25+
<button v-if="!loading && result && (action === 'refactor' || action === 'fix')" class="text-xs px-3 py-1.5 rounded bg-blue-500 text-white hover:bg-blue-600 cursor-pointer" @click="apply('replace')">{{ t('aiCode.replace') }}</button>
2626
<button v-if="!loading && result && action === 'test'" class="text-xs px-3 py-1.5 rounded bg-blue-500 text-white hover:bg-blue-600 cursor-pointer" @click="apply('insert')">{{ t('aiCode.insert') }}</button>
2727
</div>
2828
</div>
@@ -37,7 +37,7 @@ import {useI18n} from 'vue-i18n'
3737
import {useAiConfig} from '../composables/useAiConfig'
3838
import {useToast} from '../plugins/toast'
3939
40-
const props = defineProps<{ language: string; code: string; action: 'explain' | 'refactor' | 'test' }>()
40+
const props = defineProps<{ language: string; code: string; action: 'explain' | 'refactor' | 'test' | 'fix'; diagnostics?: string }>()
4141
const emit = defineEmits<{ replace: [code: string]; insert: [code: string]; close: [] }>()
4242
4343
const toast = useToast()
@@ -62,9 +62,17 @@ const systemFor = (): string => {
6262
return `你是代码助手。重构给定的 ${lang} 代码以提升可读性与质量,保持行为不变。只输出重构后的完整代码,不要解释,不要使用 Markdown 代码块标记。`
6363
case 'test':
6464
return `你是测试工程师。为给定的 ${lang} 代码生成单元测试。只输出测试代码,不要解释,不要使用 Markdown 代码块标记。`
65+
case 'fix':
66+
return `你是代码助手。修复给定 ${lang} 代码中的错误与警告(用户消息附带诊断信息),保持其余行为不变。只输出修复后的完整代码,不要解释,不要使用 Markdown 代码块标记。`
6567
}
6668
}
6769
70+
// 'fix' 把诊断附在用户消息里
71+
const userContent = (): string =>
72+
props.action === 'fix' && props.diagnostics
73+
? `${props.code}\n\n--- 待修复的诊断 ---\n${props.diagnostics}`
74+
: props.code
75+
6876
const run = async () => {
6977
reload()
7078
if (!active.value.apiKey) {
@@ -80,7 +88,7 @@ const run = async () => {
8088
apiKey: active.value.apiKey,
8189
model: active.value.model,
8290
system: systemFor(),
83-
messages: [{role: 'user', content: props.code}]
91+
messages: [{role: 'user', content: userContent()}]
8492
})
8593
result.value = props.action === 'explain' ? reply.trim() : stripFences(reply)
8694
}

src/composables/useBookmarks.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// 行书签:按文件记忆书签行,提供切换/上一处/下一处/清空,并在切换文件时同步到编辑器。
2+
import {ref, watch, type Ref} from 'vue'
3+
import {setBookmarks} from '../editor/bookmark'
4+
5+
export function useBookmarks(editorView: Ref<any>, currentFilePath: Ref<string | null>) {
6+
// 按文件路径记忆书签行号(1-based);未命名文件用空串作 key
7+
const map = ref<Record<string, number[]>>({})
8+
const keyOf = () => currentFilePath.value ?? ''
9+
const current = () => map.value[keyOf()] ?? []
10+
11+
// 把当前文件的书签派发到编辑器
12+
const apply = () => {
13+
editorView.value?.dispatch({effects: setBookmarks.of(current())})
14+
}
15+
16+
const cursorLine = (view: any): number => view.state.doc.lineAt(view.state.selection.main.head).number
17+
const gotoLine = (view: any, ln: number) => {
18+
const pos = view.state.doc.line(ln).from
19+
view.dispatch({selection: {anchor: pos}, scrollIntoView: true})
20+
view.focus()
21+
}
22+
23+
const toggleBookmark = () => {
24+
const view = editorView.value
25+
if (!view) {
26+
return
27+
}
28+
const ln = cursorLine(view)
29+
const key = keyOf()
30+
const arr = map.value[key] ? [...map.value[key]] : []
31+
const i = arr.indexOf(ln)
32+
if (i >= 0) {
33+
arr.splice(i, 1)
34+
}
35+
else {
36+
arr.push(ln)
37+
}
38+
map.value = {...map.value, [key]: arr}
39+
apply()
40+
}
41+
42+
const nextBookmark = () => {
43+
const view = editorView.value
44+
const arr = [...current()].sort((a, b) => a - b)
45+
if (!view || !arr.length) {
46+
return
47+
}
48+
const ln = cursorLine(view)
49+
gotoLine(view, arr.find(l => l > ln) ?? arr[0])
50+
}
51+
52+
const prevBookmark = () => {
53+
const view = editorView.value
54+
const arr = [...current()].sort((a, b) => a - b)
55+
if (!view || !arr.length) {
56+
return
57+
}
58+
const ln = cursorLine(view)
59+
gotoLine(view, [...arr].reverse().find(l => l < ln) ?? arr[arr.length - 1])
60+
}
61+
62+
const clearBookmarks = () => {
63+
const key = keyOf()
64+
if (map.value[key]?.length) {
65+
map.value = {...map.value, [key]: []}
66+
apply()
67+
}
68+
}
69+
70+
// 切换文件 / 编辑器重挂时同步当前文件的书签
71+
watch(currentFilePath, () => apply())
72+
watch(editorView, () => apply())
73+
74+
return {toggleBookmark, nextBookmark, prevBookmark, clearBookmarks}
75+
}

src/composables/useCodeMirrorEditor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import {useCodeMirrorSpaceOmission} from './useCodeMirrorSpaceOmission.ts'
9191
import {EditorView, keymap} from "@codemirror/view";
9292
import {showMinimap} from "@replit/codemirror-minimap";
9393
import {indentationMarkers} from "@replit/codemirror-indentation-markers";
94+
import {bookmarkExtension} from "../editor/bookmark";
9495
import {stickyScroll} from "../editor/stickyScroll";
9596
import {breakpointExtension} from "../editor/breakpointGutter";
9697
import {debugHover} from "../editor/debugHover";
@@ -703,6 +704,9 @@ export function useCodeMirrorEditor(props: Props)
703704
result.push(indentationMarkers({hideFirstIndent: true, highlightActiveBlock: true}))
704705
}
705706

707+
// 书签:行高亮,数据由 App 按当前文件 dispatch
708+
result.push(bookmarkExtension)
709+
706710
// 断点 gutter + 调试悬停求值(仅可调试语言)
707711
if (dapSupportsLanguage(props.language)) {
708712
result.push(breakpointExtension((line) => debug.toggleBreakpoint(props.filePath ?? null, line)))

src/composables/useShortcuts.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ const SHORTCUT_DEFS: { id: string; default: string }[] = [
2828
{id: 'reopenClosed', default: 'Mod+Shift+T'},
2929
{id: 'toggleSidebar', default: 'Mod+B'},
3030
{id: 'toggleTerminal', default: 'Mod+`'},
31-
{id: 'toggleWordWrap', default: 'Alt+Z'}
31+
{id: 'toggleWordWrap', default: 'Alt+Z'},
32+
{id: 'toggleBookmark', default: 'Mod+Alt+K'}
3233
]
3334

3435
const STORAGE_KEY = 'shortcuts'

src/editor/bookmark.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// 书签:按行高亮(左侧强调条 + 淡底色)。书签数据由外部(App)按当前文件 dispatch 填充。
2+
import {Decoration, EditorView, type DecorationSet} from '@codemirror/view'
3+
import {StateEffect, StateField} from '@codemirror/state'
4+
5+
// 由外部派发:设置当前文件的书签行号(1-based)
6+
export const setBookmarks = StateEffect.define<number[]>()
7+
8+
const bookmarkLine = Decoration.line({class: 'cm-bookmark-line'})
9+
10+
export const bookmarkField = StateField.define<DecorationSet>({
11+
create: () => Decoration.none,
12+
update(deco, tr) {
13+
for (const e of tr.effects) {
14+
if (e.is(setBookmarks)) {
15+
const ranges = e.value
16+
.filter(ln => ln >= 1 && ln <= tr.state.doc.lines)
17+
.sort((a, b) => a - b)
18+
.map(ln => bookmarkLine.range(tr.state.doc.line(ln).from))
19+
return Decoration.set(ranges, true)
20+
}
21+
}
22+
// 文档变化时让标记跟随行偏移
23+
return deco.map(tr.changes)
24+
},
25+
provide: f => EditorView.decorations.from(f)
26+
})
27+
28+
const bookmarkTheme = EditorView.baseTheme({
29+
'.cm-bookmark-line': {
30+
backgroundColor: 'rgba(99, 102, 241, 0.10)',
31+
boxShadow: 'inset 3px 0 0 #6366f1'
32+
}
33+
})
34+
35+
export const bookmarkExtension = [bookmarkField, bookmarkTheme]

src/i18n/locales/en.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@
683683
"explainCode": "AI explain code (selection or all)",
684684
"generateTests": "AI generate tests (selection or all)",
685685
"formatWithAi": "AI format code",
686+
"aiFixDiagnostics": "AI fix diagnostics",
686687
"history": "Run history",
687688
"diff": "Diff (current vs saved)",
688689
"preview": "Live preview (Markdown / HTML)",
@@ -694,6 +695,15 @@
694695
"sendToTerminal": "Send selection to terminal",
695696
"reopenClosed": "Reopen closed tab",
696697
"revealInTree": "Reveal in file tree",
698+
"foldAll": "Fold all",
699+
"unfoldAll": "Unfold all",
700+
"goToMatchingBracket": "Go to matching bracket",
701+
"groupCode": "Code",
702+
"toggleBookmark": "Toggle bookmark",
703+
"nextBookmark": "Next bookmark",
704+
"prevBookmark": "Previous bookmark",
705+
"clearBookmarks": "Clear bookmarks in file",
706+
"groupBookmark": "Bookmarks",
697707
"copyPermalink": "Copy remote permalink (current line)",
698708
"openPermalink": "Open remote link in browser (current line)",
699709
"sortLinesAsc": "Sort lines ascending",
@@ -738,7 +748,8 @@
738748
"reopenClosed": "Reopen closed tab",
739749
"toggleSidebar": "Toggle sidebar",
740750
"toggleTerminal": "Toggle terminal",
741-
"toggleWordWrap": "Toggle word wrap"
751+
"toggleWordWrap": "Toggle word wrap",
752+
"toggleBookmark": "Toggle bookmark"
742753
},
743754
"view": {
744755
"clear": "Clear",
@@ -1118,7 +1129,8 @@
11181129
"title": {
11191130
"explain": "AI: Explain code",
11201131
"refactor": "AI: Refactor code",
1121-
"test": "AI: Generate tests"
1132+
"test": "AI: Generate tests",
1133+
"fix": "AI: Fix diagnostics"
11221134
},
11231135
"thinking": "AI is thinking…",
11241136
"replace": "Replace selection",
@@ -1327,6 +1339,8 @@
13271339
"pathCopied": "Path copied",
13281340
"copiedMarkdown": "Copied as Markdown code block",
13291341
"indentConverted": "Indentation converted",
1342+
"noMatchingBracket": "No matching bracket at cursor",
1343+
"noDiagnostics": "No diagnostics in the current file",
13301344
"clipboardEmpty": "Clipboard is empty",
13311345
"clipboardReadFailed": "Failed to read clipboard: ",
13321346
"copyFailed": "Copy failed: ",

0 commit comments

Comments
 (0)