diff --git a/docs/src/content/release/26.4.0.md b/docs/src/content/release/26.4.0.md new file mode 100644 index 00000000..1b33f3e3 --- /dev/null +++ b/docs/src/content/release/26.4.0.md @@ -0,0 +1,106 @@ +--- +title: 26.4.0 +date: 2026-07-06 +description: 可视化查询构建器、数据库工具、AI 增强与编辑器生产力 +--- + +CodeForge v26.4.0 是一次以**数据库与 AI** 为核心的版本:全新的**拖拽式可视化查询构建器**让你不写 SQL 也能搭出多表 JOIN、聚合、分组、过滤与排序的复杂查询;配套带来**表格行内编辑回写、连接分组与导入导出、SQL 一键格式化**等数据库能力;AI 侧新增**修复诊断、生成文档注释、翻译注释、跨文件多文件编辑**;编辑器则补齐**行书签、折叠展开、括号跳转、语法扩选、空白字符渲染**等一批生产力功能。 + +CodeForge v26.4.0 is a release centered on **database tooling and AI**: a brand-new **drag-and-drop visual query builder** lets you compose complex multi-table JOIN / aggregate / group / filter / sort queries without writing SQL; it ships alongside **inline cell editing with write-back, connection grouping and import/export, and one-click SQL formatting**; AI gains **diagnostic fixes, doc-comment generation, comment translation and multi-file editing**; and the editor adds **line bookmarks, folding, bracket matching, syntax-aware selection, and whitespace rendering**. + +--- + +## 📦 版本信息 | Release Information + +- **项目地址 | Repository**:https://github.com/devlive-community/codeforge +- **官方网站 | Official Website**:https://codeforge.devlive.org/ +- **版本号 | Version**:v26.4.0 +- **发布日期 | Release Date**:2026年7月6日 | July 6, 2026 + +--- + +## 🧱 可视化查询构建器 | Visual Query Builder + +不写 SQL,也能拖出完整查询——从左侧结构面板把列拖到各个落区即可实时生成 SQL 并运行: +Compose a full query without writing SQL — drag columns from the schema panel into drop zones to generate and run SQL live: + +- **拖拽式搭建**:列拖入 SELECT / WHERE / GROUP BY / ORDER BY / HAVING 落区,落区带放置提示 +- **多表 JOIN**:加表、选 INNER/LEFT/RIGHT/FULL 与 ON 条件,列自动带表限定 +- **类型感知 WHERE**:运算符随列类型变化,值输入自适应 BETWEEN / IN / 布尔 / 日期 +- **聚合与分组**:聚合函数、别名(AS)、DISTINCT、GROUP BY(含自动分组)、HAVING +- **函数任意用**:可搜索分类的函数选择器,支持**自由输入任意函数名 + 参数**(如 `SUBSTRING(col,1,3)`、`split_part(col,',',1)`),SELECT 与 WHERE 均可套函数 +- **保存 / 载入查询**:按数据源命名存取常用查询;状态自动记忆 +- **未指定库可切库**:连接未选数据库时在构建器内选库 / 切库,表名自动带库限定 + +A drag-driven builder with drop zones for SELECT / WHERE / GROUP BY / ORDER BY / HAVING; **multi-table JOIN** with configurable ON conditions and auto table-qualified columns; **type-aware WHERE** (operators and value inputs adapt to the column type — BETWEEN / IN / boolean / date); aggregates, aliases, DISTINCT, GROUP BY (with auto-grouping) and HAVING; a searchable, categorized function picker that also accepts **any custom function name plus arguments** (usable in both SELECT and WHERE); **named saved queries** per data source with automatic state memory; and in-builder **database picking/switching** for connections without a default database. + +--- + +## 🗄 数据库 · SQL | Database & SQL + +- **表格行内编辑回写** - 双击单元格改值,生成 UPDATE 语句确认后写库(单表结果) +- **连接分组** - 为数据库连接分组,设置页按分组归类展示 +- **连接导入 / 导出** - 连接配置导出为 JSON、从 JSON 导入(默认不含密码,可选包含) +- **格式化 SQL** - 命令面板一键格式化当前 SQL + +**Inline cell editing** with write-back (double-click to edit; generates a confirmed UPDATE for single-table results), **connection grouping** in settings, **connection import/export** as JSON (passwords excluded by default, optional to include), and a **Format SQL** command. + +--- + +## 🤖 AI 能力增强 | AI + +- **AI 修复诊断** - 把当前文件的 LSP 诊断交给 AI 修复 +- **AI 生成文档注释** - 为选中代码生成规范的文档注释 +- **AI 翻译注释 / 文档** - 中英互译注释与文档,保留代码结构 +- **AI 多文件编辑** - 给出指令,跨已打开文件生成修改并逐个差异审查后应用 + +**Fix diagnostics** (hand the file's LSP diagnostics to AI), **generate doc comments** for a selection, **translate comments/docs** between Chinese and English while preserving code structure, and **multi-file editing** — describe a change and review per-file diffs before applying across open files. + +--- + +## 🏃 运行 · REPL | Run & REPL + +- **运行预设管理** - 保存多套运行输入(参数 / stdin / 环境变量)一键切换 +- **运行输出过滤** - 只显示包含关键字的输出行 +- **启动 REPL** - 为当前语言启动交互式解释器 + +Manage **run presets** (args / stdin / env) with one-click switching, **filter run output** to lines matching a keyword, and **start a REPL** for the current language. + +--- + +## ✏️ 编辑器生产力 | Editor Productivity + +- **行书签** - 切换 / 上一处 / 下一处 / 清空,按文件记忆 +- **折叠 / 展开全部代码**、**转到匹配括号**、**展开 / 收缩选区**(按语法节点逐级扩选) +- **转换缩进** - 空格 ↔ 制表符互转 +- **渲染空白字符** - 空格与制表符可视化开关 + +**Line bookmarks** (toggle / next / previous / clear, remembered per file), **fold/unfold all**, **go to matching bracket**, **expand/shrink selection** by syntax node, **convert indentation** (spaces ↔ tabs), and a **render-whitespace** toggle. + +--- + +## 🧭 大纲 · 诊断 · 工作区 | Outline, Diagnostics & Workspace + +- **大纲优先用 LSP** - 基于 documentSymbol,回退正则解析 +- **问题面板跨文件聚合诊断** - 汇总多个文件的诊断 +- **命名工作区** - 保存 / 打开一组工作区根 + +Outline now prefers **LSP documentSymbol** (falling back to regex), the problems panel **aggregates diagnostics across files**, and **named workspaces** let you save and reopen a set of roots. + +--- + +## 🌿 Git · 命令面板 | Git & Command Palette + +- **在浏览器打开仓库主页** - 一键跳转远程仓库 +- **强制删除未合并分支** - 删除失败时可确认强制删除 +- **命令面板最近使用置顶** - 常用命令更快触达 + +**Open the repository homepage in a browser**, **force-delete** branches that aren't fully merged (with confirmation), and **recently-used commands** float to the top of the command palette. + +--- + +## 📥 立即下载 | Download Now + +在 [GitHub Releases](https://github.com/devlive-community/codeforge/releases) 下载最新版本,或访问[官方网站](https://codeforge.devlive.org/)了解更多信息。 + +Download the latest version from [GitHub Releases](https://github.com/devlive-community/codeforge/releases), or visit the [Official Website](https://codeforge.devlive.org/) for more information. diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index a6705d25..a690fb6e 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -24,6 +24,7 @@ pub struct EditorConfig { pub show_sticky_scroll: Option, // 是否启用粘性滚动 pub word_wrap: Option, // 是否自动换行 pub show_indent_guides: Option, // 是否显示缩进参考线 + pub render_whitespace: Option, // 是否渲染空白字符 pub layout: Option, // 编辑器/控制台布局: horizontal | vertical | editor pub last_direction: Option, // 仅编辑器模式下控制台弹出方向: horizontal | vertical pub max_open_file_size: Option, // 打开文件大小上限(MB),超过则拒绝打开 @@ -77,6 +78,7 @@ impl Default for AppConfig { show_sticky_scroll: Some(false), word_wrap: Some(false), show_indent_guides: Some(false), + render_whitespace: Some(false), layout: Some("horizontal".to_string()), last_direction: Some("horizontal".to_string()), max_open_file_size: Some(5), @@ -151,6 +153,7 @@ impl ConfigManager { show_sticky_scroll: Some(false), word_wrap: Some(false), show_indent_guides: Some(false), + render_whitespace: Some(false), layout: Some("horizontal".to_string()), last_direction: Some("horizontal".to_string()), max_open_file_size: Some(5), @@ -278,6 +281,7 @@ impl ConfigManager { show_sticky_scroll: Some(false), word_wrap: Some(false), show_indent_guides: Some(false), + render_whitespace: Some(false), layout: Some("horizontal".to_string()), last_direction: Some("horizontal".to_string()), max_open_file_size: Some(5), diff --git a/src-tauri/src/db_connections.rs b/src-tauri/src/db_connections.rs index acf8cb58..01b8e6cc 100644 --- a/src-tauri/src/db_connections.rs +++ b/src-tauri/src/db_connections.rs @@ -41,6 +41,9 @@ pub struct DbConnection { pub ssh_password: Option, #[serde(default)] pub ssh_key_file: Option, + // 分组(用于在列表里归类;数据库列名为 grp,避开 SQL 关键字) + #[serde(default)] + pub group: Option, } pub struct DbConnStore { @@ -72,6 +75,7 @@ impl DbConnStore { ssh_user TEXT, ssh_password TEXT, ssh_key_file TEXT, + grp TEXT, sort_order INTEGER NOT NULL DEFAULT 0 )", [], @@ -87,6 +91,7 @@ impl DbConnStore { "ssh_user TEXT", "ssh_password TEXT", "ssh_key_file TEXT", + "grp TEXT", ] { let _ = conn.execute( &format!("ALTER TABLE db_connections ADD COLUMN {}", col), @@ -101,7 +106,7 @@ impl DbConnStore { } const COLUMNS: &str = "id, name, kind, file, host, port, user, password, database, \ - ssl, ssh_enabled, ssh_host, ssh_port, ssh_user, ssh_password, ssh_key_file"; + ssl, ssh_enabled, ssh_host, ssh_port, ssh_user, ssh_password, ssh_key_file, grp"; fn row_to_conn(row: &rusqlite::Row) -> rusqlite::Result { Ok(DbConnection { @@ -121,6 +126,7 @@ fn row_to_conn(row: &rusqlite::Row) -> rusqlite::Result { ssh_user: row.get(13)?, ssh_password: row.get(14)?, ssh_key_file: row.get(15)?, + group: row.get(16)?, }) } @@ -166,7 +172,7 @@ pub async fn db_connection_save( conn.execute( "UPDATE db_connections SET name=?2, kind=?3, file=?4, host=?5, port=?6, user=?7, \ password=?8, database=?9, ssl=?10, ssh_enabled=?11, ssh_host=?12, ssh_port=?13, \ - ssh_user=?14, ssh_password=?15, ssh_key_file=?16 WHERE id=?1", + ssh_user=?14, ssh_password=?15, ssh_key_file=?16, grp=?17 WHERE id=?1", params![ c.id, c.name, @@ -183,7 +189,8 @@ pub async fn db_connection_save( c.ssh_port, c.ssh_user, c.ssh_password, - c.ssh_key_file + c.ssh_key_file, + c.group ], ) .map_err(|e| format!("更新连接失败: {}", e))?; @@ -198,8 +205,8 @@ pub async fn db_connection_save( conn.execute( "INSERT INTO db_connections (id, name, kind, file, host, port, user, password, \ database, ssl, ssh_enabled, ssh_host, ssh_port, ssh_user, ssh_password, \ - ssh_key_file, sort_order) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)", + ssh_key_file, grp, sort_order) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18)", params![ c.id, c.name, @@ -217,6 +224,7 @@ pub async fn db_connection_save( c.ssh_user, c.ssh_password, c.ssh_key_file, + c.group, next ], ) diff --git a/src/App.vue b/src/App.vue index 7b821eef..d7224db8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -43,10 +43,13 @@ - +
+ + +
@@ -101,6 +104,7 @@ + @@ -198,8 +202,10 @@ :is-running="isRunning" :execution-time="lastExecutionTime" :paging="sqlPaging" + :editable-table="sqlEditableTable" @prev="sqlPrevPage" @next="sqlNextPage" + @edit-cell="onSqlEditCell" @clear="clearOutput"/> @@ -243,6 +249,7 @@ + @@ -325,6 +332,18 @@ + + +
+

