tests.rs

  1use super::*;
  2use gpui::{ModelHandle, MutableAppContext};
  3use std::{iter::FromIterator, 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| {
 83        Buffer::new(0, text, cx).with_language(Some(Arc::new(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        Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
227    });
228    let buffer = buffer.read(cx);
229    assert_eq!(
230        buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
231        Some((
232            Point::new(0, 6)..Point::new(0, 7),
233            Point::new(4, 0)..Point::new(4, 1)
234        ))
235    );
236    assert_eq!(
237        buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
238        Some((
239            Point::new(1, 10)..Point::new(1, 11),
240            Point::new(3, 4)..Point::new(3, 5)
241        ))
242    );
243    assert_eq!(
244        buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
245        Some((
246            Point::new(1, 10)..Point::new(1, 11),
247            Point::new(3, 4)..Point::new(3, 5)
248        ))
249    );
250}
251
252#[gpui::test]
253fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
254    cx.add_model(|cx| {
255        let text = "fn a() {}";
256        let mut buffer =
257            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
258
259        buffer.edit_with_autoindent([8..8], "\n\n", cx);
260        assert_eq!(buffer.text(), "fn a() {\n    \n}");
261
262        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
263        assert_eq!(buffer.text(), "fn a() {\n    b()\n    \n}");
264
265        buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
266        assert_eq!(buffer.text(), "fn a() {\n    b()\n        .c\n}");
267
268        buffer
269    });
270}
271
272#[gpui::test]
273fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
274    cx.add_model(|cx| {
275        let text = "fn a() {}";
276
277        let mut buffer =
278            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
279
280        let selection_set_id = buffer.add_selection_set::<usize>(&[], cx);
281        buffer.start_transaction(Some(selection_set_id)).unwrap();
282        buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
283        buffer
284            .update_selection_set(
285                selection_set_id,
286                &[
287                    Selection {
288                        id: 0,
289                        start: Point::new(1, 0),
290                        end: Point::new(1, 0),
291                        reversed: false,
292                        goal: SelectionGoal::None,
293                    },
294                    Selection {
295                        id: 1,
296                        start: Point::new(4, 0),
297                        end: Point::new(4, 0),
298                        reversed: false,
299                        goal: SelectionGoal::None,
300                    },
301                ],
302                cx,
303            )
304            .unwrap();
305        assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
306
307        // Ending the transaction runs the auto-indent. The selection
308        // at the start of the auto-indented row is pushed to the right.
309        buffer.end_transaction(Some(selection_set_id), cx).unwrap();
310        assert_eq!(buffer.text(), "fn a(\n    \n) {}\n\n");
311        let selection_ranges = buffer
312            .selection_set(selection_set_id)
313            .unwrap()
314            .selections::<Point, _>(&buffer)
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
336        let mut buffer =
337            Buffer::new(0, text, cx).with_language(Some(Arc::new(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 = "
382            fn a() {}
383        "
384        .unindent();
385
386        let mut buffer =
387            Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
388
389        buffer.edit_with_autoindent([5..5], "\nb", cx);
390        assert_eq!(
391            buffer.text(),
392            "
393                fn a(
394                    b) {}
395            "
396            .unindent()
397        );
398
399        // The indentation suggestion changed because `@end` node (a close paren)
400        // is now at the beginning of the line.
401        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
402        assert_eq!(
403            buffer.text(),
404            "
405                fn a(
406                ) {}
407            "
408            .unindent()
409        );
410
411        buffer
412    });
413}
414
415#[gpui::test]
416async fn test_diagnostics(mut cx: gpui::TestAppContext) {
417    let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()).await;
418    let mut rust_lang = rust_lang();
419    rust_lang.config.language_server = Some(LanguageServerConfig {
420        disk_based_diagnostic_sources: HashSet::from_iter(["disk".to_string()]),
421        ..Default::default()
422    });
423
424    let text = "
425        fn a() { A }
426        fn b() { BB }
427        fn c() { CCC }
428    "
429    .unindent();
430
431    let buffer = cx.add_model(|cx| {
432        Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang)), Some(language_server), cx)
433    });
434
435    let open_notification = fake
436        .receive_notification::<lsp::notification::DidOpenTextDocument>()
437        .await;
438
439    // Edit the buffer, moving the content down
440    buffer.update(&mut cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx));
441    let change_notification_1 = fake
442        .receive_notification::<lsp::notification::DidChangeTextDocument>()
443        .await;
444    assert!(change_notification_1.text_document.version > open_notification.text_document.version);
445
446    buffer.update(&mut cx, |buffer, cx| {
447        // Receive diagnostics for an earlier version of the buffer.
448        buffer
449            .update_diagnostics(
450                Some(open_notification.text_document.version),
451                vec![
452                    lsp::Diagnostic {
453                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
454                        severity: Some(lsp::DiagnosticSeverity::ERROR),
455                        message: "undefined variable 'A'".to_string(),
456                        ..Default::default()
457                    },
458                    lsp::Diagnostic {
459                        range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
460                        severity: Some(lsp::DiagnosticSeverity::ERROR),
461                        message: "undefined variable 'BB'".to_string(),
462                        ..Default::default()
463                    },
464                    lsp::Diagnostic {
465                        range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
466                        severity: Some(lsp::DiagnosticSeverity::ERROR),
467                        message: "undefined variable 'CCC'".to_string(),
468                        ..Default::default()
469                    },
470                ],
471                cx,
472            )
473            .unwrap();
474
475        // The diagnostics have moved down since they were created.
476        assert_eq!(
477            buffer
478                .diagnostics_in_range(Point::new(3, 0)..Point::new(5, 0))
479                .collect::<Vec<_>>(),
480            &[
481                (
482                    Point::new(3, 9)..Point::new(3, 11),
483                    &Diagnostic {
484                        severity: DiagnosticSeverity::ERROR,
485                        message: "undefined variable 'BB'".to_string()
486                    },
487                ),
488                (
489                    Point::new(4, 9)..Point::new(4, 12),
490                    &Diagnostic {
491                        severity: DiagnosticSeverity::ERROR,
492                        message: "undefined variable 'CCC'".to_string()
493                    }
494                )
495            ]
496        );
497        assert_eq!(
498            chunks_with_diagnostics(buffer, 0..buffer.len()),
499            [
500                ("\n\nfn a() { ".to_string(), None),
501                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
502                (" }\nfn b() { ".to_string(), None),
503                ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
504                (" }\nfn c() { ".to_string(), None),
505                ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
506                (" }\n".to_string(), None),
507            ]
508        );
509        assert_eq!(
510            chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
511            [
512                ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
513                (" }\nfn c() { ".to_string(), None),
514                ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
515            ]
516        );
517
518        // Ensure overlapping diagnostics are highlighted correctly.
519        buffer
520            .update_diagnostics(
521                Some(open_notification.text_document.version),
522                vec![
523                    lsp::Diagnostic {
524                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
525                        severity: Some(lsp::DiagnosticSeverity::ERROR),
526                        message: "undefined variable 'A'".to_string(),
527                        ..Default::default()
528                    },
529                    lsp::Diagnostic {
530                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
531                        severity: Some(lsp::DiagnosticSeverity::WARNING),
532                        message: "unreachable statement".to_string(),
533                        ..Default::default()
534                    },
535                ],
536                cx,
537            )
538            .unwrap();
539        assert_eq!(
540            buffer
541                .diagnostics_in_range(Point::new(2, 0)..Point::new(3, 0))
542                .collect::<Vec<_>>(),
543            &[
544                (
545                    Point::new(2, 9)..Point::new(2, 12),
546                    &Diagnostic {
547                        severity: DiagnosticSeverity::WARNING,
548                        message: "unreachable statement".to_string()
549                    }
550                ),
551                (
552                    Point::new(2, 9)..Point::new(2, 10),
553                    &Diagnostic {
554                        severity: DiagnosticSeverity::ERROR,
555                        message: "undefined variable 'A'".to_string()
556                    },
557                )
558            ]
559        );
560        assert_eq!(
561            chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
562            [
563                ("fn a() { ".to_string(), None),
564                ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
565                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
566                ("\n".to_string(), None),
567            ]
568        );
569        assert_eq!(
570            chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
571            [
572                (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
573                ("\n".to_string(), None),
574            ]
575        );
576    });
577
578    // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
579    // changes since the last save.
580    buffer.update(&mut cx, |buffer, cx| {
581        buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), "    ", cx);
582        buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
583    });
584    let change_notification_2 = fake
585        .receive_notification::<lsp::notification::DidChangeTextDocument>()
586        .await;
587    assert!(
588        change_notification_2.text_document.version > change_notification_1.text_document.version
589    );
590
591    buffer.update(&mut cx, |buffer, cx| {
592        buffer
593            .update_diagnostics(
594                Some(change_notification_2.text_document.version),
595                vec![
596                    lsp::Diagnostic {
597                        range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
598                        severity: Some(lsp::DiagnosticSeverity::ERROR),
599                        message: "undefined variable 'BB'".to_string(),
600                        source: Some("disk".to_string()),
601                        ..Default::default()
602                    },
603                    lsp::Diagnostic {
604                        range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
605                        severity: Some(lsp::DiagnosticSeverity::ERROR),
606                        message: "undefined variable 'A'".to_string(),
607                        source: Some("disk".to_string()),
608                        ..Default::default()
609                    },
610                ],
611                cx,
612            )
613            .unwrap();
614        assert_eq!(
615            buffer
616                .diagnostics_in_range(0..buffer.len())
617                .collect::<Vec<_>>(),
618            &[
619                (
620                    Point::new(2, 21)..Point::new(2, 22),
621                    &Diagnostic {
622                        severity: DiagnosticSeverity::ERROR,
623                        message: "undefined variable 'A'".to_string()
624                    }
625                ),
626                (
627                    Point::new(3, 9)..Point::new(3, 11),
628                    &Diagnostic {
629                        severity: DiagnosticSeverity::ERROR,
630                        message: "undefined variable 'BB'".to_string()
631                    },
632                )
633            ]
634        );
635    });
636}
637
638#[gpui::test]
639async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
640    cx.add_model(|cx| {
641        let text = concat!(
642            "let one = ;\n", //
643            "let two = \n",
644            "let three = 3;\n",
645        );
646
647        let mut buffer = Buffer::new(0, text, cx);
648        buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
649        buffer
650            .update_diagnostics(
651                None,
652                vec![
653                    lsp::Diagnostic {
654                        range: lsp::Range::new(
655                            lsp::Position::new(0, 10),
656                            lsp::Position::new(0, 10),
657                        ),
658                        severity: Some(lsp::DiagnosticSeverity::ERROR),
659                        message: "syntax error 1".to_string(),
660                        ..Default::default()
661                    },
662                    lsp::Diagnostic {
663                        range: lsp::Range::new(
664                            lsp::Position::new(1, 10),
665                            lsp::Position::new(1, 10),
666                        ),
667                        severity: Some(lsp::DiagnosticSeverity::ERROR),
668                        message: "syntax error 2".to_string(),
669                        ..Default::default()
670                    },
671                ],
672                cx,
673            )
674            .unwrap();
675
676        // An empty range is extended forward to include the following character.
677        // At the end of a line, an empty range is extended backward to include
678        // the preceding character.
679        let chunks = chunks_with_diagnostics(&buffer, 0..buffer.len());
680        assert_eq!(
681            chunks
682                .iter()
683                .map(|(s, d)| (s.as_str(), *d))
684                .collect::<Vec<_>>(),
685            &[
686                ("let one = ", None),
687                (";", Some(lsp::DiagnosticSeverity::ERROR)),
688                ("\nlet two =", None),
689                (" ", Some(lsp::DiagnosticSeverity::ERROR)),
690                ("\nlet three = 3;\n", None)
691            ]
692        );
693        buffer
694    });
695}
696
697fn chunks_with_diagnostics<T: ToOffset>(
698    buffer: &Buffer,
699    range: Range<T>,
700) -> Vec<(String, Option<DiagnosticSeverity>)> {
701    let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
702    for chunk in buffer.snapshot().highlighted_text_for_range(range) {
703        if chunks
704            .last()
705            .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
706        {
707            chunks.last_mut().unwrap().0.push_str(chunk.text);
708        } else {
709            chunks.push((chunk.text.to_string(), chunk.diagnostic));
710        }
711    }
712    chunks
713}
714
715#[test]
716fn test_contiguous_ranges() {
717    assert_eq!(
718        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
719        &[1..4, 5..7, 9..13]
720    );
721
722    // Respects the `max_len` parameter
723    assert_eq!(
724        contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
725        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
726    );
727}
728
729impl Buffer {
730    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
731        &self,
732        range: Range<T>,
733    ) -> Option<(Range<Point>, Range<Point>)> {
734        self.enclosing_bracket_ranges(range).map(|(start, end)| {
735            let point_start = start.start.to_point(self)..start.end.to_point(self);
736            let point_end = end.start.to_point(self)..end.end.to_point(self);
737            (point_start, point_end)
738        })
739    }
740}
741
742fn rust_lang() -> Language {
743    Language::new(
744        LanguageConfig {
745            name: "Rust".to_string(),
746            path_suffixes: vec!["rs".to_string()],
747            language_server: None,
748            ..Default::default()
749        },
750        tree_sitter_rust::language(),
751    )
752    .with_indents_query(
753        r#"
754                (call_expression) @indent
755                (field_expression) @indent
756                (_ "(" ")" @end) @indent
757                (_ "{" "}" @end) @indent
758            "#,
759    )
760    .unwrap()
761    .with_brackets_query(r#" ("{" @open "}" @close) "#)
762    .unwrap()
763}
764
765fn empty(point: Point) -> Range<Point> {
766    point..point
767}