1use crate::*;
2use gpui::{ModelHandle, MutableAppContext};
3use unindent::Unindent as _;
4
5#[gpui::test]
6async fn test_reparse(mut cx: gpui::TestAppContext) {
7 let buffer = cx.add_model(|cx| {
8 let text = "fn a() {}".into();
9 Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
10 });
11
12 // Wait for the initial text to parse
13 buffer
14 .condition(&cx, |buffer, _| !buffer.is_parsing())
15 .await;
16 assert_eq!(
17 get_tree_sexp(&buffer, &cx),
18 concat!(
19 "(source_file (function_item name: (identifier) ",
20 "parameters: (parameters) ",
21 "body: (block)))"
22 )
23 );
24
25 buffer.update(&mut cx, |buffer, _| {
26 buffer.set_sync_parse_timeout(Duration::ZERO)
27 });
28
29 // Perform some edits (add parameter and variable reference)
30 // Parsing doesn't begin until the transaction is complete
31 buffer.update(&mut cx, |buf, cx| {
32 buf.start_transaction(None).unwrap();
33
34 let offset = buf.text().find(")").unwrap();
35 buf.edit(vec![offset..offset], "b: C", cx);
36 assert!(!buf.is_parsing());
37
38 let offset = buf.text().find("}").unwrap();
39 buf.edit(vec![offset..offset], " d; ", cx);
40 assert!(!buf.is_parsing());
41
42 buf.end_transaction(None, cx).unwrap();
43 assert_eq!(buf.text(), "fn a(b: C) { d; }");
44 assert!(buf.is_parsing());
45 });
46 buffer
47 .condition(&cx, |buffer, _| !buffer.is_parsing())
48 .await;
49 assert_eq!(
50 get_tree_sexp(&buffer, &cx),
51 concat!(
52 "(source_file (function_item name: (identifier) ",
53 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
54 "body: (block (identifier))))"
55 )
56 );
57
58 // Perform a series of edits without waiting for the current parse to complete:
59 // * turn identifier into a field expression
60 // * turn field expression into a method call
61 // * add a turbofish to the method call
62 buffer.update(&mut cx, |buf, cx| {
63 let offset = buf.text().find(";").unwrap();
64 buf.edit(vec![offset..offset], ".e", cx);
65 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
66 assert!(buf.is_parsing());
67 });
68 buffer.update(&mut cx, |buf, cx| {
69 let offset = buf.text().find(";").unwrap();
70 buf.edit(vec![offset..offset], "(f)", cx);
71 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
72 assert!(buf.is_parsing());
73 });
74 buffer.update(&mut cx, |buf, cx| {
75 let offset = buf.text().find("(f)").unwrap();
76 buf.edit(vec![offset..offset], "::<G>", cx);
77 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
78 assert!(buf.is_parsing());
79 });
80 buffer
81 .condition(&cx, |buffer, _| !buffer.is_parsing())
82 .await;
83 assert_eq!(
84 get_tree_sexp(&buffer, &cx),
85 concat!(
86 "(source_file (function_item name: (identifier) ",
87 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
88 "body: (block (call_expression ",
89 "function: (generic_function ",
90 "function: (field_expression value: (identifier) field: (field_identifier)) ",
91 "type_arguments: (type_arguments (type_identifier))) ",
92 "arguments: (arguments (identifier))))))",
93 )
94 );
95
96 buffer.update(&mut cx, |buf, cx| {
97 buf.undo(cx);
98 assert_eq!(buf.text(), "fn a() {}");
99 assert!(buf.is_parsing());
100 });
101 buffer
102 .condition(&cx, |buffer, _| !buffer.is_parsing())
103 .await;
104 assert_eq!(
105 get_tree_sexp(&buffer, &cx),
106 concat!(
107 "(source_file (function_item name: (identifier) ",
108 "parameters: (parameters) ",
109 "body: (block)))"
110 )
111 );
112
113 buffer.update(&mut cx, |buf, cx| {
114 buf.redo(cx);
115 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
116 assert!(buf.is_parsing());
117 });
118 buffer
119 .condition(&cx, |buffer, _| !buffer.is_parsing())
120 .await;
121 assert_eq!(
122 get_tree_sexp(&buffer, &cx),
123 concat!(
124 "(source_file (function_item name: (identifier) ",
125 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
126 "body: (block (call_expression ",
127 "function: (generic_function ",
128 "function: (field_expression value: (identifier) field: (field_identifier)) ",
129 "type_arguments: (type_arguments (type_identifier))) ",
130 "arguments: (arguments (identifier))))))",
131 )
132 );
133
134 fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
135 buffer.read_with(cx, |buffer, _| {
136 buffer.syntax_tree().unwrap().root_node().to_sexp()
137 })
138 }
139}
140
141#[gpui::test]
142fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
143 let buffer = cx.add_model(|cx| {
144 let text = "
145 mod x {
146 mod y {
147
148 }
149 }
150 "
151 .unindent()
152 .into();
153 Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
154 });
155 let buffer = buffer.read(cx);
156 assert_eq!(
157 buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
158 Some((
159 Point::new(0, 6)..Point::new(0, 7),
160 Point::new(4, 0)..Point::new(4, 1)
161 ))
162 );
163 assert_eq!(
164 buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
165 Some((
166 Point::new(1, 10)..Point::new(1, 11),
167 Point::new(3, 4)..Point::new(3, 5)
168 ))
169 );
170 assert_eq!(
171 buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
172 Some((
173 Point::new(1, 10)..Point::new(1, 11),
174 Point::new(3, 4)..Point::new(3, 5)
175 ))
176 );
177}
178
179#[gpui::test]
180fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
181 cx.add_model(|cx| {
182 let text = "fn a() {}".into();
183 let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
184
185 buffer.edit_with_autoindent([8..8], "\n\n", cx);
186 assert_eq!(buffer.text(), "fn a() {\n \n}");
187
188 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
189 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
190
191 buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
192 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
193
194 buffer
195 });
196}
197
198#[gpui::test]
199fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
200 cx.add_model(|cx| {
201 let text = History::new("fn a() {}".into());
202 let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
203
204 let selection_set_id = buffer.add_selection_set(Vec::new(), cx);
205 buffer.start_transaction(Some(selection_set_id)).unwrap();
206 buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
207 buffer
208 .update_selection_set(
209 selection_set_id,
210 vec![
211 Selection {
212 id: 0,
213 start: buffer.anchor_before(Point::new(1, 0)),
214 end: buffer.anchor_before(Point::new(1, 0)),
215 reversed: false,
216 goal: SelectionGoal::None,
217 },
218 Selection {
219 id: 1,
220 start: buffer.anchor_before(Point::new(4, 0)),
221 end: buffer.anchor_before(Point::new(4, 0)),
222 reversed: false,
223 goal: SelectionGoal::None,
224 },
225 ],
226 cx,
227 )
228 .unwrap();
229 assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
230
231 // Ending the transaction runs the auto-indent. The selection
232 // at the start of the auto-indented row is pushed to the right.
233 buffer.end_transaction(Some(selection_set_id), cx).unwrap();
234 assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
235 let selection_ranges = buffer
236 .selection_set(selection_set_id)
237 .unwrap()
238 .selections
239 .iter()
240 .map(|selection| selection.point_range(&buffer))
241 .collect::<Vec<_>>();
242
243 assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
244 assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
245
246 buffer
247 });
248}
249
250#[gpui::test]
251fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
252 cx.add_model(|cx| {
253 let text = "
254 fn a() {
255 c;
256 d;
257 }
258 "
259 .unindent()
260 .into();
261 let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
262
263 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
264 // their indentation is not adjusted.
265 buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
266 assert_eq!(
267 buffer.text(),
268 "
269 fn a() {
270 c();
271 d();
272 }
273 "
274 .unindent()
275 );
276
277 // When appending new content after these lines, the indentation is based on the
278 // preceding lines' actual indentation.
279 buffer.edit_with_autoindent(
280 [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
281 "\n.f\n.g",
282 cx,
283 );
284 assert_eq!(
285 buffer.text(),
286 "
287 fn a() {
288 c
289 .f
290 .g();
291 d
292 .f
293 .g();
294 }
295 "
296 .unindent()
297 );
298 buffer
299 });
300}
301
302#[gpui::test]
303fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
304 cx.add_model(|cx| {
305 let text = History::new(
306 "
307 fn a() {}
308 "
309 .unindent()
310 .into(),
311 );
312 let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
313
314 buffer.edit_with_autoindent([5..5], "\nb", cx);
315 assert_eq!(
316 buffer.text(),
317 "
318 fn a(
319 b) {}
320 "
321 .unindent()
322 );
323
324 // The indentation suggestion changed because `@end` node (a close paren)
325 // is now at the beginning of the line.
326 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
327 assert_eq!(
328 buffer.text(),
329 "
330 fn a(
331 ) {}
332 "
333 .unindent()
334 );
335
336 buffer
337 });
338}
339
340#[test]
341fn test_contiguous_ranges() {
342 assert_eq!(
343 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
344 &[1..4, 5..7, 9..13]
345 );
346
347 // Respects the `max_len` parameter
348 assert_eq!(
349 contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
350 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
351 );
352}
353
354impl Buffer {
355 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
356 &self,
357 range: Range<T>,
358 ) -> Option<(Range<Point>, Range<Point>)> {
359 self.enclosing_bracket_ranges(range).map(|(start, end)| {
360 let point_start = start.start.to_point(self)..start.end.to_point(self);
361 let point_end = end.start.to_point(self)..end.end.to_point(self);
362 (point_start, point_end)
363 })
364 }
365}
366
367fn rust_lang() -> Arc<Language> {
368 Arc::new(
369 Language::new(
370 LanguageConfig {
371 name: "Rust".to_string(),
372 path_suffixes: vec!["rs".to_string()],
373 ..Default::default()
374 },
375 tree_sitter_rust::language(),
376 )
377 .with_indents_query(
378 r#"
379 (call_expression) @indent
380 (field_expression) @indent
381 (_ "(" ")" @end) @indent
382 (_ "{" "}" @end) @indent
383 "#,
384 )
385 .unwrap()
386 .with_brackets_query(r#" ("{" @open "}" @close) "#)
387 .unwrap(),
388 )
389}
390
391fn empty(point: Point) -> Range<Point> {
392 point..point
393}