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 }
65 child_outline_ix += 1;
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}