use crate::ActionLog;use anyhow::{Context as _, Result};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) .with_context(|| format!("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.buffer_read(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 outline = snapshot .outline(None) .context("No outline information available for this file at path {path}")?; 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}
use crate::ActionLog;
use anyhow::{Context as _, Result};
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)
.with_context(|| format!("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.buffer_read(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 outline = snapshot
.outline(None)
.context("No outline information available for this file at path {path}")?;
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
}