Skip to content

Commit ce00dab

Browse files
author
ODAncona
committed
n level recursion
1 parent 17cec7a commit ce00dab

File tree

4 files changed

+122
-67
lines changed

4 files changed

+122
-67
lines changed

crates/code2prompt/src/model/file_tree.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct FileNode {
2424
pub is_selected: bool,
2525
pub children: Vec<FileNode>,
2626
pub level: usize,
27+
pub children_loaded: bool,
2728
}
2829

2930
impl FileTreeState {
@@ -66,6 +67,15 @@ impl FileTreeState {
6667
Self::set_directory_expanded(&mut self.file_tree, &path_str, false);
6768
}
6869

70+
/// Load children for a directory if not already loaded
71+
pub fn load_directory_children(
72+
&mut self,
73+
path: &std::path::Path,
74+
) -> Result<(), std::io::Error> {
75+
let path_str = path.to_string_lossy();
76+
Self::load_children_for_path(&mut self.file_tree, &path_str)
77+
}
78+
6979
fn toggle_directory_selection_recursive(nodes: &mut [FileNode], path: &str, selected: bool) {
7080
for node in nodes {
7181
if node.path.to_string_lossy() == path {
@@ -175,6 +185,39 @@ impl FileTreeState {
175185
// Fallback to contains
176186
text.to_lowercase().contains(&pattern.to_lowercase())
177187
}
188+
189+
/// Load children for a specific directory path
190+
fn load_children_for_path(nodes: &mut [FileNode], path: &str) -> Result<(), std::io::Error> {
191+
for node in nodes {
192+
if node.path.to_string_lossy() == path && node.is_directory && !node.children_loaded {
193+
// Load children from filesystem
194+
let entries = std::fs::read_dir(&node.path)?;
195+
let mut children = Vec::new();
196+
197+
for entry in entries {
198+
let entry = entry?;
199+
let child_path = entry.path();
200+
let child_node = FileNode::new(child_path, node.level + 1);
201+
children.push(child_node);
202+
}
203+
204+
// Sort children (directories first, then alphabetically)
205+
children.sort_by(|a, b| match (a.is_directory, b.is_directory) {
206+
(true, false) => std::cmp::Ordering::Less,
207+
(false, true) => std::cmp::Ordering::Greater,
208+
_ => a.name.cmp(&b.name),
209+
});
210+
211+
node.children = children;
212+
node.children_loaded = true;
213+
return Ok(());
214+
}
215+
if !node.children.is_empty() {
216+
Self::load_children_for_path(&mut node.children, path)?;
217+
}
218+
}
219+
Ok(())
220+
}
178221
}
179222

180223
impl FileNode {
@@ -194,6 +237,7 @@ impl FileNode {
194237
is_selected: false,
195238
children: Vec::new(),
196239
level,
240+
children_loaded: false,
197241
}
198242
}
199243
}

crates/code2prompt/src/model/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,31 @@ impl Model {
255255
if node.is_directory {
256256
let node_path = node.path.clone();
257257
let name = node.name.clone();
258+
let needs_loading = !node.children_loaded;
258259

259260
// Drop the immutable borrow before making mutable changes
260261
drop(visible_nodes);
261262

263+
// First, load children if needed
264+
if needs_loading {
265+
match new_model.file_tree.load_directory_children(&node_path) {
266+
Ok(_) => {
267+
new_model.status_message =
268+
format!("Loaded and expanded {}", name);
269+
}
270+
Err(e) => {
271+
new_model.status_message =
272+
format!("Failed to load children for {}: {}", name, e);
273+
return (new_model, Cmd::None);
274+
}
275+
}
276+
}
277+
278+
// Then expand the directory
262279
new_model.file_tree.expand_directory(&node_path);
263-
new_model.status_message = format!("Expanded {}", name);
280+
if !needs_loading {
281+
new_model.status_message = format!("Expanded {}", name);
282+
}
264283
}
265284
}
266285
(new_model, Cmd::None)

crates/code2prompt/src/tui.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub enum InputMode {
3030
Normal,
3131
Search,
3232
}
33-
use crate::utils::build_file_tree_from_session;
33+
use crate::utils::{build_file_tree_from_session, build_lightweight_file_tree};
3434

3535
pub struct TuiApp {
3636
model: Model,
@@ -575,14 +575,29 @@ impl TuiApp {
575575
}
576576

577577
crate::model::Cmd::RefreshFileTree => {
578-
// Build file tree using session data
579-
match build_file_tree_from_session(&mut self.model.session) {
580-
Ok(tree) => {
581-
self.model.file_tree.set_file_tree(tree);
582-
self.model.status_message = "File tree refreshed".to_string();
578+
// Use lightweight tree for initial navigation, full tree only when needed
579+
if self.model.session.data.files.is_none() {
580+
// First time: use lightweight tree for fast startup
581+
match build_lightweight_file_tree(&self.model.session.config.path) {
582+
Ok(tree) => {
583+
self.model.file_tree.set_file_tree(tree);
584+
self.model.status_message =
585+
"File tree loaded (lightweight mode)".to_string();
586+
}
587+
Err(e) => {
588+
self.model.status_message = format!("Error loading files: {}", e);
589+
}
583590
}
584-
Err(e) => {
585-
self.model.status_message = format!("Error loading files: {}", e);
591+
} else {
592+
// Subsequent refreshes: use full tree with session data
593+
match build_file_tree_from_session(&mut self.model.session) {
594+
Ok(tree) => {
595+
self.model.file_tree.set_file_tree(tree);
596+
self.model.status_message = "File tree refreshed".to_string();
597+
}
598+
Err(e) => {
599+
self.model.status_message = format!("Error loading files: {}", e);
600+
}
586601
}
587602
}
588603
}

crates/code2prompt/src/utils.rs

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ use crate::model::FileNode;
1212

1313
/// Build a file tree using session data from core traversal
1414
pub fn build_file_tree_from_session(session: &mut Code2PromptSession) -> Result<Vec<FileNode>> {
15-
// Load codebase data using the session
16-
session
17-
.load_codebase()
18-
.context("Failed to load codebase from session")?;
15+
// Only load codebase if not already loaded (performance optimization)
16+
if session.data.files.is_none() {
17+
session
18+
.load_codebase()
19+
.context("Failed to load codebase from session")?;
20+
}
1921

2022
// Get the files data from session
2123
let files_data = session
@@ -42,12 +44,9 @@ pub fn build_file_tree_from_session(session: &mut Code2PromptSession) -> Result<
4244
Ok(root_nodes)
4345
}
4446

45-
/// Build directory hierarchy from file paths - simplified approach
46-
fn build_directory_hierarchy(
47-
root: &std::path::Path,
48-
file_paths: &[String],
49-
) -> Result<Vec<FileNode>> {
50-
let entries = fs::read_dir(root).context("Failed to read root directory")?;
47+
/// Build a lightweight file tree for navigation only (no file content loading)
48+
pub fn build_lightweight_file_tree(root_path: &std::path::Path) -> Result<Vec<FileNode>> {
49+
let entries = fs::read_dir(root_path).context("Failed to read root directory")?;
5150
let mut root_children = Vec::new();
5251

5352
for entry in entries {
@@ -56,81 +55,59 @@ fn build_directory_hierarchy(
5655

5756
let mut node = FileNode::new(path, 0);
5857

59-
// Check if this file/directory should be selected based on session data
60-
let relative_path = node.path.strip_prefix(root).unwrap_or(&node.path);
61-
let relative_str = relative_path.to_string_lossy();
62-
63-
node.is_selected = file_paths.iter().any(|file_path| {
64-
file_path == &relative_str || file_path.starts_with(&format!("{}/", relative_str))
65-
});
66-
67-
// For directories, recursively load if they contain session files
58+
// For directories, mark as not loaded for lazy loading
6859
if node.is_directory {
69-
let has_session_files = file_paths
70-
.iter()
71-
.any(|file_path| file_path.starts_with(&format!("{}/", relative_str)));
72-
73-
if has_session_files {
74-
// Load children for this directory since it contains files from session
75-
if let Ok(children) = build_directory_children(root, &node.path, file_paths, 1) {
76-
node.children = children;
77-
node.is_expanded = true;
78-
}
79-
}
60+
node.children_loaded = false;
8061
}
8162

63+
// Don't pre-select any files in lightweight mode
64+
node.is_selected = false;
65+
8266
root_children.push(node);
8367
}
8468

69+
// Sort nodes
70+
root_children.sort_by(|a, b| match (a.is_directory, b.is_directory) {
71+
(true, false) => std::cmp::Ordering::Less,
72+
(false, true) => std::cmp::Ordering::Greater,
73+
_ => a.name.cmp(&b.name),
74+
});
75+
8576
Ok(root_children)
8677
}
8778

88-
/// Recursively build children for a directory
89-
fn build_directory_children(
79+
/// Build directory hierarchy from file paths - simplified approach
80+
fn build_directory_hierarchy(
9081
root: &std::path::Path,
91-
dir_path: &std::path::Path,
9282
file_paths: &[String],
93-
level: usize,
9483
) -> Result<Vec<FileNode>> {
95-
if level > 3 {
96-
return Ok(Vec::new()); // Prevent too deep recursion
97-
}
98-
99-
let entries = fs::read_dir(dir_path).context("Failed to read directory")?;
100-
let mut children = Vec::new();
84+
let entries = fs::read_dir(root).context("Failed to read root directory")?;
85+
let mut root_children = Vec::new();
10186

10287
for entry in entries {
10388
let entry = entry.context("Failed to read directory entry")?;
10489
let path = entry.path();
10590

106-
let mut node = FileNode::new(path, level);
91+
let mut node = FileNode::new(path, 0);
10792

108-
// Check selection against session data
93+
// Check if this file/directory should be selected based on session data
10994
let relative_path = node.path.strip_prefix(root).unwrap_or(&node.path);
11095
let relative_str = relative_path.to_string_lossy();
11196

112-
node.is_selected = file_paths.contains(&relative_str.to_string());
97+
node.is_selected = file_paths.iter().any(|file_path| {
98+
file_path == &relative_str || file_path.starts_with(&format!("{}/", relative_str))
99+
});
113100

114-
// Recursively load subdirectories that contain session files
101+
// For directories, mark as not loaded for lazy loading
102+
// This prevents initial recursive loading and improves performance
115103
if node.is_directory {
116-
let has_session_files = file_paths
117-
.iter()
118-
.any(|file_path| file_path.starts_with(&format!("{}/", relative_str)));
119-
120-
if has_session_files {
121-
if let Ok(grandchildren) =
122-
build_directory_children(root, &node.path, file_paths, level + 1)
123-
{
124-
node.children = grandchildren;
125-
node.is_expanded = true;
126-
}
127-
}
104+
node.children_loaded = false;
128105
}
129106

130-
children.push(node);
107+
root_children.push(node);
131108
}
132109

133-
Ok(children)
110+
Ok(root_children)
134111
}
135112

136113
/// Sort file nodes (directories first, then alphabetically)

0 commit comments

Comments
 (0)