Use outline (#29687)
## Before  ## After  Release Notes: - Context picker and `@`-mentions now work with very large files.
This commit is contained in:
parent
8173534ad5
commit
50f705e779
8 changed files with 301 additions and 142 deletions
|
@ -24,6 +24,7 @@ language.workspace = true
|
|||
language_model.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
text.workspace = true
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mod action_log;
|
||||
pub mod outline;
|
||||
mod tool_registry;
|
||||
mod tool_schema;
|
||||
mod tool_working_set;
|
||||
|
|
132
crates/assistant_tool/src/outline.rs
Normal file
132
crates/assistant_tool/src/outline.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use crate::ActionLog;
|
||||
use anyhow::{Result, anyhow};
|
||||
use gpui::{AsyncApp, Entity};
|
||||
use language::{OutlineItem, ParseStatus};
|
||||
use project::Project;
|
||||
use regex::Regex;
|
||||
use std::fmt::Write;
|
||||
use text::Point;
|
||||
|
||||
/// For files over this size, instead of reading them (or including them in context),
|
||||
/// we automatically provide the file's symbol outline instead, with line numbers.
|
||||
pub const AUTO_OUTLINE_SIZE: usize = 16384;
|
||||
|
||||
pub async fn file_outline(
|
||||
project: Entity<Project>,
|
||||
path: String,
|
||||
action_log: Entity<ActionLog>,
|
||||
regex: Option<Regex>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> anyhow::Result<String> {
|
||||
let buffer = {
|
||||
let project_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.find_project_path(&path, cx)
|
||||
.ok_or_else(|| anyhow!("Path {path} not found in project"))
|
||||
})??;
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.await?
|
||||
};
|
||||
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.track_buffer(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
// Wait until the buffer has been fully parsed, so that we can read its outline.
|
||||
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
|
||||
while *parse_status.borrow() != ParseStatus::Idle {
|
||||
parse_status.changed().await?;
|
||||
}
|
||||
|
||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let Some(outline) = snapshot.outline(None) else {
|
||||
return Err(anyhow!("No outline information available for this file."));
|
||||
};
|
||||
|
||||
render_outline(
|
||||
outline
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| item.to_point(&snapshot)),
|
||||
regex,
|
||||
0,
|
||||
usize::MAX,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn render_outline(
|
||||
items: impl IntoIterator<Item = OutlineItem<Point>>,
|
||||
regex: Option<Regex>,
|
||||
offset: usize,
|
||||
results_per_page: usize,
|
||||
) -> Result<String> {
|
||||
let mut items = items.into_iter().skip(offset);
|
||||
|
||||
let entries = items
|
||||
.by_ref()
|
||||
.filter(|item| {
|
||||
regex
|
||||
.as_ref()
|
||||
.is_none_or(|regex| regex.is_match(&item.text))
|
||||
})
|
||||
.take(results_per_page)
|
||||
.collect::<Vec<_>>();
|
||||
let has_more = items.next().is_some();
|
||||
|
||||
let mut output = String::new();
|
||||
let entries_rendered = render_entries(&mut output, entries);
|
||||
|
||||
// Calculate pagination information
|
||||
let page_start = offset + 1;
|
||||
let page_end = offset + entries_rendered;
|
||||
let total_symbols = if has_more {
|
||||
format!("more than {}", page_end)
|
||||
} else {
|
||||
page_end.to_string()
|
||||
};
|
||||
|
||||
// Add pagination information
|
||||
if has_more {
|
||||
writeln!(&mut output, "\nShowing symbols {page_start}-{page_end} (there were more symbols found; use offset: {page_end} to see next page)",
|
||||
)
|
||||
} else {
|
||||
writeln!(
|
||||
&mut output,
|
||||
"\nShowing symbols {page_start}-{page_end} (total symbols: {total_symbols})",
|
||||
)
|
||||
}
|
||||
.ok();
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn render_entries(
|
||||
output: &mut String,
|
||||
items: impl IntoIterator<Item = OutlineItem<Point>>,
|
||||
) -> usize {
|
||||
let mut entries_rendered = 0;
|
||||
|
||||
for item in items {
|
||||
// Indent based on depth ("" for level 0, " " for level 1, etc.)
|
||||
for _ in 0..item.depth {
|
||||
output.push(' ');
|
||||
}
|
||||
output.push_str(&item.text);
|
||||
|
||||
// Add position information - convert to 1-based line numbers for display
|
||||
let start_line = item.range.start.row + 1;
|
||||
let end_line = item.range.end.row + 1;
|
||||
|
||||
if start_line == end_line {
|
||||
writeln!(output, " [L{}]", start_line).ok();
|
||||
} else {
|
||||
writeln!(output, " [L{}-{}]", start_line, end_line).ok();
|
||||
}
|
||||
entries_rendered += 1;
|
||||
}
|
||||
|
||||
entries_rendered
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue