优化页面布局
This commit is contained in:
82
src-tauri/Cargo.lock
generated
82
src-tauri/Cargo.lock
generated
@@ -631,6 +631,7 @@ dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"log",
|
||||
"notify",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"regex",
|
||||
@@ -1439,6 +1440,15 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
@@ -2292,6 +2302,26 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intl-memoizer"
|
||||
version = "0.5.3"
|
||||
@@ -2492,6 +2522,26 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.2"
|
||||
@@ -2709,6 +2759,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
@@ -2823,6 +2885,24 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-rust"
|
||||
version = "4.11.7"
|
||||
@@ -5283,7 +5363,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.4",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
|
@@ -52,6 +52,7 @@ sha2 = "0.10"
|
||||
zstd = "0.13"
|
||||
uuid = { version = "1.6", features = ["v4", "serde"] }
|
||||
walkdir = "2"
|
||||
notify = { version = "6.1", default-features = false, features = ["macos_fsevent"] }
|
||||
serde_yaml = "0.9"
|
||||
fluent = "0.16"
|
||||
fluent-bundle = "0.15"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use tauri::Emitter;
|
||||
use tauri::State;
|
||||
use crate::file_watcher::FileWatcherState;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct FileNode {
|
||||
@@ -245,22 +246,39 @@ pub async fn get_file_info(path: String) -> Result<FileNode, String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 监听文件系统变化(简化版本)
|
||||
/// 监听文件系统变化
|
||||
#[tauri::command]
|
||||
pub async fn watch_directory(
|
||||
app: tauri::AppHandle,
|
||||
watcher_state: State<'_, FileWatcherState>,
|
||||
path: String,
|
||||
recursive: Option<bool>,
|
||||
) -> Result<(), String> {
|
||||
let recursive = recursive.unwrap_or(false);
|
||||
|
||||
watcher_state.with_manager(|manager| {
|
||||
manager.watch_path(&path, recursive)
|
||||
})
|
||||
}
|
||||
|
||||
/// 停止监听指定路径
|
||||
#[tauri::command]
|
||||
pub async fn unwatch_directory(
|
||||
watcher_state: State<'_, FileWatcherState>,
|
||||
path: String,
|
||||
) -> Result<(), String> {
|
||||
// 这里可以集成 notify crate 来实现文件系统监听
|
||||
// 为了简化,先返回成功
|
||||
|
||||
// 发送测试事件
|
||||
app.emit("file-system-change", FileSystemChange {
|
||||
path: path.clone(),
|
||||
change_type: String::from("watching"),
|
||||
}).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(())
|
||||
watcher_state.with_manager(|manager| {
|
||||
manager.unwatch_path(&path)
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取当前监听的路径列表
|
||||
#[tauri::command]
|
||||
pub async fn get_watched_paths(
|
||||
watcher_state: State<'_, FileWatcherState>,
|
||||
) -> Result<Vec<String>, String> {
|
||||
watcher_state.with_manager(|manager| {
|
||||
Ok(manager.get_watched_paths())
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取文件树(简化版,供文件浏览器使用)
|
||||
|
195
src-tauri/src/file_watcher.rs
Normal file
195
src-tauri/src/file_watcher.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct FileChangeEvent {
|
||||
pub path: String,
|
||||
pub change_type: String,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
pub struct FileWatcherManager {
|
||||
watchers: Arc<Mutex<HashMap<String, RecommendedWatcher>>>,
|
||||
app_handle: AppHandle,
|
||||
// 用于去重,避免短时间内重复事件
|
||||
last_events: Arc<Mutex<HashMap<PathBuf, SystemTime>>>,
|
||||
}
|
||||
|
||||
impl FileWatcherManager {
|
||||
pub fn new(app_handle: AppHandle) -> Self {
|
||||
Self {
|
||||
watchers: Arc::new(Mutex::new(HashMap::new())),
|
||||
app_handle,
|
||||
last_events: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 监听指定路径(文件或目录)
|
||||
pub fn watch_path(&self, path: &str, recursive: bool) -> Result<(), String> {
|
||||
let path_buf = PathBuf::from(path);
|
||||
|
||||
// 检查路径是否存在
|
||||
if !path_buf.exists() {
|
||||
return Err(format!("Path does not exist: {}", path));
|
||||
}
|
||||
|
||||
// 检查是否已经在监听
|
||||
{
|
||||
let watchers = self.watchers.lock().unwrap();
|
||||
if watchers.contains_key(path) {
|
||||
log::debug!("Already watching path: {}", path);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let app_handle = self.app_handle.clone();
|
||||
let last_events = self.last_events.clone();
|
||||
let watch_path = path.to_string();
|
||||
|
||||
// 创建文件监听器
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res: Result<Event, notify::Error>| {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
Self::handle_event(event, &app_handle, &last_events);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Watch error: {:?}", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
Config::default()
|
||||
.with_poll_interval(Duration::from_secs(1))
|
||||
.with_compare_contents(false),
|
||||
).map_err(|e| format!("Failed to create watcher: {}", e))?;
|
||||
|
||||
// 开始监听
|
||||
let mode = if recursive {
|
||||
RecursiveMode::Recursive
|
||||
} else {
|
||||
RecursiveMode::NonRecursive
|
||||
};
|
||||
|
||||
watcher
|
||||
.watch(&path_buf, mode)
|
||||
.map_err(|e| format!("Failed to watch path: {}", e))?;
|
||||
|
||||
// 存储监听器
|
||||
let mut watchers = self.watchers.lock().unwrap();
|
||||
watchers.insert(watch_path, watcher);
|
||||
|
||||
log::info!("Started watching path: {} (recursive: {})", path, recursive);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 停止监听指定路径
|
||||
pub fn unwatch_path(&self, path: &str) -> Result<(), String> {
|
||||
let mut watchers = self.watchers.lock().unwrap();
|
||||
|
||||
if watchers.remove(path).is_some() {
|
||||
log::info!("Stopped watching path: {}", path);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Path not being watched: {}", path))
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止所有监听
|
||||
#[allow(dead_code)]
|
||||
pub fn unwatch_all(&self) {
|
||||
let mut watchers = self.watchers.lock().unwrap();
|
||||
let count = watchers.len();
|
||||
watchers.clear();
|
||||
log::info!("Stopped watching {} paths", count);
|
||||
}
|
||||
|
||||
/// 处理文件系统事件
|
||||
fn handle_event(event: Event, app_handle: &AppHandle, last_events: &Arc<Mutex<HashMap<PathBuf, SystemTime>>>) {
|
||||
// 过滤不需要的事件
|
||||
let change_type = match event.kind {
|
||||
EventKind::Create(_) => "created",
|
||||
EventKind::Modify(_) => "modified",
|
||||
EventKind::Remove(_) => "deleted",
|
||||
_ => return, // 忽略其他事件(包括 Access 等)
|
||||
};
|
||||
|
||||
// 处理每个受影响的路径
|
||||
for path in event.paths {
|
||||
// 去重:检查是否在短时间内已经发送过相同路径的事件
|
||||
let now = SystemTime::now();
|
||||
let should_emit = {
|
||||
let mut last_events = last_events.lock().unwrap();
|
||||
|
||||
if let Some(last_time) = last_events.get(&path) {
|
||||
// 如果距离上次事件不到500ms,忽略
|
||||
if now.duration_since(*last_time).unwrap_or(Duration::ZERO) < Duration::from_millis(500) {
|
||||
false
|
||||
} else {
|
||||
last_events.insert(path.clone(), now);
|
||||
true
|
||||
}
|
||||
} else {
|
||||
last_events.insert(path.clone(), now);
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if should_emit {
|
||||
let change_event = FileChangeEvent {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
change_type: change_type.to_string(),
|
||||
timestamp: now
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
};
|
||||
|
||||
// 发送事件到前端
|
||||
if let Err(e) = app_handle.emit("file-system-change", &change_event) {
|
||||
log::error!("Failed to emit file change event: {}", e);
|
||||
} else {
|
||||
log::debug!(
|
||||
"Emitted file change event: {} ({})",
|
||||
change_event.path,
|
||||
change_event.change_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取当前监听的路径列表
|
||||
pub fn get_watched_paths(&self) -> Vec<String> {
|
||||
let watchers = self.watchers.lock().unwrap();
|
||||
watchers.keys().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
// 全局文件监听管理器状态
|
||||
pub struct FileWatcherState(pub Arc<Mutex<Option<FileWatcherManager>>>);
|
||||
|
||||
impl FileWatcherState {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(Mutex::new(None)))
|
||||
}
|
||||
|
||||
pub fn init(&self, app_handle: AppHandle) {
|
||||
let mut state = self.0.lock().unwrap();
|
||||
*state = Some(FileWatcherManager::new(app_handle));
|
||||
}
|
||||
|
||||
pub fn with_manager<F, R>(&self, f: F) -> Result<R, String>
|
||||
where
|
||||
F: FnOnce(&FileWatcherManager) -> Result<R, String>,
|
||||
{
|
||||
let state = self.0.lock().unwrap();
|
||||
match state.as_ref() {
|
||||
Some(manager) => f(manager),
|
||||
None => Err("File watcher manager not initialized".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ pub mod claude_config;
|
||||
pub mod commands;
|
||||
pub mod process;
|
||||
pub mod i18n;
|
||||
pub mod file_watcher;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
|
@@ -7,6 +7,7 @@ mod commands;
|
||||
mod process;
|
||||
mod i18n;
|
||||
mod claude_config;
|
||||
mod file_watcher;
|
||||
|
||||
use checkpoint::state::CheckpointState;
|
||||
use commands::agents::{
|
||||
@@ -68,12 +69,13 @@ use commands::packycode_nodes::{
|
||||
};
|
||||
use commands::filesystem::{
|
||||
read_directory_tree, search_files_by_name, get_file_info, watch_directory,
|
||||
read_file, write_file, get_file_tree,
|
||||
read_file, write_file, get_file_tree, unwatch_directory, get_watched_paths,
|
||||
};
|
||||
use commands::git::{
|
||||
get_git_status, get_git_history, get_git_branches, get_git_diff, get_git_commits,
|
||||
};
|
||||
use process::ProcessRegistryState;
|
||||
use file_watcher::FileWatcherState;
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
|
||||
@@ -162,6 +164,11 @@ fn main() {
|
||||
|
||||
// Initialize process registry
|
||||
app.manage(ProcessRegistryState::default());
|
||||
|
||||
// Initialize file watcher state
|
||||
let file_watcher_state = FileWatcherState::new();
|
||||
file_watcher_state.init(app.handle().clone());
|
||||
app.manage(file_watcher_state);
|
||||
|
||||
// Initialize Claude process state
|
||||
app.manage(ClaudeProcessState::default());
|
||||
@@ -332,6 +339,8 @@ fn main() {
|
||||
search_files_by_name,
|
||||
get_file_info,
|
||||
watch_directory,
|
||||
unwatch_directory,
|
||||
get_watched_paths,
|
||||
read_file,
|
||||
write_file,
|
||||
get_file_tree,
|
||||
|
Reference in New Issue
Block a user