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