Skip to content

Commit c46fd25

Browse files
committed
feat: add Rust backend modules for file operations and project management
Add new IPC command modules that complement the existing fs module: - models.rs: Shared data models (FileContent, ProjectConfig, ProjectInfo) with serde serialization and re-exports of FileEntry/FileMetadata - fs_commands.rs: Additional file system commands - read_file: Returns FileContent with chardetng+encoding_rs encoding detection - delete_entry: Unified file/directory delete with cache invalidation - project.rs: Project management commands - open_project: Native folder picker via tauri_plugin_dialog DialogExt - get_recent_projects: Load from app data dir with in-memory caching - save_recent_project: Persist to recent_projects.json with LRU limit - commands/mod.rs: Command registry documentation and re-exports All commands use async tokio I/O, path validation security, and follow the existing cortex_commands! macro-chain registration pattern.
1 parent faad862 commit c46fd25

File tree

7 files changed

+369
-0
lines changed

7 files changed

+369
-0
lines changed

src-tauri/src/app/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ pub fn register_state(
453453
.manage(crate::extensions::api::debug::DebugApiState::new())
454454
.manage(crate::extensions::api::scm::ScmApiState::new())
455455
.manage(crate::workspace::manager::WorkspaceManagerState::new())
456+
.manage(Arc::new(crate::project::ProjectState::new()))
456457
.manage(crate::remote::port_forwarding::PortForwardingState::new())
457458
.manage(LazyState::new(SandboxState::new))
458459
.manage(crate::remote::tunnel::TunnelState::new())

src-tauri/src/app/workspace_commands.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ macro_rules! workspace_commands {
147147
$crate::fs::fs_get_supported_encodings,
148148
// Workspace edit support
149149
$crate::fs::apply_workspace_edit,
150+
// Unified file commands (fs_commands module)
151+
$crate::fs_commands::read_file,
152+
$crate::fs_commands::delete_entry,
153+
// Project management commands
154+
$crate::project::open_project,
155+
$crate::project::get_recent_projects,
156+
$crate::project::save_recent_project,
150157
// Batch command system for IPC optimization
151158
$crate::batch_ipc::batch_invoke,
152159
$crate::batch::batch_commands,

src-tauri/src/commands/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Command Registry — Central reference for all Tauri IPC commands.
2+
//!
3+
//! Cortex Desktop uses a macro-chain pattern defined in `app/mod.rs` to
4+
//! register commands with `tauri::generate_handler![]`. Each feature area
5+
//! has its own `*_commands.rs` file under `app/` that contributes commands
6+
//! to the chain via the `cortex_commands!` macro.
7+
//!
8+
//! ## Command Groups
9+
//!
10+
//! | Group | File | Description |
11+
//! |-------|------|-------------|
12+
//! | AI | `app/ai_commands.rs` | AI providers, agents, sessions, completions |
13+
//! | Collab | `app/collab_commands.rs` | Real-time collaboration (CRDT, WebSocket) |
14+
//! | Editor | `app/editor_commands.rs` | Folding, symbols, refactoring, snippets |
15+
//! | Extension | `app/extension_commands.rs` | Extension lifecycle, marketplace, WASM/Node host |
16+
//! | Git | `app/git_commands.rs` | Git operations (branch, commit, diff, merge, etc.) |
17+
//! | I18n | `app/i18n_commands.rs` | Internationalization and locale detection |
18+
//! | Misc | `app/misc_commands.rs` | Server, notifications, updates, MCP, window, WSL |
19+
//! | Notebook | `app/notebook_commands.rs` | Jupyter-style notebook kernels |
20+
//! | Remote | `app/remote_commands.rs` | SSH remote development |
21+
//! | Settings | `app/settings_commands.rs` | User/workspace settings, profiles, sync |
22+
//! | Terminal | `app/terminal_commands.rs` | PTY terminal management |
23+
//! | Workspace | `app/workspace_commands.rs` | FS ops, search, testing, tasks, projects |
24+
//!
25+
//! ## Adding New Commands
26+
//!
27+
//! 1. Define `#[tauri::command]` functions in the appropriate module.
28+
//! 2. Add the command paths to the matching `*_commands.rs` macro.
29+
//! 3. If creating a new group, add a new `*_commands.rs` file and insert
30+
//! a chain step in `app/mod.rs`.
31+
//!
32+
//! ## Re-exports
33+
//!
34+
//! This module re-exports key command-bearing modules for discoverability.
35+
36+
pub use crate::fs_commands::{delete_entry, read_file};
37+
pub use crate::project::{get_recent_projects, open_project, save_recent_project};

src-tauri/src/fs_commands.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! File System Commands — Unified IPC commands for file operations.
2+
//!
3+
//! This module provides additional Tauri commands that complement the
4+
//! existing `fs` module. It adds:
5+
//! - `read_file` — returns `FileContent` with encoding detection
6+
//! - `delete_entry` — unified delete for files and directories
7+
//!
8+
//! The bulk of file system operations (`fs_read_file`, `fs_write_file`,
9+
//! `fs_create_file`, `fs_create_directory`, `fs_rename`, `fs_get_metadata`,
10+
//! `fs_watch_directory`, etc.) are defined in the `fs` module and registered
11+
//! via `workspace_commands!`.
12+
13+
use std::path::PathBuf;
14+
use std::sync::Arc;
15+
16+
use tauri::{AppHandle, Manager};
17+
use tracing::info;
18+
19+
use crate::fs::security::{validate_path_for_delete, validate_path_for_read};
20+
use crate::fs::types::DirectoryCache;
21+
use crate::models::FileContent;
22+
23+
/// Read a file and return its content with encoding metadata.
24+
///
25+
/// Uses `chardetng` + `encoding_rs` to detect the file encoding and decode
26+
/// the content accordingly. Returns a `FileContent` struct containing the
27+
/// decoded text, detected encoding name, file size, and path.
28+
#[tauri::command]
29+
pub async fn read_file(path: String) -> Result<FileContent, String> {
30+
let file_path = PathBuf::from(&path);
31+
let validated_path = validate_path_for_read(&file_path)?;
32+
33+
if !validated_path.exists() {
34+
return Err(format!("File does not exist: {}", path));
35+
}
36+
37+
if !validated_path.is_file() {
38+
return Err(format!("Path is not a file: {}", path));
39+
}
40+
41+
let bytes = tokio::fs::read(&validated_path)
42+
.await
43+
.map_err(|e| format!("Failed to read file: {}", e))?;
44+
45+
let size = bytes.len() as u64;
46+
47+
// Detect encoding via BOM first, then chardetng
48+
let (encoding, content) = if let Some((enc, _bom_len)) = encoding_rs::Encoding::for_bom(&bytes)
49+
{
50+
let (decoded, _, _) = enc.decode(&bytes);
51+
(enc.name().to_string(), decoded.into_owned())
52+
} else {
53+
let mut detector = chardetng::EncodingDetector::new();
54+
detector.feed(&bytes, true);
55+
let enc = detector.guess(None, true);
56+
let (decoded, _, _) = enc.decode(&bytes);
57+
(enc.name().to_string(), decoded.into_owned())
58+
};
59+
60+
Ok(FileContent {
61+
content,
62+
encoding,
63+
size,
64+
path,
65+
})
66+
}
67+
68+
/// Delete a file or directory at the given path.
69+
///
70+
/// For directories, performs a recursive delete. Invalidates the directory
71+
/// cache for the parent path and (for directories) any cached subtrees.
72+
#[tauri::command]
73+
pub async fn delete_entry(app: AppHandle, path: String) -> Result<(), String> {
74+
let entry_path = PathBuf::from(&path);
75+
let validated_path = validate_path_for_delete(&entry_path)?;
76+
77+
if !validated_path.exists() {
78+
return Ok(());
79+
}
80+
81+
let cache = app.state::<Arc<DirectoryCache>>();
82+
83+
if let Some(parent) = validated_path.parent() {
84+
cache.invalidate(&parent.to_string_lossy());
85+
}
86+
87+
if validated_path.is_dir() {
88+
cache.invalidate_prefix(&path);
89+
tokio::fs::remove_dir_all(&validated_path)
90+
.await
91+
.map_err(|e| format!("Failed to delete directory: {}", e))?;
92+
info!("Deleted directory: {}", path);
93+
} else {
94+
tokio::fs::remove_file(&validated_path)
95+
.await
96+
.map_err(|e| format!("Failed to delete file: {}", e))?;
97+
info!("Deleted file: {}", path);
98+
}
99+
100+
Ok(())
101+
}

src-tauri/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod batch;
1414
mod batch_ipc;
1515
mod browser;
1616
mod collab;
17+
mod commands;
1718
mod context_server;
1819
mod cortex_engine;
1920
mod cortex_protocol;
@@ -27,15 +28,18 @@ mod extensions;
2728
mod factory;
2829
mod formatter;
2930
mod fs;
31+
mod fs_commands;
3032
mod git;
3133
mod i18n;
3234
mod keybindings;
3335
mod language_selector;
3436
mod lsp;
3537
mod mcp;
38+
mod models;
3639
mod notebook;
3740
mod process;
3841
mod process_utils;
42+
mod project;
3943
mod prompt_store;
4044
mod remote;
4145
mod repl;

src-tauri/src/models.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//! Shared data models for the Cortex Desktop backend.
2+
//!
3+
//! This module provides serde-serializable structs used across IPC commands.
4+
//! Core file system types (`FileEntry`, `FileMetadata`) live in `fs::types`
5+
//! and are re-exported here for convenience.
6+
7+
use serde::{Deserialize, Serialize};
8+
9+
// Re-export core file system types from the fs module
10+
pub use crate::fs::types::{FileEntry, FileMetadata};
11+
12+
/// File content returned by `read_file`, including encoding metadata.
13+
#[derive(Debug, Clone, Serialize, Deserialize)]
14+
#[serde(rename_all = "camelCase")]
15+
pub struct FileContent {
16+
pub content: String,
17+
pub encoding: String,
18+
pub size: u64,
19+
pub path: String,
20+
}
21+
22+
/// Project configuration stored alongside a project.
23+
#[derive(Debug, Clone, Serialize, Deserialize)]
24+
#[serde(rename_all = "camelCase")]
25+
pub struct ProjectConfig {
26+
pub name: String,
27+
pub root_path: String,
28+
#[serde(default)]
29+
pub excluded_paths: Vec<String>,
30+
#[serde(default)]
31+
pub settings: serde_json::Value,
32+
}
33+
34+
/// Information about an opened or recent project.
35+
#[derive(Debug, Clone, Serialize, Deserialize)]
36+
#[serde(rename_all = "camelCase")]
37+
pub struct ProjectInfo {
38+
pub name: String,
39+
pub path: String,
40+
pub last_opened: u64,
41+
}

0 commit comments

Comments
 (0)