Skip to content

Commit bae4515

Browse files
authored
Merge pull request #112 from qianmoQ/dev-26.3.0
Enhance workspace features with tabs, editor commands, and Git links
2 parents d1bc409 + 2a674e2 commit bae4515

19 files changed

Lines changed: 2263 additions & 1434 deletions

docs/pnpm-lock.yaml

Lines changed: 1691 additions & 1378 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@codemirror/state": "^6.5.2",
3333
"@codemirror/view": "^6.38.1",
3434
"@open-rpc/client-js": "^2.0.0",
35+
"@replit/codemirror-indentation-markers": "^6.5.3",
3536
"@replit/codemirror-minimap": "^0.5.2",
3637
"@tauri-apps/api": "^2",
3738
"@tauri-apps/plugin-dialog": "^2.3.2",

src-tauri/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct EditorConfig {
2323
pub show_minimap: Option<bool>, // 是否显示代码缩略图
2424
pub show_sticky_scroll: Option<bool>, // 是否启用粘性滚动
2525
pub word_wrap: Option<bool>, // 是否自动换行
26+
pub show_indent_guides: Option<bool>, // 是否显示缩进参考线
2627
pub layout: Option<String>, // 编辑器/控制台布局: horizontal | vertical | editor
2728
pub last_direction: Option<String>, // 仅编辑器模式下控制台弹出方向: horizontal | vertical
2829
pub max_open_file_size: Option<u32>, // 打开文件大小上限(MB),超过则拒绝打开
@@ -75,6 +76,7 @@ impl Default for AppConfig {
7576
show_minimap: Some(false),
7677
show_sticky_scroll: Some(false),
7778
word_wrap: Some(false),
79+
show_indent_guides: Some(false),
7880
layout: Some("horizontal".to_string()),
7981
last_direction: Some("horizontal".to_string()),
8082
max_open_file_size: Some(5),
@@ -148,6 +150,7 @@ impl ConfigManager {
148150
show_minimap: Some(false),
149151
show_sticky_scroll: Some(false),
150152
word_wrap: Some(false),
153+
show_indent_guides: Some(false),
151154
layout: Some("horizontal".to_string()),
152155
last_direction: Some("horizontal".to_string()),
153156
max_open_file_size: Some(5),
@@ -274,6 +277,7 @@ impl ConfigManager {
274277
show_minimap: Some(false),
275278
show_sticky_scroll: Some(false),
276279
word_wrap: Some(false),
280+
show_indent_guides: Some(false),
277281
layout: Some("horizontal".to_string()),
278282
last_direction: Some("horizontal".to_string()),
279283
max_open_file_size: Some(5),

src-tauri/src/filesystem.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,86 @@ pub async fn git_diff(root: String) -> Result<String, String> {
469469
.map_err(|e| format!("git 任务失败: {}", e))?
470470
}
471471

472+
/// 已暂存改动的 diff(git diff --cached),用于 AI 生成提交信息
473+
#[tauri::command]
474+
pub async fn git_staged_diff(root: String) -> Result<String, String> {
475+
tokio::task::spawn_blocking(move || {
476+
let output = std::process::Command::new("git")
477+
.args(["-C", &root, "diff", "--cached"])
478+
.output()
479+
.map_err(|e| format!("执行 git 失败: {}", e))?;
480+
if !output.status.success() {
481+
let err = String::from_utf8_lossy(&output.stderr);
482+
return Err(format!("git diff --cached 失败:{}", err.trim()));
483+
}
484+
let mut diff = String::from_utf8_lossy(&output.stdout).to_string();
485+
if diff.len() > MAX_DIFF_LEN {
486+
diff.truncate(MAX_DIFF_LEN);
487+
diff.push_str("\n…(diff 过长已截断)");
488+
}
489+
Ok(diff)
490+
})
491+
.await
492+
.map_err(|e| format!("git 任务失败: {}", e))?
493+
}
494+
495+
/// 生成当前文件指定行的远程仓库永久链接(基于 origin 远程与当前 HEAD 提交)。
496+
/// 支持 GitHub / Gitee(/blob/)与 GitLab(/-/blob/),SSH 与 HTTPS 远程均可解析。
497+
#[tauri::command]
498+
pub async fn git_permalink(root: String, rel_path: String, line: u32) -> Result<String, String> {
499+
tokio::task::spawn_blocking(move || {
500+
let git = |args: &[&str]| -> Result<String, String> {
501+
let mut a: Vec<&str> = vec!["-C", &root];
502+
a.extend_from_slice(args);
503+
let out = std::process::Command::new("git")
504+
.args(&a)
505+
.output()
506+
.map_err(|e| format!("执行 git 失败: {}", e))?;
507+
if !out.status.success() {
508+
return Err(String::from_utf8_lossy(&out.stderr).trim().to_string());
509+
}
510+
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
511+
};
512+
513+
let remote =
514+
git(&["remote", "get-url", "origin"]).map_err(|_| "未找到 origin 远程".to_string())?;
515+
let sha = git(&["rev-parse", "HEAD"]).map_err(|_| "无法获取当前提交".to_string())?;
516+
517+
// 解析远程地址 → (host, owner/repo)
518+
let raw = remote.trim();
519+
let raw = raw.strip_suffix(".git").unwrap_or(raw);
520+
let (host, path) = if let Some(rest) = raw.strip_prefix("git@") {
521+
// scp 形式:git@host:owner/repo
522+
rest.split_once(':')
523+
.map(|(h, p)| (h.to_string(), p.trim_start_matches('/').to_string()))
524+
.ok_or_else(|| "无法解析远程地址".to_string())?
525+
} else {
526+
let rest = raw
527+
.strip_prefix("https://")
528+
.or_else(|| raw.strip_prefix("http://"))
529+
.or_else(|| raw.strip_prefix("ssh://"))
530+
.ok_or_else(|| "无法解析远程地址".to_string())?;
531+
let rest = rest.strip_prefix("git@").unwrap_or(rest);
532+
rest.split_once('/')
533+
.map(|(h, p)| (h.to_string(), p.trim_start_matches('/').to_string()))
534+
.ok_or_else(|| "无法解析远程地址".to_string())?
535+
};
536+
537+
let rel = rel_path.trim_start_matches(['/', '\\']).replace('\\', "/");
538+
let blob = if host.contains("gitlab") {
539+
"/-/blob/"
540+
} else {
541+
"/blob/"
542+
};
543+
Ok(format!(
544+
"https://{}/{}{}{}/{}#L{}",
545+
host, path, blob, sha, rel, line
546+
))
547+
})
548+
.await
549+
.map_err(|e| format!("git 任务失败: {}", e))?
550+
}
551+
472552
// ===== Git 源代码管理 =====
473553

474554
/// 克隆远程仓库到 dir 下,返回克隆出的仓库目录路径。

src-tauri/src/main.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ use crate::filesystem::{
7171
git_file_diff, git_file_head, git_get_identity, git_get_signing, git_graph, git_hook_delete,
7272
git_hook_read, git_hook_save, git_hooks, git_ignore_add, git_ignore_append_block, git_init,
7373
git_log, git_log_file, git_merge, git_op_abort, git_op_continue, git_op_skip, git_op_state,
74-
git_pull, git_pull_rebase, git_push, git_push_force, git_push_tags, git_rebase_interactive,
75-
git_reflog, git_remote_add, git_remote_branches, git_remote_remove, git_remotes, git_reset,
76-
git_restore_file, git_revert, git_set_identity, git_set_signing, git_set_upstream, git_show,
77-
git_stage, git_stash_apply, git_stash_drop, git_stash_list, git_stash_pop, git_stash_push,
78-
git_stash_show, git_status, git_submodule_sync, git_submodule_update, git_submodules,
79-
git_tag_create, git_tag_delete, git_tags, git_unstage, git_worktree_add, git_worktree_prune,
80-
git_worktree_remove, git_worktrees, list_files, read_directory_tree, read_file_lines,
81-
read_file_text, rename_path, replace_in_files, resolve_editorconfig, reveal_path,
82-
search_in_files, watch_directory, write_file_text,
74+
git_permalink, git_pull, git_pull_rebase, git_push, git_push_force, git_push_tags,
75+
git_rebase_interactive, git_reflog, git_remote_add, git_remote_branches, git_remote_remove,
76+
git_remotes, git_reset, git_restore_file, git_revert, git_set_identity, git_set_signing,
77+
git_set_upstream, git_show, git_stage, git_staged_diff, git_stash_apply, git_stash_drop,
78+
git_stash_list, git_stash_pop, git_stash_push, git_stash_show, git_status, git_submodule_sync,
79+
git_submodule_update, git_submodules, git_tag_create, git_tag_delete, git_tags, git_unstage,
80+
git_worktree_add, git_worktree_prune, git_worktree_remove, git_worktrees, list_files,
81+
read_directory_tree, read_file_lines, read_file_text, rename_path, replace_in_files,
82+
resolve_editorconfig, reveal_path, search_in_files, watch_directory, write_file_text,
8383
};
8484
use crate::gitignore_templates::{
8585
GitignoreStore, gitignore_template_delete, gitignore_template_save, gitignore_templates_list,
@@ -232,6 +232,8 @@ fn main() {
232232
search_in_files,
233233
replace_in_files,
234234
git_diff,
235+
git_staged_diff,
236+
git_permalink,
235237
git_file_diff,
236238
git_apply_patch,
237239
git_status,

0 commit comments

Comments
 (0)