assemble_excerpts.rs

  1use crate::RelatedExcerpt;
  2use language::{BufferSnapshot, OffsetRangeExt as _, Point};
  3use std::ops::Range;
  4
  5#[cfg(not(test))]
  6const MAX_OUTLINE_ITEM_BODY_SIZE: usize = 512;
  7#[cfg(test)]
  8const MAX_OUTLINE_ITEM_BODY_SIZE: usize = 24;
  9
 10pub fn assemble_excerpts(
 11    buffer: &BufferSnapshot,
 12    mut input_ranges: Vec<Range<Point>>,
 13) -> Vec<RelatedExcerpt> {
 14    merge_ranges(&mut input_ranges);
 15
 16    let mut outline_ranges = Vec::new();
 17    let outline_items = buffer.outline_items_as_points_containing(0..buffer.len(), false, None);
 18    let mut outline_ix = 0;
 19    for input_range in &mut input_ranges {
 20        *input_range = clip_range_to_lines(input_range, false, buffer);
 21
 22        while let Some(outline_item) = outline_items.get(outline_ix) {
 23            let item_range = clip_range_to_lines(&outline_item.range, false, buffer);
 24
 25            if item_range.start > input_range.start {
 26                break;
 27            }
 28
 29            if item_range.end > input_range.start {
 30                let body_range = outline_item
 31                    .body_range(buffer)
 32                    .map(|body| clip_range_to_lines(&body, true, buffer))
 33                    .filter(|body_range| {
 34                        body_range.to_offset(buffer).len() > MAX_OUTLINE_ITEM_BODY_SIZE
 35                    });
 36
 37                add_outline_item(
 38                    item_range.clone(),
 39                    body_range.clone(),
 40                    buffer,
 41                    &mut outline_ranges,
 42                );
 43
 44                if let Some(body_range) = body_range
 45                    && input_range.start < body_range.start
 46                {
 47                    let mut child_outline_ix = outline_ix + 1;
 48                    while let Some(next_outline_item) = outline_items.get(child_outline_ix) {
 49                        if next_outline_item.range.end > body_range.end {
 50                            break;
 51                        }
 52                        if next_outline_item.depth == outline_item.depth + 1 {
 53                            let next_item_range =
 54                                clip_range_to_lines(&next_outline_item.range, false, buffer);
 55
 56                            add_outline_item(
 57                                next_item_range,
 58                                next_outline_item
 59                                    .body_range(buffer)
 60                                    .map(|body| clip_range_to_lines(&body, true, buffer)),
 61                                buffer,
 62                                &mut outline_ranges,
 63                            );
 64                            child_outline_ix += 1;
 65                        }
 66                    }
 67                }
 68            }
 69
 70            outline_ix += 1;
 71        }
 72    }
 73
 74    input_ranges.extend_from_slice(&outline_ranges);
 75    merge_ranges(&mut input_ranges);
 76
 77    input_ranges
 78        .into_iter()
 79        .map(|range| {
 80            let offset_range = range.to_offset(buffer);
 81            RelatedExcerpt {
 82                point_range: range,
 83                anchor_range: buffer.anchor_before(offset_range.start)
 84                    ..buffer.anchor_after(offset_range.end),
 85                text: buffer.as_rope().slice(offset_range),
 86            }
 87        })
 88        .collect()
 89}
 90
 91fn clip_range_to_lines(
 92    range: &Range<Point>,
 93    inward: bool,
 94    buffer: &BufferSnapshot,
 95) -> Range<Point> {
 96    let mut range = range.clone();
 97    if inward {
 98        if range.start.column > 0 {
 99            range.start.column = buffer.line_len(range.start.row);
100        }
101        range.end.column = 0;
102    } else {
103        range.start.column = 0;
104        if range.end.column > 0 {
105            range.end.column = buffer.line_len(range.end.row);
106        }
107    }
108    range
109}
110
111fn add_outline_item(
112    mut item_range: Range<Point>,
113    body_range: Option<Range<Point>>,
114    buffer: &BufferSnapshot,
115    outline_ranges: &mut Vec<Range<Point>>,
116) {
117    if let Some(mut body_range) = body_range {
118        if body_range.start.column > 0 {
119            body_range.start.column = buffer.line_len(body_range.start.row);
120        }
121        body_range.end.column = 0;
122
123        let head_range = item_range.start..body_range.start;
124        if head_range.start < head_range.end {
125            outline_ranges.push(head_range);
126        }
127
128        let tail_range = body_range.end..item_range.end;
129        if tail_range.start < tail_range.end {
130            outline_ranges.push(tail_range);
131        }
132    } else {
133        item_range.start.column = 0;
134        item_range.end.column = buffer.line_len(item_range.end.row);
135        outline_ranges.push(item_range);
136    }
137}
138
139pub fn merge_ranges(ranges: &mut Vec<Range<Point>>) {
140    ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
141
142    let mut index = 1;
143    while index < ranges.len() {
144        let mut prev_range_end = ranges[index - 1].end;
145        if prev_range_end.column > 0 {
146            prev_range_end += Point::new(1, 0);
147        }
148
149        if (prev_range_end + Point::new(1, 0))
150            .cmp(&ranges[index].start)
151            .is_ge()
152        {
153            let removed = ranges.remove(index);
154            if removed.end.cmp(&ranges[index - 1].end).is_gt() {
155                ranges[index - 1].end = removed.end;
156            }
157        } else {
158            index += 1;
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use gpui::{TestAppContext, prelude::*};
167    use indoc::indoc;
168    use language::{Buffer, Language, LanguageConfig, LanguageMatcher, OffsetRangeExt};
169    use pretty_assertions::assert_eq;
170    use std::{fmt::Write as _, sync::Arc};
171    use util::test::marked_text_ranges;
172
173    #[gpui::test]
174    fn test_rust(cx: &mut TestAppContext) {
175        let table = [
176            (
177                indoc! {r#"
178                    struct User {
179                        first_name: String,
180                        «last_name»: String,
181                        age: u32,
182                        email: String,
183                        create_at: Instant,
184                    }
185
186                    impl User {
187                        pub fn first_name(&self) -> String {
188                            self.first_name.clone()
189                        }
190
191                        pub fn full_name(&self) -> String {
192                    «        format!("{} {}", self.first_name, self.last_name)
193                    »    }
194                    }
195                "#},
196                indoc! {r#"
197                    struct User {
198                        first_name: String,
199                        last_name: String,
200201                    }
202
203                    impl User {
204205                        pub fn full_name(&self) -> String {
206                            format!("{} {}", self.first_name, self.last_name)
207                        }
208                    }
209                "#},
210            ),
211            (
212                indoc! {r#"
213                    struct «User» {
214                        first_name: String,
215                        last_name: String,
216                        age: u32,
217                    }
218
219                    impl User {
220                        // methods
221                    }
222                    "#
223                },
224                indoc! {r#"
225                    struct User {
226                        first_name: String,
227                        last_name: String,
228                        age: u32,
229                    }
230231                "#},
232            ),
233            (
234                indoc! {r#"
235                    trait «FooProvider» {
236                        const NAME: &'static str;
237
238                        fn provide_foo(&self, id: usize) -> Foo;
239
240                        fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
241                             ids.iter()
242                                .map(|id| self.provide_foo(*id))
243                                .collect()
244                        }
245
246                        fn sync(&self);
247                    }
248                    "#
249                },
250                indoc! {r#"
251                    trait FooProvider {
252                        const NAME: &'static str;
253
254                        fn provide_foo(&self, id: usize) -> Foo;
255
256                        fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
257258                        }
259
260                        fn sync(&self);
261                    }
262                "#},
263            ),
264        ];
265
266        for (input, expected_output) in table {
267            let (input, ranges) = marked_text_ranges(&input, false);
268            let buffer =
269                cx.new(|cx| Buffer::local(input, cx).with_language(Arc::new(rust_lang()), cx));
270            buffer.read_with(cx, |buffer, _cx| {
271                let ranges: Vec<Range<Point>> = ranges
272                    .into_iter()
273                    .map(|range| range.to_point(&buffer))
274                    .collect();
275
276                let excerpts = assemble_excerpts(&buffer.snapshot(), ranges);
277
278                let output = format_excerpts(buffer, &excerpts);
279                assert_eq!(output, expected_output);
280            });
281        }
282    }
283
284    fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
285        let mut output = String::new();
286        let file_line_count = buffer.max_point().row;
287        let mut current_row = 0;
288        for excerpt in excerpts {
289            if excerpt.text.is_empty() {
290                continue;
291            }
292            if current_row < excerpt.point_range.start.row {
293                writeln!(&mut output, "").unwrap();
294            }
295            current_row = excerpt.point_range.start.row;
296
297            for line in excerpt.text.to_string().lines() {
298                output.push_str(line);
299                output.push('\n');
300                current_row += 1;
301            }
302        }
303        if current_row < file_line_count {
304            writeln!(&mut output, "").unwrap();
305        }
306        output
307    }
308
309    fn rust_lang() -> Language {
310        Language::new(
311            LanguageConfig {
312                name: "Rust".into(),
313                matcher: LanguageMatcher {
314                    path_suffixes: vec!["rs".to_string()],
315                    ..Default::default()
316                },
317                ..Default::default()
318            },
319            Some(language::tree_sitter_rust::LANGUAGE.into()),
320        )
321        .with_outline_query(include_str!("../../languages/src/rust/outline.scm"))
322        .unwrap()
323    }
324}