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 text = "fn a() {}";
82 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(rust_lang(), None, cx));
83
84 // Wait for the initial text to parse
85 buffer
86 .condition(&cx, |buffer, _| !buffer.is_parsing())
87 .await;
88 assert_eq!(
89 get_tree_sexp(&buffer, &cx),
90 concat!(
91 "(source_file (function_item name: (identifier) ",
92 "parameters: (parameters) ",
93 "body: (block)))"
94 )
95 );
96
97 buffer.update(&mut cx, |buffer, _| {
98 buffer.set_sync_parse_timeout(Duration::ZERO)
99 });
100
101 // Perform some edits (add parameter and variable reference)
102 // Parsing doesn't begin until the transaction is complete
103 buffer.update(&mut cx, |buf, cx| {
104 buf.start_transaction(None).unwrap();
105
106 let offset = buf.text().find(")").unwrap();
107 buf.edit(vec![offset..offset], "b: C", cx);
108 assert!(!buf.is_parsing());
109
110 let offset = buf.text().find("}").unwrap();
111 buf.edit(vec![offset..offset], " d; ", cx);
112 assert!(!buf.is_parsing());
113
114 buf.end_transaction(None, cx).unwrap();
115 assert_eq!(buf.text(), "fn a(b: C) { d; }");
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 (identifier))))"
127 )
128 );
129
130 // Perform a series of edits without waiting for the current parse to complete:
131 // * turn identifier into a field expression
132 // * turn field expression into a method call
133 // * add a turbofish to the method call
134 buffer.update(&mut cx, |buf, cx| {
135 let offset = buf.text().find(";").unwrap();
136 buf.edit(vec![offset..offset], ".e", cx);
137 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
138 assert!(buf.is_parsing());
139 });
140 buffer.update(&mut cx, |buf, cx| {
141 let offset = buf.text().find(";").unwrap();
142 buf.edit(vec![offset..offset], "(f)", cx);
143 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
144 assert!(buf.is_parsing());
145 });
146 buffer.update(&mut cx, |buf, cx| {
147 let offset = buf.text().find("(f)").unwrap();
148 buf.edit(vec![offset..offset], "::<G>", cx);
149 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
150 assert!(buf.is_parsing());
151 });
152 buffer
153 .condition(&cx, |buffer, _| !buffer.is_parsing())
154 .await;
155 assert_eq!(
156 get_tree_sexp(&buffer, &cx),
157 concat!(
158 "(source_file (function_item name: (identifier) ",
159 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
160 "body: (block (call_expression ",
161 "function: (generic_function ",
162 "function: (field_expression value: (identifier) field: (field_identifier)) ",
163 "type_arguments: (type_arguments (type_identifier))) ",
164 "arguments: (arguments (identifier))))))",
165 )
166 );
167
168 buffer.update(&mut cx, |buf, cx| {
169 buf.undo(cx);
170 assert_eq!(buf.text(), "fn a() {}");
171 assert!(buf.is_parsing());
172 });
173 buffer
174 .condition(&cx, |buffer, _| !buffer.is_parsing())
175 .await;
176 assert_eq!(
177 get_tree_sexp(&buffer, &cx),
178 concat!(
179 "(source_file (function_item name: (identifier) ",
180 "parameters: (parameters) ",
181 "body: (block)))"
182 )
183 );
184
185 buffer.update(&mut cx, |buf, cx| {
186 buf.redo(cx);
187 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
188 assert!(buf.is_parsing());
189 });
190 buffer
191 .condition(&cx, |buffer, _| !buffer.is_parsing())
192 .await;
193 assert_eq!(
194 get_tree_sexp(&buffer, &cx),
195 concat!(
196 "(source_file (function_item name: (identifier) ",
197 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
198 "body: (block (call_expression ",
199 "function: (generic_function ",
200 "function: (field_expression value: (identifier) field: (field_identifier)) ",
201 "type_arguments: (type_arguments (type_identifier))) ",
202 "arguments: (arguments (identifier))))))",
203 )
204 );
205
206 fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
207 buffer.read_with(cx, |buffer, _| {
208 buffer.syntax_tree().unwrap().root_node().to_sexp()
209 })
210 }
211}
212
213#[gpui::test]
214fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
215 let buffer = cx.add_model(|cx| {
216 let text = "
217 mod x {
218 mod y {
219
220 }
221 }
222 "
223 .unindent();
224 Buffer::new(0, text, cx).with_language(rust_lang(), None, cx)
225 });
226 let buffer = buffer.read(cx);
227 assert_eq!(
228 buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
229 Some((
230 Point::new(0, 6)..Point::new(0, 7),
231 Point::new(4, 0)..Point::new(4, 1)
232 ))
233 );
234 assert_eq!(
235 buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
236 Some((
237 Point::new(1, 10)..Point::new(1, 11),
238 Point::new(3, 4)..Point::new(3, 5)
239 ))
240 );
241 assert_eq!(
242 buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
243 Some((
244 Point::new(1, 10)..Point::new(1, 11),
245 Point::new(3, 4)..Point::new(3, 5)
246 ))
247 );
248}
249
250#[gpui::test]
251fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
252 cx.add_model(|cx| {
253 let text = "fn a() {}";
254 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
255
256 buffer.edit_with_autoindent([8..8], "\n\n", cx);
257 assert_eq!(buffer.text(), "fn a() {\n \n}");
258
259 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
260 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
261
262 buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
263 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
264
265 buffer
266 });
267}
268
269#[gpui::test]
270fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
271 cx.add_model(|cx| {
272 let text = "fn a() {}";
273
274 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
275
276 let selection_set_id = buffer.add_selection_set(Vec::new(), cx);
277 buffer.start_transaction(Some(selection_set_id)).unwrap();
278 buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
279 buffer
280 .update_selection_set(
281 selection_set_id,
282 vec![
283 Selection {
284 id: 0,
285 start: buffer.anchor_before(Point::new(1, 0)),
286 end: buffer.anchor_before(Point::new(1, 0)),
287 reversed: false,
288 goal: SelectionGoal::None,
289 },
290 Selection {
291 id: 1,
292 start: buffer.anchor_before(Point::new(4, 0)),
293 end: buffer.anchor_before(Point::new(4, 0)),
294 reversed: false,
295 goal: SelectionGoal::None,
296 },
297 ],
298 cx,
299 )
300 .unwrap();
301 assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
302
303 // Ending the transaction runs the auto-indent. The selection
304 // at the start of the auto-indented row is pushed to the right.
305 buffer.end_transaction(Some(selection_set_id), cx).unwrap();
306 assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
307 let selection_ranges = buffer
308 .selection_set(selection_set_id)
309 .unwrap()
310 .selections
311 .iter()
312 .map(|selection| selection.point_range(&buffer))
313 .collect::<Vec<_>>();
314
315 assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
316 assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
317
318 buffer
319 });
320}
321
322#[gpui::test]
323fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
324 cx.add_model(|cx| {
325 let text = "
326 fn a() {
327 c;
328 d;
329 }
330 "
331 .unindent();
332
333 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
334
335 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
336 // their indentation is not adjusted.
337 buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
338 assert_eq!(
339 buffer.text(),
340 "
341 fn a() {
342 c();
343 d();
344 }
345 "
346 .unindent()
347 );
348
349 // When appending new content after these lines, the indentation is based on the
350 // preceding lines' actual indentation.
351 buffer.edit_with_autoindent(
352 [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
353 "\n.f\n.g",
354 cx,
355 );
356 assert_eq!(
357 buffer.text(),
358 "
359 fn a() {
360 c
361 .f
362 .g();
363 d
364 .f
365 .g();
366 }
367 "
368 .unindent()
369 );
370 buffer
371 });
372}
373
374#[gpui::test]
375fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
376 cx.add_model(|cx| {
377 let text = "
378 fn a() {}
379 "
380 .unindent();
381
382 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
383
384 buffer.edit_with_autoindent([5..5], "\nb", cx);
385 assert_eq!(
386 buffer.text(),
387 "
388 fn a(
389 b) {}
390 "
391 .unindent()
392 );
393
394 // The indentation suggestion changed because `@end` node (a close paren)
395 // is now at the beginning of the line.
396 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
397 assert_eq!(
398 buffer.text(),
399 "
400 fn a(
401 ) {}
402 "
403 .unindent()
404 );
405
406 buffer
407 });
408}
409
410#[gpui::test]
411async fn test_diagnostics(mut cx: gpui::TestAppContext) {
412 let (language_server, mut fake) = lsp::LanguageServer::fake(&cx.background()).await;
413
414 let text = "
415 fn a() { A }
416 fn b() { BB }
417 fn c() { CCC }
418 "
419 .unindent();
420
421 let buffer = cx.add_model(|cx| {
422 Buffer::new(0, text, cx).with_language(rust_lang(), Some(language_server), cx)
423 });
424
425 let open_notification = fake
426 .receive_notification::<lsp::notification::DidOpenTextDocument>()
427 .await;
428
429 buffer.update(&mut cx, |buffer, cx| {
430 // Edit the buffer, moving the content down
431 buffer.edit([0..0], "\n\n", cx);
432
433 // Receive diagnostics for an earlier version of the buffer.
434 buffer
435 .update_diagnostics(
436 Some(open_notification.text_document.version),
437 vec![
438 lsp::Diagnostic {
439 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
440 severity: Some(lsp::DiagnosticSeverity::ERROR),
441 message: "undefined variable 'A'".to_string(),
442 ..Default::default()
443 },
444 lsp::Diagnostic {
445 range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
446 severity: Some(lsp::DiagnosticSeverity::ERROR),
447 message: "undefined variable 'BB'".to_string(),
448 ..Default::default()
449 },
450 lsp::Diagnostic {
451 range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
452 severity: Some(lsp::DiagnosticSeverity::ERROR),
453 message: "undefined variable 'CCC'".to_string(),
454 ..Default::default()
455 },
456 ],
457 cx,
458 )
459 .unwrap();
460
461 // The diagnostics have moved down since they were created.
462 assert_eq!(
463 buffer
464 .diagnostics_in_range(Point::new(3, 0)..Point::new(5, 0))
465 .collect::<Vec<_>>(),
466 &[
467 Diagnostic {
468 range: Point::new(3, 9)..Point::new(3, 11),
469 severity: DiagnosticSeverity::ERROR,
470 message: "undefined variable 'BB'".to_string()
471 },
472 Diagnostic {
473 range: Point::new(4, 9)..Point::new(4, 12),
474 severity: DiagnosticSeverity::ERROR,
475 message: "undefined variable 'CCC'".to_string()
476 }
477 ]
478 );
479 assert_eq!(
480 chunks_with_diagnostics(buffer, 0..buffer.len()),
481 [
482 ("\n\nfn a() { ".to_string(), None),
483 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
484 (" }\nfn b() { ".to_string(), None),
485 ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
486 (" }\nfn c() { ".to_string(), None),
487 ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
488 (" }\n".to_string(), None),
489 ]
490 );
491 assert_eq!(
492 chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
493 [
494 ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
495 (" }\nfn c() { ".to_string(), None),
496 ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
497 ]
498 );
499
500 // Ensure overlapping diagnostics are highlighted correctly.
501 buffer
502 .update_diagnostics(
503 Some(open_notification.text_document.version),
504 vec![
505 lsp::Diagnostic {
506 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
507 severity: Some(lsp::DiagnosticSeverity::ERROR),
508 message: "undefined variable 'A'".to_string(),
509 ..Default::default()
510 },
511 lsp::Diagnostic {
512 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
513 severity: Some(lsp::DiagnosticSeverity::WARNING),
514 message: "unreachable statement".to_string(),
515 ..Default::default()
516 },
517 ],
518 cx,
519 )
520 .unwrap();
521 assert_eq!(
522 buffer
523 .diagnostics_in_range(Point::new(2, 0)..Point::new(3, 0))
524 .collect::<Vec<_>>(),
525 &[
526 Diagnostic {
527 range: Point::new(2, 9)..Point::new(2, 12),
528 severity: DiagnosticSeverity::WARNING,
529 message: "unreachable statement".to_string()
530 },
531 Diagnostic {
532 range: Point::new(2, 9)..Point::new(2, 10),
533 severity: DiagnosticSeverity::ERROR,
534 message: "undefined variable 'A'".to_string()
535 },
536 ]
537 );
538 assert_eq!(
539 chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
540 [
541 ("fn a() { ".to_string(), None),
542 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
543 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
544 ("\n".to_string(), None),
545 ]
546 );
547 assert_eq!(
548 chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
549 [
550 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
551 ("\n".to_string(), None),
552 ]
553 );
554
555 fn chunks_with_diagnostics<T: ToOffset>(
556 buffer: &Buffer,
557 range: Range<T>,
558 ) -> Vec<(String, Option<DiagnosticSeverity>)> {
559 let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
560 for chunk in buffer.snapshot().highlighted_text_for_range(range) {
561 if chunks
562 .last()
563 .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
564 {
565 chunks.last_mut().unwrap().0.push_str(chunk.text);
566 } else {
567 chunks.push((chunk.text.to_string(), chunk.diagnostic));
568 }
569 }
570 chunks
571 }
572 });
573}
574
575#[test]
576fn test_contiguous_ranges() {
577 assert_eq!(
578 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
579 &[1..4, 5..7, 9..13]
580 );
581
582 // Respects the `max_len` parameter
583 assert_eq!(
584 contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
585 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
586 );
587}
588
589impl Buffer {
590 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
591 &self,
592 range: Range<T>,
593 ) -> Option<(Range<Point>, Range<Point>)> {
594 self.enclosing_bracket_ranges(range).map(|(start, end)| {
595 let point_start = start.start.to_point(self)..start.end.to_point(self);
596 let point_end = end.start.to_point(self)..end.end.to_point(self);
597 (point_start, point_end)
598 })
599 }
600}
601
602fn rust_lang() -> Option<Arc<Language>> {
603 Some(Arc::new(
604 Language::new(
605 LanguageConfig {
606 name: "Rust".to_string(),
607 path_suffixes: vec!["rs".to_string()],
608 ..Default::default()
609 },
610 tree_sitter_rust::language(),
611 )
612 .with_indents_query(
613 r#"
614 (call_expression) @indent
615 (field_expression) @indent
616 (_ "(" ")" @end) @indent
617 (_ "{" "}" @end) @indent
618 "#,
619 )
620 .unwrap()
621 .with_brackets_query(r#" ("{" @open "}" @close) "#)
622 .unwrap(),
623 ))
624}
625
626fn empty(point: Point) -> Range<Point> {
627 point..point
628}