diff --git a/README.md b/README.md
index ba0117c..b0e712d 100644
--- a/README.md
+++ b/README.md
@@ -1,70 +1,125 @@
-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

+---
+
+## 📹 演示视频
+
+[下载演示视频](https://devlive-cdn.oss-cn-beijing.aliyuncs.com/applications/codeforge/codeforge.mp4)(点击下载或观看)
+
+> GitHub 不支持直接播放视频,请下载或点击链接查看。
+
+---
+
+## ✨ 核心功能
+
+### 📂 编辑与项目
+- **文件树侧栏 + 多标签编辑** —— 打开文件夹,像 IDE 一样浏览、编辑整个项目
+- **面包屑路径导航** —— 点击任意层级在系统文件管理器中定位
+- **命令面板**(`Cmd/Ctrl + Shift + P`)—— 一处入口直达所有命令
+- **快速打开**(`Cmd/Ctrl + P`)—— 模糊匹配 + 最近文件优先
+- **符号大纲**(`Cmd/Ctrl + Shift + O`)、**跳转到行**(`Cmd/Ctrl + G`)
+- **代码片段** —— 自定义前缀,输入后按 `Tab` 展开(`$0` 为光标落点)
+- **会话恢复** —— 重启自动恢复上次的文件夹与标签页
+- **深色模式** —— 跟随系统 / 浅色 / 深色,编辑器主题同步切换
+
+### ▶️ 运行与调试
+- **一键运行 / 按文件就地运行**,实时流式输出、执行耗时统计
+- **运行选中片段**(`Cmd/Ctrl + Shift + Enter`)
+- **监听模式** —— 保存后自动重跑
+- **运行输入** —— 自定义参数 / stdin / 环境变量,并按文件记忆
+- **执行历史** —— 持久化保存,可一键重跑与还原
+
+### 📊 结构化数据可视化
+- **JSON / XML / YAML** —— 可折叠**层级树**,以及卡片 + 连线的**关系图**两种可视化
+- **Markdown** —— 实时渲染预览(支持内嵌 HTML,DOMPurify 净化防 XSS)
+- **GitHub Actions 工作流** —— 自动识别并渲染为 **Jobs 依赖 DAG 图**(触发事件 → 各 Job → Steps)
+
+### 🤖 AI 助手
+- **多服务商** —— Claude (Anthropic) / OpenAI / DeepSeek
+- **AI 代码预测** —— 编辑器内幽灵补全,`Tab` 接受
+- **解释代码 / 生成测试 / 格式化代码** —— 一键发起,应用前可 diff 预览确认
+- **报错分析、自然语言生成代码、生成 Git 提交信息**
+- **对话与执行历史绑定** —— 每次执行的 AI 讨论可追溯
+
+### 🔱 Git 集成
+- **源代码管理面板** —— 暂存 / 提交 / 推送 / 分支切换,AI 一键生成提交信息
+- **文件树状态徽标**(M / A / D / U)
+- **编辑器行内差异标记** —— 相对 HEAD 的增 / 改 / 删
+
+### 🔎 搜索与终端
+- **文件夹内搜索与替换**(`Cmd/Ctrl + Shift + F`)
+- **集成终端** —— 真实 shell、多标签、可拖拽改高度(`` Cmd/Ctrl + ` ``)
+
+---
+
+## 🧩 支持的语言
+
+可运行语言均采用**插件化架构**,每种语言独立实现;JSON / XML / YAML / Markdown / 纯文本为编辑与可视化类型。
+
+
+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+
-## 安装
+
-**系统要求:**
+`Python` · `Node.js` · `TypeScript` · `JavaScript` · `Go` · `Rust` · `Java` · `Kotlin` · `Scala` · `Groovy` · `Clojure` · `C` · `C++` · `Objective-C/C++` · `Swift` · `Ruby` · `PHP` · `R` · `Lua` · `Haskell` · `Cangjie` · `Shell` · `AppleScript` · `HTML` · `CSS` · `SVG` · `JSON` · `XML` · `YAML` · `Markdown` · `Text`
-- Node.js 18+
-- Rust 1.8+
-- Tauri 2.x
-- Vue 3.x
+
-**构建步骤:**
+---
+
+## 🚀 安装与构建
+
+**环境要求:** Node.js 22+ · Rust 1.8+ · pnpm
```bash
# 克隆项目
@@ -81,12 +136,23 @@ pnpm tauri dev
pnpm tauri build
```
-## 技术栈
+---
+
+## 🛠 技术栈
+
+| 层 | 技术 |
+| --- | --- |
+| 前端 | Vue 3 · TypeScript · Tailwind CSS · CodeMirror 6 |
+| 后端 | Rust · Tauri 2 |
+| 存储 | SQLite(执行历史 / AI 对话 / 代码片段 / 应用配置统一入库) |
+| 架构 | 插件化语言支持系统 |
+
+---
+
+## 🤝 贡献与反馈
-- **前端:** Vue 3 + TypeScript + Tailwind CSS
-- **后端:** Rust + Tauri
-- **架构:** 插件化语言支持系统
+欢迎提交 Issue 与 PR:
-## 许可证
+## 📄 许可证
-MIT License
\ No newline at end of file
+[MIT License](LICENSE)
diff --git a/package.json b/package.json
index 1f6fa75..50e2f35 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-rust": "^6.0.2",
+ "@codemirror/lang-sql": "^6.10.0",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/lang-yaml": "^6.1.3",
"@codemirror/language": "^6.11.2",
@@ -38,6 +39,8 @@
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"codemirror": "^6.0.2",
+ "dompurify": "^3.4.8",
+ "js-yaml": "^4.2.0",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.539.0",
"markdown-it": "^14.2.0",
@@ -48,6 +51,7 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.1.11",
"@tauri-apps/cli": "^2",
+ "@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/markdown-it": "^14.1.2",
"@vitejs/plugin-vue": "^5.2.1",
diff --git a/public/icons/sql.svg b/public/icons/sql.svg
new file mode 100644
index 0000000..a10961d
--- /dev/null
+++ b/public/icons/sql.svg
@@ -0,0 +1,6 @@
+
diff --git a/src-tauri/src/ai_history.rs b/src-tauri/src/ai_history.rs
index 916c209..8aa0d08 100644
--- a/src-tauri/src/ai_history.rs
+++ b/src-tauri/src/ai_history.rs
@@ -14,6 +14,8 @@ impl AiHistory {
let db_path = get_codeforge_db_path()?;
let conn = Connection::open(&db_path).map_err(|e| format!("打开数据库失败: {}", e))?;
let _ = conn.pragma_update(None, "journal_mode", "WAL");
+ let _ = conn.pragma_update(None, "synchronous", "NORMAL");
+ let _ = conn.busy_timeout(std::time::Duration::from_secs(5));
// 清理早期错误结构的旧表
let _ = conn.execute("DROP TABLE IF EXISTS ai_conversations", []);
conn.execute(
diff --git a/src-tauri/src/execution.rs b/src-tauri/src/execution.rs
index bf72c6b..761d6cf 100644
--- a/src-tauri/src/execution.rs
+++ b/src-tauri/src/execution.rs
@@ -41,6 +41,12 @@ impl ExecutionHistory {
let conn =
Connection::open(&db_path).map_err(|e| format!("打开执行历史数据库失败: {}", e))?;
+ // 与其它连接(ai/kv/snippets)共用同一个库文件:统一 WAL + NORMAL + busy_timeout,
+ // 避免并发写入(如运行时同时有 kv 写入)相互阻塞导致 insert 卡顿
+ let _ = conn.pragma_update(None, "journal_mode", "WAL");
+ let _ = conn.pragma_update(None, "synchronous", "NORMAL");
+ let _ = conn.busy_timeout(std::time::Duration::from_secs(5));
+
conn.execute(
"CREATE TABLE IF NOT EXISTS execution_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -627,7 +633,7 @@ pub async fn execute_code(
return Ok(result);
}
Ok(None) => {
- tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+ tokio::time::sleep(tokio::time::Duration::from_millis(20)).await;
}
Err(e) => {
let _ = child.kill();
diff --git a/src-tauri/src/kv.rs b/src-tauri/src/kv.rs
index a0bf793..d4eab09 100644
--- a/src-tauri/src/kv.rs
+++ b/src-tauri/src/kv.rs
@@ -15,6 +15,8 @@ impl KvStore {
let db_path = get_codeforge_db_path()?;
let conn = Connection::open(&db_path).map_err(|e| format!("打开数据库失败: {}", e))?;
let _ = conn.pragma_update(None, "journal_mode", "WAL");
+ let _ = conn.pragma_update(None, "synchronous", "NORMAL");
+ let _ = conn.busy_timeout(std::time::Duration::from_secs(5));
conn.execute(
"CREATE TABLE IF NOT EXISTS kv_store (
key TEXT PRIMARY KEY,
diff --git a/src-tauri/src/plugins/json.rs b/src-tauri/src/plugins/json.rs
index e228d3a..37ff321 100644
--- a/src-tauri/src/plugins/json.rs
+++ b/src-tauri/src/plugins/json.rs
@@ -37,11 +37,12 @@ impl LanguagePlugin for JsonPlugin {
before_compile: None,
extension: String::from("json"),
execute_home: None,
+ // 输出原始 JSON 内容,由前端 JSON 视图(可折叠树)渲染
run_command: Some(String::from("cat $filename")),
after_compile: None,
template: Some(String::from("{\n \n}")),
timeout: Some(30),
- console_type: Some(String::from("console")),
+ console_type: Some(String::from("json")),
icon_path: None,
}
}
diff --git a/src-tauri/src/plugins/manager.rs b/src-tauri/src/plugins/manager.rs
index 374d7d9..587883c 100644
--- a/src-tauri/src/plugins/manager.rs
+++ b/src-tauri/src/plugins/manager.rs
@@ -29,6 +29,7 @@ use crate::plugins::ruby::RubyPlugin;
use crate::plugins::rust::RustPlugin;
use crate::plugins::scala::ScalaPlugin;
use crate::plugins::shell::ShellPlugin;
+use crate::plugins::sql::SqlPlugin;
use crate::plugins::svg::SvgPlugin;
use crate::plugins::swift::SwiftPlugin;
use crate::plugins::text::TextPlugin;
@@ -75,6 +76,7 @@ impl PluginManager {
("yaml".to_string(), Box::new(YamlPlugin)),
("markdown".to_string(), Box::new(MarkdownPlugin)),
("text".to_string(), Box::new(TextPlugin)),
+ ("sql".to_string(), Box::new(SqlPlugin)),
("php".to_string(), Box::new(PHPPlugin)),
("r".to_string(), Box::new(RPlugin)),
("cangjie".to_string(), Box::new(CangjiePlugin)),
diff --git a/src-tauri/src/plugins/markdown.rs b/src-tauri/src/plugins/markdown.rs
index 6336c8c..89e6fa6 100644
--- a/src-tauri/src/plugins/markdown.rs
+++ b/src-tauri/src/plugins/markdown.rs
@@ -37,11 +37,12 @@ impl LanguagePlugin for MarkdownPlugin {
before_compile: None,
extension: String::from("md"),
execute_home: None,
+ // 输出原始 Markdown,由前端渲染为预览
run_command: Some(String::from("cat $filename")),
after_compile: None,
template: Some(String::from("# 标题\n\n在这里输入 Markdown 内容\n")),
timeout: Some(30),
- console_type: Some(String::from("console")),
+ console_type: Some(String::from("markdown")),
icon_path: None,
}
}
diff --git a/src-tauri/src/plugins/mod.rs b/src-tauri/src/plugins/mod.rs
index eed10ee..c3a86a6 100644
--- a/src-tauri/src/plugins/mod.rs
+++ b/src-tauri/src/plugins/mod.rs
@@ -429,6 +429,7 @@ pub mod ruby;
pub mod rust;
pub mod scala;
pub mod shell;
+pub mod sql;
pub mod svg;
pub mod swift;
pub mod text;
diff --git a/src-tauri/src/plugins/sql.rs b/src-tauri/src/plugins/sql.rs
new file mode 100644
index 0000000..bd353d3
--- /dev/null
+++ b/src-tauri/src/plugins/sql.rs
@@ -0,0 +1,57 @@
+use super::{LanguagePlugin, PluginConfig};
+use std::vec;
+
+pub struct SqlPlugin;
+
+impl LanguagePlugin for SqlPlugin {
+ fn get_order(&self) -> i32 {
+ 28
+ }
+
+ fn get_language_name(&self) -> &'static str {
+ "SQL"
+ }
+
+ fn get_language_key(&self) -> &'static str {
+ "sql"
+ }
+
+ fn get_file_extension(&self) -> String {
+ self.get_config()
+ .map(|config| config.extension.clone())
+ .unwrap_or_else(|| "sql".to_string())
+ }
+
+ fn get_version_args(&self) -> Vec<&'static str> {
+ vec!["--version"]
+ }
+
+ fn get_path_command(&self) -> String {
+ "sqlite3".to_string()
+ }
+
+ fn get_default_config(&self) -> PluginConfig {
+ PluginConfig {
+ enabled: true,
+ language: String::from("sql"),
+ before_compile: None,
+ extension: String::from("sql"),
+ execute_home: None,
+ // 在内存 SQLite 中执行脚本(需要本机有 sqlite3)
+ run_command: Some(String::from("sqlite3 :memory: .read $filename")),
+ after_compile: None,
+ template: Some(String::from(
+ "-- 在这里输入 SQL(默认在内存 SQLite 中执行)\nSELECT 'Hello, CodeForge' AS message;\n",
+ )),
+ timeout: Some(30),
+ console_type: Some(String::from("console")),
+ icon_path: None,
+ }
+ }
+
+ fn get_default_command(&self) -> String {
+ self.get_config()
+ .and_then(|config| config.run_command.clone())
+ .unwrap_or_else(|| "sqlite3".to_string())
+ }
+}
diff --git a/src-tauri/src/plugins/xml.rs b/src-tauri/src/plugins/xml.rs
index 31e4eeb..a93ac0c 100644
--- a/src-tauri/src/plugins/xml.rs
+++ b/src-tauri/src/plugins/xml.rs
@@ -37,11 +37,12 @@ impl LanguagePlugin for XmlPlugin {
before_compile: None,
extension: String::from("xml"),
execute_home: None,
+ // 输出原始 XML,由前端 XML 视图(可折叠树)渲染
run_command: Some(String::from("cat $filename")),
after_compile: None,
template: Some(String::from("\n")),
timeout: Some(30),
- console_type: Some(String::from("console")),
+ console_type: Some(String::from("xml")),
icon_path: None,
}
}
diff --git a/src-tauri/src/plugins/yaml.rs b/src-tauri/src/plugins/yaml.rs
index bd04999..417c5c7 100644
--- a/src-tauri/src/plugins/yaml.rs
+++ b/src-tauri/src/plugins/yaml.rs
@@ -46,11 +46,12 @@ impl LanguagePlugin for YamlPlugin {
before_compile: None,
extension: String::from("yaml,yml"),
execute_home: None,
+ // 输出原始 YAML,由前端 YAML 视图(可折叠树)渲染
run_command: Some(String::from("cat $filename")),
after_compile: None,
template: Some(String::from("# 在这里输入 YAML 内容\n")),
timeout: Some(30),
- console_type: Some(String::from("console")),
+ console_type: Some(String::from("yaml")),
icon_path: None,
}
}
diff --git a/src-tauri/src/snippets.rs b/src-tauri/src/snippets.rs
index 8056db6..27df8b8 100644
--- a/src-tauri/src/snippets.rs
+++ b/src-tauri/src/snippets.rs
@@ -26,6 +26,8 @@ impl Snippets {
let db_path = get_codeforge_db_path()?;
let conn = Connection::open(&db_path).map_err(|e| format!("打开数据库失败: {}", e))?;
let _ = conn.pragma_update(None, "journal_mode", "WAL");
+ let _ = conn.pragma_update(None, "synchronous", "NORMAL");
+ let _ = conn.busy_timeout(std::time::Duration::from_secs(5));
conn.execute(
"CREATE TABLE IF NOT EXISTS snippets (
id TEXT PRIMARY KEY,
diff --git a/src/App.vue b/src/App.vue
index d60189a..02d6127 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -142,6 +142,38 @@
:execution-time="lastExecutionTime"
@clear="clearOutput">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -200,7 +232,7 @@
@close="showTerminal = false; terminalMounted = false"/>
-