{{ t('sqlEdit.confirmHint') }}

+
{{ pendingSqlUpdate.sql }}
+
+ + +
+
+
+ @@ -405,6 +424,9 @@ + + + @@ -420,7 +442,8 @@ @@ -467,6 +490,7 @@ +
- + @@ -37,7 +37,7 @@ import {useI18n} from 'vue-i18n' import {useAiConfig} from '../composables/useAiConfig' import {useToast} from '../plugins/toast' -const props = defineProps<{ language: string; code: string; action: 'explain' | 'refactor' | 'test' | 'fix' | 'doc'; diagnostics?: string }>() +const props = defineProps<{ language: string; code: string; action: 'explain' | 'refactor' | 'test' | 'fix' | 'doc' | 'translate'; diagnostics?: string }>() const emit = defineEmits<{ replace: [code: string]; insert: [code: string]; close: [] }>() const toast = useToast() @@ -66,6 +66,8 @@ const systemFor = (): string => { return `你是代码助手。修复给定 ${lang} 代码中的错误与警告(用户消息附带诊断信息),保持其余行为不变。只输出修复后的完整代码,不要解释,不要使用 Markdown 代码块标记。` case 'doc': return `你是代码助手。为给定的 ${lang} 代码生成规范的文档注释(如 JSDoc/docstring/rustdoc 等,与语言习惯一致)。只输出注释块本身,不要重复原代码,不要解释,不要使用 Markdown 代码块标记。` + case 'translate': + return `你是翻译助手。把给定内容里的自然语言(注释、文档、字符串描述)在中文与英文之间互译:中文译为英文,英文译为中文。严格保留代码结构、标识符、符号与缩进不变,只翻译其中的自然语言部分。只输出翻译后的完整内容,不要解释,不要使用 Markdown 代码块标记。` } } diff --git a/src/components/AiMultiEdit.vue b/src/components/AiMultiEdit.vue new file mode 100644 index 00000000..0a9839f9 --- /dev/null +++ b/src/components/AiMultiEdit.vue @@ -0,0 +1,240 @@ + + + diff --git a/src/components/ConsoleOutput.vue b/src/components/ConsoleOutput.vue index be76d1e5..a6491e37 100644 --- a/src/components/ConsoleOutput.vue +++ b/src/components/ConsoleOutput.vue @@ -12,6 +12,15 @@
{{ t('console.copied') }} + + +
+ +
+ + + {{ t('console.filterCount', { n: matchCount }) }} +
+
@@ -63,7 +81,7 @@ diff --git a/src/components/LaunchPresets.vue b/src/components/LaunchPresets.vue new file mode 100644 index 00000000..bd23ad39 --- /dev/null +++ b/src/components/LaunchPresets.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/components/QueryBuilder.vue b/src/components/QueryBuilder.vue new file mode 100644 index 00000000..e2e547ba --- /dev/null +++ b/src/components/QueryBuilder.vue @@ -0,0 +1,828 @@ + + + diff --git a/src/components/SqlResultTable.vue b/src/components/SqlResultTable.vue index 762ea57e..2e6709a5 100644 --- a/src/components/SqlResultTable.vue +++ b/src/components/SqlResultTable.vue @@ -15,7 +15,9 @@
{{ t('sql.resultSet', { n: ri + 1, rows: rs.rows.length }) }}
- +
@@ -37,7 +39,8 @@ const {t} = useI18n() interface ResultSet { columns: string[]; rows: any[][] } interface SqlResult { result_sets: ResultSet[]; messages: string[]; error: string | null; elapsed_ms: number } -const props = defineProps<{ output: string; emptyText?: string; fillHeight?: boolean }>() +const props = defineProps<{ output: string; emptyText?: string; fillHeight?: boolean; editable?: boolean }>() +const emit = defineEmits<{ editCell: [payload: { column: string; row: any[]; columns: string[]; oldValue: any; newValue: any }] }>() const result = computed(() => { if (!props.output.trim()) { diff --git a/src/components/SqlTableView.vue b/src/components/SqlTableView.vue index e06d6259..69646c9a 100644 --- a/src/components/SqlTableView.vue +++ b/src/components/SqlTableView.vue @@ -62,7 +62,8 @@
- +
@@ -84,8 +85,14 @@ const props = defineProps<{ isRunning: boolean executionTime?: number paging?: { active: boolean; offset: number; pageSize: number; hasMore: boolean } + editableTable?: string | null +}>() +const emit = defineEmits<{ + clear: [] + prev: [] + next: [] + editCell: [payload: { table: string; column: string; row: any[]; columns: string[]; oldValue: any; newValue: any }] }>() -const emit = defineEmits<{ clear: []; prev: []; next: [] }>() const {t} = useI18n() const viewMode = ref<'table' | 'chart' | 'pivot'>('table') diff --git a/src/components/VirtualTable.vue b/src/components/VirtualTable.vue index b874c842..fa85116b 100644 --- a/src/components/VirtualTable.vue +++ b/src/components/VirtualTable.vue @@ -16,8 +16,23 @@ {{ start + i + 1 }} - {{ fmt(row[ci]) }} + + + + @@ -29,11 +44,50 @@