Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:35:00.002Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

outline.rs

230 lines · 7.9 KB · rust
1use anyhow::Result;
2use gpui::{AsyncApp, Entity};
3use language::{Buffer, OutlineItem};
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, a symbol outline, or a
15    /// truncated fallback — see `is_synthetic`).
16    pub text: String,
17    /// `true` when `text` is not the file's full content — either a symbol
18    /// outline or the truncated first-1KB fallback used when no outline is
19    /// available. Callers that prefix line numbers to file content must
20    /// skip prefixing in this case, because line numbers in `text` would
21    /// not correspond to the file's real line numbers.
22    pub is_synthetic: bool,
23}
24
25/// Returns either the full content of a buffer or its outline, depending on size.
26/// For files larger than AUTO_OUTLINE_SIZE, returns an outline with a header.
27/// For smaller files, returns the full content.
28pub async fn get_buffer_content_or_outline(
29    buffer: Entity<Buffer>,
30    path: Option<&str>,
31    cx: &AsyncApp,
32) -> Result<BufferContent> {
33    let file_size = buffer.read_with(cx, |buffer, _| buffer.text().len());
34
35    if file_size > AUTO_OUTLINE_SIZE {
36        // For large files, use outline instead of full content
37        // Wait until the buffer has been fully parsed, so we can read its outline
38        buffer
39            .read_with(cx, |buffer, _| buffer.parsing_idle())
40            .await;
41
42        let outline_items = buffer.read_with(cx, |buffer, _| {
43            let snapshot = buffer.snapshot();
44            snapshot
45                .outline(None)
46                .items
47                .into_iter()
48                .map(|item| item.to_point(&snapshot))
49                .collect::<Vec<_>>()
50        });
51
52        // If no outline exists, fall back to first 1KB so the agent has some context.
53        // This is reported as `is_synthetic: true` because the returned text is not
54        // the file's full content — it has a synthetic header and is truncated — so
55        // callers must not attach real-file line numbers to it.
56        if outline_items.is_empty() {
57            let text = buffer.read_with(cx, |buffer, _| {
58                let snapshot = buffer.snapshot();
59                let len = snapshot.len().min(snapshot.as_rope().floor_char_boundary(1024));
60                let content = snapshot.text_for_range(0..len).collect::<String>();
61                if let Some(path) = path {
62                    format!("# First 1KB of {path} (file too large to show full content, and no outline available)\n\n{content}")
63                } else {
64                    format!("# First 1KB of file (file too large to show full content, and no outline available)\n\n{content}")
65                }
66            });
67
68            return Ok(BufferContent {
69                text,
70                is_synthetic: true,
71            });
72        }
73
74        let outline_text = render_outline(outline_items, None, 0, usize::MAX).await?;
75
76        let text = if let Some(path) = path {
77            format!("# File outline for {path}\n\n{outline_text}",)
78        } else {
79            format!("# File outline\n\n{outline_text}",)
80        };
81        Ok(BufferContent {
82            text,
83            is_synthetic: true,
84        })
85    } else {
86        // File is small enough, return full content
87        let text = buffer.read_with(cx, |buffer, _| buffer.text());
88        Ok(BufferContent {
89            text,
90            is_synthetic: false,
91        })
92    }
93}
94
95async fn render_outline(
96    items: impl IntoIterator<Item = OutlineItem<Point>>,
97    regex: Option<Regex>,
98    offset: usize,
99    results_per_page: usize,
100) -> Result<String> {
101    let mut items = items.into_iter().skip(offset);
102
103    let entries = items
104        .by_ref()
105        .filter(|item| {
106            regex
107                .as_ref()
108                .is_none_or(|regex| regex.is_match(&item.text))
109        })
110        .take(results_per_page)
111        .collect::<Vec<_>>();
112    let has_more = items.next().is_some();
113
114    let mut output = String::new();
115    let entries_rendered = render_entries(&mut output, entries);
116
117    // Calculate pagination information
118    let page_start = offset + 1;
119    let page_end = offset + entries_rendered;
120    let total_symbols = if has_more {
121        format!("more than {}", page_end)
122    } else {
123        page_end.to_string()
124    };
125
126    // Add pagination information
127    if has_more {
128        writeln!(&mut output, "\nShowing symbols {page_start}-{page_end} (there were more symbols found; use offset: {page_end} to see next page)",
129        )
130    } else {
131        writeln!(
132            &mut output,
133            "\nShowing symbols {page_start}-{page_end} (total symbols: {total_symbols})",
134        )
135    }
136    .ok();
137
138    Ok(output)
139}
140
141fn render_entries(
142    output: &mut String,
143    items: impl IntoIterator<Item = OutlineItem<Point>>,
144) -> usize {
145    let mut entries_rendered = 0;
146
147    for item in items {
148        // Indent based on depth ("" for level 0, "  " for level 1, etc.)
149        for _ in 0..item.depth {
150            output.push(' ');
151        }
152        output.push_str(&item.text);
153
154        // Add position information - convert to 1-based line numbers for display
155        let start_line = item.range.start.row + 1;
156        let end_line = item.range.end.row + 1;
157
158        if start_line == end_line {
159            writeln!(output, " [L{}]", start_line).ok();
160        } else {
161            writeln!(output, " [L{}-{}]", start_line, end_line).ok();
162        }
163        entries_rendered += 1;
164    }
165
166    entries_rendered
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use fs::FakeFs;
173    use gpui::TestAppContext;
174    use project::Project;
175    use settings::SettingsStore;
176
177    #[gpui::test]
178    async fn test_large_file_fallback_to_subset(cx: &mut TestAppContext) {
179        cx.update(|cx| {
180            let settings = SettingsStore::test(cx);
181            cx.set_global(settings);
182        });
183
184        let fs = FakeFs::new(cx.executor());
185        let project = Project::test(fs, [], cx).await;
186
187        let content = "⚡".repeat(100 * 1024); // 100KB
188        let content_len = content.len();
189        let buffer = project
190            .update(cx, |project, cx| project.create_buffer(None, true, cx))
191            .await
192            .expect("failed to create buffer");
193
194        buffer.update(cx, |buffer, cx| buffer.set_text(content, cx));
195
196        let result = cx
197            .spawn(|cx| async move { get_buffer_content_or_outline(buffer, None, &cx).await })
198            .await
199            .unwrap();
200
201        // Should contain some of the actual file content
202        assert!(
203            result.text.contains("⚡⚡⚡⚡⚡⚡⚡"),
204            "Result did not contain content subset"
205        );
206
207        // Should be marked synthetic: the returned text is not the file's full
208        // content (it's a truncated first-1KB fallback with a synthetic header), so
209        // callers must treat it the same as the symbol-outline case and not attach
210        // real-file line numbers to it.
211        assert!(
212            result.is_synthetic,
213            "Truncated fallback should be reported as synthetic so callers skip line numbering"
214        );
215
216        // Should be reasonably sized (much smaller than original)
217        assert!(
218            result.text.len() < 50 * 1024,
219            "Result size {} should be smaller than 50KB",
220            result.text.len()
221        );
222
223        // Should be significantly smaller than the original content
224        assert!(
225            result.text.len() < content_len / 10,
226            "Result should be much smaller than original content"
227        );
228    }
229}
230
Served at tenant.openagents/omega Member data and write actions are omitted.