fix: view can recognise a dir (#4701)
This commit is contained in:
@@ -2963,6 +2963,157 @@ mod tests {
|
||||
assert!(text.text.contains("100: Line 100"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_text_editor_view_directory() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let temp_path = temp_dir.path();
|
||||
|
||||
// Set the current directory before creating the server
|
||||
std::env::set_current_dir(&temp_path).unwrap();
|
||||
|
||||
// Create some test files and directories
|
||||
fs::create_dir(temp_path.join("subdir1")).unwrap();
|
||||
fs::create_dir(temp_path.join("subdir2")).unwrap();
|
||||
fs::create_dir(temp_path.join("another_dir")).unwrap();
|
||||
|
||||
fs::write(temp_path.join("file1.txt"), "content1").unwrap();
|
||||
fs::write(temp_path.join("file2.rs"), "content2").unwrap();
|
||||
fs::write(temp_path.join("README.md"), "content3").unwrap();
|
||||
|
||||
let server = create_test_server();
|
||||
|
||||
// Test viewing a directory
|
||||
let result = server
|
||||
.text_editor(Parameters(TextEditorParams {
|
||||
command: "view".to_string(),
|
||||
path: temp_path.to_str().unwrap().to_string(),
|
||||
view_range: None,
|
||||
file_text: None,
|
||||
old_str: None,
|
||||
new_str: None,
|
||||
insert_line: None,
|
||||
diff: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let content = result.unwrap().content;
|
||||
assert_eq!(content.len(), 1);
|
||||
|
||||
// Check the content is a text message with directory listing
|
||||
let text_content = content[0].as_text().expect("Expected text content");
|
||||
let output = &text_content.text;
|
||||
|
||||
// Check that it identifies as a directory
|
||||
assert!(output.contains("is a directory"));
|
||||
assert!(output.contains("Contents:"));
|
||||
|
||||
// Check directories are listed with trailing slash
|
||||
assert!(output.contains("Directories:"));
|
||||
assert!(output.contains("another_dir/"));
|
||||
assert!(output.contains("subdir1/"));
|
||||
assert!(output.contains("subdir2/"));
|
||||
|
||||
// Check files are listed
|
||||
assert!(output.contains("Files:"));
|
||||
assert!(output.contains("file1.txt"));
|
||||
assert!(output.contains("file2.rs"));
|
||||
assert!(output.contains("README.md"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_text_editor_view_directory_with_many_files() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let temp_path = temp_dir.path();
|
||||
|
||||
// Set the current directory before creating the server
|
||||
std::env::set_current_dir(&temp_path).unwrap();
|
||||
|
||||
// Create more than 50 files to test the limit
|
||||
for i in 0..60 {
|
||||
fs::write(
|
||||
temp_path.join(format!("file{:03}.txt", i)),
|
||||
format!("content{}", i),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Create some directories too
|
||||
for i in 0..10 {
|
||||
fs::create_dir(temp_path.join(format!("dir{:02}", i))).unwrap();
|
||||
}
|
||||
|
||||
let server = create_test_server();
|
||||
|
||||
let result = server
|
||||
.text_editor(Parameters(TextEditorParams {
|
||||
command: "view".to_string(),
|
||||
path: temp_path.to_str().unwrap().to_string(),
|
||||
view_range: None,
|
||||
file_text: None,
|
||||
old_str: None,
|
||||
new_str: None,
|
||||
insert_line: None,
|
||||
diff: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let content = result.unwrap().content;
|
||||
assert_eq!(content.len(), 1);
|
||||
|
||||
let text_content = content[0].as_text().expect("Expected text content");
|
||||
let output = &text_content.text;
|
||||
|
||||
// Check that it shows the limit message
|
||||
assert!(output.contains("... and"));
|
||||
assert!(output.contains("more items"));
|
||||
assert!(output.contains("(showing first 50 items)"));
|
||||
|
||||
// Count the actual number of items shown (should be 50)
|
||||
let dir_count = output.matches("/\n").count(); // directories end with /
|
||||
let file_count = output.matches(".txt\n").count(); // only counting .txt files for simplicity
|
||||
assert!(dir_count + file_count <= 50);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_text_editor_view_empty_directory() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let temp_path = temp_dir.path();
|
||||
|
||||
// Set the current directory before creating the server
|
||||
std::env::set_current_dir(&temp_path).unwrap();
|
||||
|
||||
let server = create_test_server();
|
||||
|
||||
let result = server
|
||||
.text_editor(Parameters(TextEditorParams {
|
||||
command: "view".to_string(),
|
||||
path: temp_path.to_str().unwrap().to_string(),
|
||||
view_range: None,
|
||||
file_text: None,
|
||||
old_str: None,
|
||||
new_str: None,
|
||||
insert_line: None,
|
||||
diff: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let content = result.unwrap().content;
|
||||
assert_eq!(content.len(), 1);
|
||||
|
||||
let text_content = content[0].as_text().expect("Expected text content");
|
||||
let output = &text_content.text;
|
||||
|
||||
// Check that it shows empty directory message
|
||||
assert!(output.contains("is a directory"));
|
||||
assert!(output.contains("(empty directory)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_shell_output_truncation() {
|
||||
|
||||
@@ -468,15 +468,105 @@ pub fn recommend_read_range(path: &Path, total_lines: usize) -> Result<Vec<Conte
|
||||
), None))
|
||||
}
|
||||
|
||||
/// Lists the contents of a directory with a maximum number of items
|
||||
fn list_directory_contents(path: &Path) -> Result<Vec<Content>, ErrorData> {
|
||||
const MAX_ITEMS: usize = 50; // Maximum number of items to display
|
||||
|
||||
// List files in the directory (similar to ls output)
|
||||
let entries = std::fs::read_dir(path).map_err(|e| {
|
||||
ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to read directory: {}", e),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut files = Vec::new();
|
||||
let mut dirs = Vec::new();
|
||||
let mut total_count = 0;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| {
|
||||
ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to read directory entry: {}", e),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
total_count += 1;
|
||||
|
||||
// Only process up to MAX_ITEMS entries
|
||||
if dirs.len() + files.len() < MAX_ITEMS {
|
||||
let metadata = entry.metadata().map_err(|e| {
|
||||
ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to read metadata: {}", e),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
if metadata.is_dir() {
|
||||
dirs.push(format!("{}/", name));
|
||||
} else {
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for consistent output
|
||||
dirs.sort();
|
||||
files.sort();
|
||||
|
||||
let mut output = format!("'{}' is a directory. Contents:\n\n", path.display());
|
||||
|
||||
if !dirs.is_empty() {
|
||||
output.push_str("Directories:\n");
|
||||
for dir in &dirs {
|
||||
output.push_str(&format!(" {}\n", dir));
|
||||
}
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if !files.is_empty() {
|
||||
output.push_str("Files:\n");
|
||||
for file in &files {
|
||||
output.push_str(&format!(" {}\n", file));
|
||||
}
|
||||
}
|
||||
|
||||
if dirs.is_empty() && files.is_empty() {
|
||||
output.push_str(" (empty directory)\n");
|
||||
}
|
||||
|
||||
// If we hit the limit, indicate there are more items
|
||||
if total_count > MAX_ITEMS {
|
||||
output.push_str(&format!(
|
||||
"\n... and {} more items (showing first {} items)\n",
|
||||
total_count - MAX_ITEMS,
|
||||
MAX_ITEMS
|
||||
));
|
||||
}
|
||||
|
||||
Ok(vec![Content::text(output)])
|
||||
}
|
||||
|
||||
pub async fn text_editor_view(
|
||||
path: &PathBuf,
|
||||
view_range: Option<(usize, i64)>,
|
||||
) -> Result<Vec<Content>, ErrorData> {
|
||||
// Check if path is a directory
|
||||
if path.is_dir() {
|
||||
return list_directory_contents(path);
|
||||
}
|
||||
|
||||
if !path.is_file() {
|
||||
return Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!(
|
||||
"The path '{}' does not exist or is not a file.",
|
||||
"The path '{}' does not exist or is not accessible.",
|
||||
path.display()
|
||||
),
|
||||
None,
|
||||
|
||||
Reference in New Issue
Block a user