tests.rs

  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}