1use anyhow::Result;
2use gpui::{AsyncApp, Entity};
3use language::{Buffer, OutlineItem, ParseStatus};
4use regex::Regex;
5use std::fmt::Write;
6use text::Point;
7
8/// For files over this size, instead of reading them (or including them in context),
9/// we automatically provide the file's symbol outline instead, with line numbers.
10pub const AUTO_OUTLINE_SIZE: usize = 16384;
11
12/// Result of getting buffer content, which can be either full content or an outline.
13pub struct BufferContent {
14 /// The actual content (either full text or outline)
15 pub text: String,
16 /// Whether this is an outline (true) or full content (false)
17 pub is_outline: bool,
18}
19
20/// Returns either the full content of a buffer or its outline, depending on size.
21/// For files larger than AUTO_OUTLINE_SIZE, returns an outline with a header.
22/// For smaller files, returns the full content.
23pub async fn get_buffer_content_or_outline(
24 buffer: Entity<Buffer>,
25 path: Option<&str>,
26 cx: &AsyncApp,
27) -> Result<BufferContent> {
28 let file_size = buffer.read_with(cx, |buffer, _| buffer.text().len())?;
29
30 if file_size > AUTO_OUTLINE_SIZE {
31 // For large files, use outline instead of full content
32 // Wait until the buffer has been fully parsed, so we can read its outline
33 let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
34 while *parse_status.borrow() != ParseStatus::Idle {
35 parse_status.changed().await?;
36 }
37
38 let outline_items = buffer.read_with(cx, |buffer, _| {
39 let snapshot = buffer.snapshot();
40 snapshot
41 .outline(None)
42 .items
43 .into_iter()
44 .map(|item| item.to_point(&snapshot))
45 .collect::<Vec<_>>()
46 })?;
47
48 let outline_text = render_outline(outline_items, None, 0, usize::MAX).await?;
49
50 let text = if let Some(path) = path {
51 format!(
52 "# File outline for {path} (file too large to show full content)\n\n{outline_text}",
53 )
54 } else {
55 format!("# File outline (file too large to show full content)\n\n{outline_text}",)
56 };
57 Ok(BufferContent {
58 text,
59 is_outline: true,
60 })
61 } else {
62 // File is small enough, return full content
63 let text = buffer.read_with(cx, |buffer, _| buffer.text())?;
64 Ok(BufferContent {
65 text,
66 is_outline: false,
67 })
68 }
69}
70
71async fn render_outline(
72 items: impl IntoIterator<Item = OutlineItem<Point>>,
73 regex: Option<Regex>,
74 offset: usize,
75 results_per_page: usize,
76) -> Result<String> {
77 let mut items = items.into_iter().skip(offset);
78
79 let entries = items
80 .by_ref()
81 .filter(|item| {
82 regex
83 .as_ref()
84 .is_none_or(|regex| regex.is_match(&item.text))
85 })
86 .take(results_per_page)
87 .collect::<Vec<_>>();
88 let has_more = items.next().is_some();
89
90 let mut output = String::new();
91 let entries_rendered = render_entries(&mut output, entries);
92
93 // Calculate pagination information
94 let page_start = offset + 1;
95 let page_end = offset + entries_rendered;
96 let total_symbols = if has_more {
97 format!("more than {}", page_end)
98 } else {
99 page_end.to_string()
100 };
101
102 // Add pagination information
103 if has_more {
104 writeln!(&mut output, "\nShowing symbols {page_start}-{page_end} (there were more symbols found; use offset: {page_end} to see next page)",
105 )
106 } else {
107 writeln!(
108 &mut output,
109 "\nShowing symbols {page_start}-{page_end} (total symbols: {total_symbols})",
110 )
111 }
112 .ok();
113
114 Ok(output)
115}
116
117fn render_entries(
118 output: &mut String,
119 items: impl IntoIterator<Item = OutlineItem<Point>>,
120) -> usize {
121 let mut entries_rendered = 0;
122
123 for item in items {
124 // Indent based on depth ("" for level 0, " " for level 1, etc.)
125 for _ in 0..item.depth {
126 output.push(' ');
127 }
128 output.push_str(&item.text);
129
130 // Add position information - convert to 1-based line numbers for display
131 let start_line = item.range.start.row + 1;
132 let end_line = item.range.end.row + 1;
133
134 if start_line == end_line {
135 writeln!(output, " [L{}]", start_line).ok();
136 } else {
137 writeln!(output, " [L{}-{}]", start_line, end_line).ok();
138 }
139 entries_rendered += 1;
140 }
141
142 entries_rendered
143}