Skip to content

Commit 98799f8

Browse files
fix(file): change the ui of file and add file edit
1 parent 0429038 commit 98799f8

11 files changed

Lines changed: 1499 additions & 606 deletions

File tree

desktop/src-tauri/Cargo.lock

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

desktop/src-tauri/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ notify = "6"
4545
notify-debouncer-mini = "0.4"
4646
arboard = "3.6.1"
4747
tauri-plugin-os = "2"
48+
walkdir = "2"
49+
regex = "1"

desktop/src-tauri/src/commands/fs.rs

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ pub fn fs_list_dir(path: String) -> Result<Vec<FileNode>, String> {
5656
.map_err(|e| e.to_string())?
5757
.filter_map(|e| e.ok())
5858
.filter(|e| {
59-
// hide hidden files and node_modules/target
6059
let name = e.file_name().to_string_lossy().to_string();
6160
!name.starts_with('.') && name != "node_modules" && name != "target"
6261
})
@@ -86,4 +85,113 @@ pub fn fs_list_dir(path: String) -> Result<Vec<FileNode>, String> {
8685

8786
entries.sort_by(|a, b| b.is_dir.cmp(&a.is_dir).then(a.name.cmp(&b.name)));
8887
Ok(entries)
88+
}
89+
90+
// ── NEW: write file ───────────────────────────────────────────────────────────
91+
#[tauri::command]
92+
pub fn fs_write_file(path: String, content: String) -> Result<(), String> {
93+
std::fs::write(&path, content).map_err(|e| e.to_string())
94+
}
95+
96+
// ── NEW: search in files ──────────────────────────────────────────────────────
97+
#[derive(serde::Serialize, Clone)]
98+
pub struct SearchMatch {
99+
pub path: String,
100+
pub line: usize,
101+
pub col_start: usize,
102+
pub col_end: usize,
103+
pub text: String,
104+
}
105+
106+
#[tauri::command]
107+
pub fn fs_search_in_files(
108+
root: String,
109+
query: String,
110+
case_sensitive: bool,
111+
use_regex: bool,
112+
include_exts: Vec<String>, // e.g. ["ts","rs"] — empty = all
113+
exclude_dirs: Vec<String>, // e.g. ["node_modules",".git","target"]
114+
) -> Result<Vec<SearchMatch>, String> {
115+
use std::io::{BufRead, BufReader};
116+
117+
if query.is_empty() {
118+
return Ok(vec![]);
119+
}
120+
121+
// Build regex or plain pattern
122+
let pattern: Box<dyn Fn(&str) -> Option<(usize, usize)> + Send> = if use_regex {
123+
let flags = if case_sensitive { "" } else { "(?i)" };
124+
let re = regex::Regex::new(&format!("{}{}", flags, &query))
125+
.map_err(|e| e.to_string())?;
126+
Box::new(move |line: &str| {
127+
re.find(line).map(|m| (m.start(), m.end()))
128+
})
129+
} else {
130+
let needle = if case_sensitive { query.clone() } else { query.to_lowercase() };
131+
Box::new(move |line: &str| {
132+
let hay = if case_sensitive { line.to_string() } else { line.to_lowercase() };
133+
hay.find(&needle).map(|s| (s, s + needle.len()))
134+
})
135+
};
136+
137+
let default_excludes = ["node_modules", ".git", "target", "dist", ".next", "out"];
138+
let mut results: Vec<SearchMatch> = Vec::new();
139+
140+
for entry in walkdir::WalkDir::new(&root)
141+
.follow_links(false)
142+
.into_iter()
143+
.filter_entry(|e| {
144+
let name = e.file_name().to_string_lossy();
145+
// skip hidden
146+
if name.starts_with('.') { return false; }
147+
// skip default + user-supplied exclude dirs
148+
if e.file_type().is_dir() {
149+
if default_excludes.contains(&name.as_ref()) { return false; }
150+
if exclude_dirs.iter().any(|x| x == name.as_ref()) { return false; }
151+
}
152+
true
153+
})
154+
{
155+
let entry = match entry { Ok(e) => e, Err(_) => continue };
156+
if entry.file_type().is_dir() { continue; }
157+
158+
let path_str = entry.path().to_string_lossy().to_string();
159+
160+
// extension filter
161+
if !include_exts.is_empty() {
162+
let ext = entry.path()
163+
.extension()
164+
.and_then(|e| e.to_str())
165+
.unwrap_or("")
166+
.to_lowercase();
167+
if !include_exts.iter().any(|x| x.to_lowercase() == ext) {
168+
continue;
169+
}
170+
}
171+
172+
// skip large files (> 2 MB)
173+
if entry.metadata().map(|m| m.len()).unwrap_or(0) > 2_097_152 {
174+
continue;
175+
}
176+
177+
let file = match std::fs::File::open(entry.path()) {
178+
Ok(f) => f, Err(_) => continue,
179+
};
180+
181+
for (i, line) in BufReader::new(file).lines().enumerate() {
182+
let line = match line { Ok(l) => l, Err(_) => break };
183+
if let Some((cs, ce)) = pattern(&line) {
184+
results.push(SearchMatch {
185+
path: path_str.clone(),
186+
line: i + 1,
187+
col_start: cs,
188+
col_end: ce,
189+
text: line.trim_end().to_string(),
190+
});
191+
if results.len() >= 2000 { return Ok(results); }
192+
}
193+
}
194+
}
195+
196+
Ok(results)
89197
}

desktop/src-tauri/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ pub fn run() {
130130
commands::fs::fs_create_dir,
131131
commands::fs::fs_create_file,
132132
commands::fs::copy_to_clipboard,
133+
commands::fs::fs_write_file, // ← NEW
134+
commands::fs::fs_search_in_files,
133135
// ── Browser ──────────────────────────────────────────────────────
134136
browser::webview::browser_create,
135137
browser::webview::browser_destroy,

desktop/src/App.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CreateRunboxModal } from "./sidebar/CreateRunboxModal";
88
import { IcoBrain, IcoFiles, IcoGit } from "./shared/icons";
99

1010
import { C, SANS, loadRunboxes, saveRunboxes } from "./shared/constants";
11-
import { NotificationToasts, useMemorySummaryBackfill } from "./shared/Notificationsystem";
11+
import { useMemorySummaryBackfill } from "./shared/Notificationsystem";
1212
import type { Runbox } from "./shared/types";
1313

1414
const SIDEBAR_W = 220;
@@ -201,7 +201,6 @@ export default function App() {
201201
/>
202202
)}
203203

204-
<NotificationToasts />
205204

206205
<style>{`
207206
@keyframes sbFadeUp { from{opacity:0;transform:translateY(8px) scale(.98)} to{opacity:1;transform:translateY(0) scale(1)} }

0 commit comments

Comments
 (0)