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 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}