From 03d0b1fe463ddf59da84a16d7ae41cad199b4f59 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Mon, 6 Jul 2026 19:11:14 +0800 Subject: [PATCH 1/8] =?UTF-8?q?chore(release):=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E5=8D=87=E8=87=B3=2026.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f9e0ed62..466b55f0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "codeforge", "private": true, - "version": "26.4.0", + "version": "26.5.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ba99ea03..12dd2b38 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "CodeForge" -version = "26.4.0" +version = "26.5.0" dependencies = [ "async-trait", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3c263cd0..7396e42b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "CodeForge" -version = "26.4.0" +version = "26.5.0" description = "CodeForge 是一款轻量级、高性能的桌面代码执行器,专为开发者、学生和编程爱好者设计。" authors = ["devlive-community"] edition = "2024" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index da7bb972..b38589d9 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "CodeForge", - "version": "26.4.0", + "version": "26.5.0", "identifier": "org.devlive.codeforge", "build": { "beforeDevCommand": "pnpm dev", From 9d47b1526a80d9017ea41f6e26fc6c499d76a13d Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Mon, 6 Jul 2026 19:15:24 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat(workspace):=20Git=20=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=87=E6=8D=A2=E6=B4=BB=E5=8A=A8=E6=A0=B9?= =?UTF-8?q?=EF=BC=88=E5=A4=9A=E6=A0=B9=E5=B7=A5=E4=BD=9C=E5=8C=BA=E5=90=84?= =?UTF-8?q?=E6=A0=B9=E7=8B=AC=E7=AB=8B=20Git=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 16 +++++++++++++--- src/components/GitPanel.vue | 21 ++++++++++++++++++--- src/i18n/locales/en.json | 1 + src/i18n/locales/zh-CN.json | 1 + 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/App.vue b/src/App.vue index d7224db8..3b1b12df 100644 --- a/src/App.vue +++ b/src/App.vue @@ -434,9 +434,11 @@ - @@ -786,9 +788,17 @@ const rootDir = ref(null) // 远程仓库永久链接(复制 / 在浏览器打开) const {copyPermalink, openPermalink, openRepoOnWeb} = useGitPermalink(rootDir, currentFilePath, cursorInfo) -// 多根工作区:额外挂载的文件夹(Git/搜索仍走主根 rootDir) +// 多根工作区:额外挂载的文件夹 const {extraRoots, addWorkspaceFolder, removeWorkspaceFolder, resetExtraRoots, setExtraRoots} = useWorkspaceRoots(rootDir) +// 活动根:Git 面板针对哪个根(各根独立 Git);无效时回退主根 +const activeGitRoot = ref(null) +const allRoots = computed(() => [rootDir.value, ...extraRoots.value].filter((r): r is string => !!r)) +const gitRoot = computed(() => { + const a = activeGitRoot.value + return a && allRoots.value.includes(a) ? a : rootDir.value +}) + // 命名工作区:保存/打开一组根 const showWorkspaces = ref(false) const openWorkspace = (ws: {rootDir: string; extraRoots: string[]}) => { diff --git a/src/components/GitPanel.vue b/src/components/GitPanel.vue index 2224e345..0410a9e1 100644 --- a/src/components/GitPanel.vue +++ b/src/components/GitPanel.vue @@ -7,6 +7,14 @@
+ + + + +
@@ -99,10 +103,28 @@ const token = ref('') const loading = ref(false) const error = ref('') const items = ref([]) +const branches = ref([]) const showForm = ref(false) const submitting = ref(false) const form = reactive({title: '', base: 'main', body: '', draft: false}) +// 拉取仓库分支与默认分支,供新建 PR 的 base 下拉使用 +const loadBranches = async () => { + if (!token.value) { + return + } + try { + const res = await invoke<{ default_branch: string; branches: string[] }>('github_repo_branches', { + token: token.value, owner: props.owner, repo: props.repo + }) + branches.value = res.branches + if (res.default_branch) { + form.base = res.default_branch + } + } + catch { /* 拉取失败则退回文本输入 */ } +} + const openUrl = (url: string) => url && openExternalUrl(url) const reload = async () => { @@ -175,6 +197,7 @@ onMounted(async () => { token.value = config?.github?.token || '' } catch { /* 忽略 */ } + loadBranches() reload() }) From bd2f8c515a27b108fff4dda4bf06284df6cdff67 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Tue, 7 Jul 2026 09:41:56 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat(share):=20=E5=AF=BC=E5=87=BA=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=B8=BA=E5=9B=BE=E7=89=87=EF=BC=88=E4=B8=BB=E9=A2=98?= =?UTF-8?q?/=E5=86=85=E8=BE=B9=E8=B7=9D/=E7=AA=97=E5=8F=A3=E8=A3=85?= =?UTF-8?q?=E9=A5=B0/=E8=A1=8C=E5=8F=B7=EF=BC=8C=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=88=96=E5=A4=8D=E5=88=B6=20PNG=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 18 ++- src/components/CodeImageExport.vue | 197 +++++++++++++++++++++++++++++ src/i18n/locales/en.json | 16 +++ src/i18n/locales/zh-CN.json | 16 +++ 4 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/components/CodeImageExport.vue diff --git a/src/App.vue b/src/App.vue index 3b1b12df..69bac803 100644 --- a/src/App.vue +++ b/src/App.vue @@ -427,6 +427,9 @@ + + + @@ -541,7 +544,7 @@ import {debounce} from 'lodash-es' import {formatDocument, formatSelection, renameSymbol} from 'codemirror-languageserver' import {runGotoDefinition, lspSupportsLanguage, triggerCodeActions, applyCodeAction, formatDocumentAsync} from './editor/lspExtension' import {dapSupportsLanguage} from './debug/dapClient' -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' +import {ArrowDownAZ, ArrowUpAZ, Bookmark, CaseLower, CaseUpper, ChevronRight, Code2, CornerDownRight, Eraser, Eye, FoldVertical, FolderOpen, GitBranch, GitCompare, History, Image as ImageIcon, 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' import {ExecutionResult, LayoutMode, SplitDirection} from './types/app.ts' import AppHeader from './components/AppHeader.vue' import CodeEditor from './components/CodeEditor.vue' @@ -605,6 +608,7 @@ import DebugToolbar from './components/DebugToolbar.vue' import DebugPanel from './components/DebugPanel.vue' import AiCodeAction from './components/AiCodeAction.vue' import AiMultiEdit from './components/AiMultiEdit.vue' +import CodeImageExport from './components/CodeImageExport.vue' import ErDiagram from './components/ErDiagram.vue' import TxnControl from './components/TxnControl.vue' import {useSqlTxn} from './composables/useSqlTxn' @@ -976,6 +980,17 @@ const selectedOrAll = (): string => { return code.value } +// 导出代码为图片(选中片段或整篇) +const codeImageCtx = ref<{ code: string } | null>(null) +const openCodeImage = () => { + const src = selectedOrAll() + if (!src.trim()) { + toast.info(t('app.noCodeToImage')) + return + } + codeImageCtx.value = {code: src} +} + // 以一次性提示打开 AI(临时会话,不关联执行) const openAiWithPrompt = (prompt: string) => { aiExecutionId.value = null @@ -2563,6 +2578,7 @@ const paletteCommands = computed(() => [ {id: 'removeDuplicateLines', label: t('command.removeDuplicateLines'), group: t('command.groupText'), icon: ListChecks, run: () => removeDuplicateLines()}, {id: 'trimTrailingWhitespace', label: t('command.trimTrailingWhitespace'), group: t('command.groupText'), icon: Eraser, run: () => trimTrailingWhitespace()}, {id: 'copyAsMarkdown', label: t('command.copyAsMarkdown'), group: t('command.groupText'), icon: Code2, run: () => copyAsMarkdown()}, + {id: 'exportCodeImage', label: t('command.exportCodeImage'), group: t('command.groupText'), icon: ImageIcon, run: openCodeImage}, {id: 'indentToSpaces', label: t('command.indentToSpaces'), group: t('command.groupText'), icon: Eraser, run: () => convertIndentation(false)}, {id: 'indentToTabs', label: t('command.indentToTabs'), group: t('command.groupText'), icon: Eraser, run: () => convertIndentation(true)}, {id: 'foldAll', label: t('command.foldAll'), group: t('command.groupCode'), icon: FoldVertical, run: () => { if (editorView.value) foldAll(editorView.value) }}, diff --git a/src/components/CodeImageExport.vue b/src/components/CodeImageExport.vue new file mode 100644 index 00000000..30e7d6c0 --- /dev/null +++ b/src/components/CodeImageExport.vue @@ -0,0 +1,197 @@ + + + diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 48d3cfdd..e3448c65 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -714,6 +714,7 @@ "aiGenDoc": "AI generate doc comment", "aiTranslate": "AI translate comments/docs", "aiMultiEdit": "AI multi-file edit", + "exportCodeImage": "Export code as image", "history": "Run history", "diff": "Diff (current vs saved)", "preview": "Live preview (Markdown / HTML)", @@ -1048,6 +1049,20 @@ "all": "All", "pivotHint": "Drag a field to \"Values\" to build the pivot table" }, + "codeImage": { + "title": "Export code as image", + "theme": "Theme", + "padding": "Padding", + "windowChrome": "Window chrome", + "lineNumbers": "Line numbers", + "copy": "Copy to clipboard", + "download": "Download PNG", + "rendering": "Rendering…", + "saved": "Image saved", + "copied": "Image copied", + "failed": "Render failed", + "copyFailed": "Copy failed" + }, "gh": { "prs": "Pull Requests", "issues": "Issues", @@ -1511,6 +1526,7 @@ "noDiagnostics": "No diagnostics in the current file", "replUnsupported": "No built-in REPL for the current language", "replStarted": "REPL started: {cmd}", + "noCodeToImage": "No code to export", "aiMultiNeedFiles": "Open the files you want to edit first", "aiMultiApplied": "AI edits applied ({n} file(s))", "aiMultiWriteFailed": "Failed to write {name}: ", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 3363793e..ee748b3c 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -714,6 +714,7 @@ "aiGenDoc": "AI 生成文档注释", "aiTranslate": "AI 翻译注释/文档", "aiMultiEdit": "AI 多文件编辑", + "exportCodeImage": "导出代码为图片", "history": "执行历史", "diff": "差异对比(当前 vs 已保存)", "preview": "实时预览(Markdown / HTML)", @@ -1048,6 +1049,20 @@ "all": "全部", "pivotHint": "把字段拖到「值」即可生成透视表" }, + "codeImage": { + "title": "导出代码为图片", + "theme": "主题", + "padding": "内边距", + "windowChrome": "窗口装饰", + "lineNumbers": "行号", + "copy": "复制到剪贴板", + "download": "下载 PNG", + "rendering": "渲染中…", + "saved": "图片已保存", + "copied": "图片已复制", + "failed": "生成失败", + "copyFailed": "复制失败" + }, "gh": { "prs": "Pull Requests", "issues": "Issues", @@ -1511,6 +1526,7 @@ "noDiagnostics": "当前文件没有诊断问题", "replUnsupported": "当前语言暂无内置 REPL", "replStarted": "已启动 REPL:{cmd}", + "noCodeToImage": "没有可导出的代码", "aiMultiNeedFiles": "请先打开需要参与编辑的文件", "aiMultiApplied": "已应用 AI 修改({n} 个文件)", "aiMultiWriteFailed": "写入 {name} 失败:", From f7e44a8bce1f0bfb6d783a916af39c21d408f70f Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Tue, 7 Jul 2026 10:25:58 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat(shortcut):=20=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E9=94=AE=E9=A2=84=E8=AE=BE=EF=BC=88=E4=BF=9D=E5=AD=98=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E7=BB=91=E5=AE=9A=E4=B8=BA=E5=91=BD=E5=90=8D=E9=A2=84?= =?UTF-8?q?=E8=AE=BE=EF=BC=8C=E5=8F=AF=E5=A5=97=E7=94=A8/=E5=88=87?= =?UTF-8?q?=E6=8D=A2/=E5=88=A0=E9=99=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/setting/Shortcut.vue | 45 ++++++++++++++++++++++++-- src/composables/useShortcuts.ts | 50 ++++++++++++++++++++++++++++- src/i18n/locales/en.json | 6 ++++ src/i18n/locales/zh-CN.json | 6 ++++ 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/components/setting/Shortcut.vue b/src/components/setting/Shortcut.vue index 88469ab6..214fc168 100644 --- a/src/components/setting/Shortcut.vue +++ b/src/components/setting/Shortcut.vue @@ -8,6 +8,25 @@
+ +
+ {{ t('settings.shortcut.presets') }} + +
+ {{ p.name }} + +
+
+ + +
+
+
import {onMounted, onUnmounted, ref} from 'vue' import {useI18n} from 'vue-i18n' -import {Keyboard} from 'lucide-vue-next' +import {Keyboard, X} from 'lucide-vue-next' import Button from '../../ui/Button.vue' +import {useToast} from '../../plugins/toast' import {comboFromEvent, formatCombo, useShortcuts} from '../../composables/useShortcuts' const {t} = useI18n() -const {actions, getBinding, setBinding, resetBinding, resetAll} = useShortcuts() +const toast = useToast() +const {actions, getBinding, setBinding, resetBinding, resetAll, presets, savePreset, applyPreset, deletePreset} = useShortcuts() const recordingId = ref(null) +// 快捷键预设 +const newPresetName = ref('') +const doSavePreset = () => { + const name = newPresetName.value.trim() + if (!name) { + return + } + savePreset(name) + newPresetName.value = '' + toast.success(t('settings.shortcut.presetSaved', {name})) +} +const onApplyPreset = (e: Event) => { + const name = (e.target as HTMLSelectElement).value + ;(e.target as HTMLSelectElement).value = '' + if (name) { + applyPreset(name) + toast.success(t('settings.shortcut.presetApplied', {name})) + } +} + const onRecordKeydown = (e: KeyboardEvent) => { if (!recordingId.value) { return diff --git a/src/composables/useShortcuts.ts b/src/composables/useShortcuts.ts index 6efcee98..9862ca97 100644 --- a/src/composables/useShortcuts.ts +++ b/src/composables/useShortcuts.ts @@ -9,6 +9,13 @@ export interface ShortcutAction default: string } +// 命名快捷键预设:一整套绑定,可保存/套用/切换 +export interface ShortcutPreset +{ + name: string + bindings: Record +} + // 可自定义的快捷键动作及默认绑定(Mod = mac 上 ⌘,其他平台 Ctrl)。 // 动作名(label)由 i18n 提供(shortcutAction.),随界面语言切换。 const SHORTCUT_DEFS: { id: string; default: string }[] = [ @@ -35,6 +42,7 @@ const SHORTCUT_DEFS: { id: string; default: string }[] = [ ] const STORAGE_KEY = 'shortcuts' +const PRESET_KEY = 'shortcut-presets' const isMac = typeof navigator !== 'undefined' && /Mac/i.test(navigator.platform) @@ -129,6 +137,41 @@ export function useShortcuts() SHORTCUT_DEFS.map(a => ({id: a.id, label: i18n.global.t(`shortcutAction.${a.id}`), default: a.default})) ) + // ===== 命名预设:保存当前整套绑定,随时套用/切换 ===== + const presets = ref(kvGetJSON(PRESET_KEY, [])) + const reloadPresets = () => { + presets.value = kvGetJSON(PRESET_KEY, []) + } + const persistPresets = () => kvSetJSON(PRESET_KEY, presets.value) + + // 把当前生效的整套绑定存为预设(同名覆盖) + const savePreset = (name: string) => { + const entry: ShortcutPreset = {name, bindings: {...bindings.value}} + const i = presets.value.findIndex(p => p.name === name) + if (i >= 0) { + presets.value[i] = entry + } + else { + presets.value = [...presets.value, entry] + } + persistPresets() + } + + // 套用预设:把预设的绑定作为覆盖生效 + const applyPreset = (name: string) => { + const p = presets.value.find(pr => pr.name === name) + if (!p) { + return + } + overrides.value = {...p.bindings} + persist() + } + + const deletePreset = (name: string) => { + presets.value = presets.value.filter(p => p.name !== name) + persistPresets() + } + return { actions, bindings, @@ -138,6 +181,11 @@ export function useShortcuts() resetAll, matchAction, formatCombo, - reload: load + reload: load, + presets, + savePreset, + applyPreset, + deletePreset, + reloadPresets } } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index e3448c65..3be99561 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -97,6 +97,12 @@ "cancel": "Cancel", "edit": "Edit", "reset": "Reset", + "presets": "Presets", + "applyPreset": "Apply preset…", + "savePreset": "Save as preset", + "presetNamePlaceholder": "Preset name", + "presetSaved": "Saved preset \"{name}\"", + "presetApplied": "Applied preset \"{name}\"", "hint": "Tip: changes take effect after closing Settings." }, "cache": { diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index ee748b3c..e2f01788 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -97,6 +97,12 @@ "cancel": "取消", "edit": "修改", "reset": "重置", + "presets": "预设", + "applyPreset": "套用预设…", + "savePreset": "保存为预设", + "presetNamePlaceholder": "预设名称", + "presetSaved": "已保存预设「{name}」", + "presetApplied": "已套用预设「{name}」", "hint": "提示:修改后关闭设置即生效。" }, "cache": {