syntax.rs

  1use crate::*;
  2use gpui::{ModelHandle, MutableAppContext};
  3use unindent::Unindent as _;
  4
  5#[gpui::test]
  6async fn test_reparse(mut cx: gpui::TestAppContext) {
  7    let buffer = cx.add_model(|cx| {
  8        let text = "fn a() {}".into();
  9        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
 10    });
 11
 12    // Wait for the initial text to parse
 13    buffer
 14        .condition(&cx, |buffer, _| !buffer.is_parsing())
 15        .await;
 16    assert_eq!(
 17        get_tree_sexp(&buffer, &cx),
 18        concat!(
 19            "(source_file (function_item name: (identifier) ",
 20            "parameters: (parameters) ",
 21            "body: (block)))"
 22        )
 23    );
 24
 25    buffer.update(&mut cx, |buffer, _| {
 26        buffer.set_sync_parse_timeout(Duration::ZERO)
 27    });
 28
 29    // Perform some edits (add parameter and variable reference)
 30    // Parsing doesn't begin until the transaction is complete
 31    buffer.update(&mut cx, |buf, cx| {
 32        buf.start_transaction(None).unwrap();
 33
 34        let offset = buf.text().find(")").unwrap();
 35        buf.edit(vec![offset..offset], "b: C", cx);
 36        assert!(!buf.is_parsing());
 37
 38        let offset = buf.text().find("}").unwrap();
 39        buf.edit(vec![offset..offset], " d; ", cx);
 40        assert!(!buf.is_parsing());
 41
 42        buf.end_transaction(None, cx).unwrap();
 43        assert_eq!(buf.text(), "fn a(b: C) { d; }");
 44        assert!(buf.is_parsing());
 45    });
 46    buffer
 47        .condition(&cx, |buffer, _| !buffer.is_parsing())
 48        .await;
 49    assert_eq!(
 50        get_tree_sexp(&buffer, &cx),
 51        concat!(
 52            "(source_file (function_item name: (identifier) ",
 53            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
 54            "body: (block (identifier))))"
 55        )
 56    );
 57
 58    // Perform a series of edits without waiting for the current parse to complete:
 59    // * turn identifier into a field expression
 60    // * turn field expression into a method call
 61    // * add a turbofish to the method call
 62    buffer.update(&mut cx, |buf, cx| {
 63        let offset = buf.text().find(";").unwrap();
 64        buf.edit(vec![offset..offset], ".e", cx);
 65        assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
 66        assert!(buf.is_parsing());
 67    });
 68    buffer.update(&mut cx, |buf, cx| {
 69        let offset = buf.text().find(";").unwrap();
 70        buf.edit(vec![offset..offset], "(f)", cx);
 71        assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
 72        assert!(buf.is_parsing());
 73    });
 74    buffer.update(&mut cx, |buf, cx| {
 75        let offset = buf.text().find("(f)").unwrap();
 76        buf.edit(vec![offset..offset], "::<G>", cx);
 77        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
 78        assert!(buf.is_parsing());
 79    });
 80    buffer
 81        .condition(&cx, |buffer, _| !buffer.is_parsing())
 82        .await;
 83    assert_eq!(
 84        get_tree_sexp(&buffer, &cx),
 85        concat!(
 86            "(source_file (function_item name: (identifier) ",
 87            "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
 88            "body: (block (call_expression ",
 89            "function: (generic_function ",
 90            "function: (field_expression value: (identifier) field: (field_identifier)) ",
 91            "type_arguments: (type_arguments (type_identifier))) ",
 92            "arguments: (arguments (identifier))))))",
 93        )
 94    );
 95
 96    buffer.update(&mut cx, |buf, cx| {
 97        buf.undo(cx);
 98        assert_eq!(buf.text(), "fn a() {}");
 99        assert!(buf.is_parsing());
100    });
101    buffer
102        .condition(&cx, |buffer, _| !buffer.is_parsing())
103        .await;
104    assert_eq!(
105        get_tree_sexp(&buffer, &cx),
106        concat!(
107            "(source_file (function_item name: (identifier) ",
108            "parameters: (parameters) ",
109            "body: (block)))"
110        )
111    );
112
113    buffer.update(&mut cx, |buf, cx| {
114        buf.redo(cx);
115        assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
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 (call_expression ",
127            "function: (generic_function ",
128            "function: (field_expression value: (identifier) field: (field_identifier)) ",
129            "type_arguments: (type_arguments (type_identifier))) ",
130            "arguments: (arguments (identifier))))))",
131        )
132    );
133
134    fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
135        buffer.read_with(cx, |buffer, _| {
136            buffer.syntax_tree().unwrap().root_node().to_sexp()
137        })
138    }
139}
140
141#[gpui::test]
142fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
143    let buffer = cx.add_model(|cx| {
144        let text = "
145            mod x {
146                mod y {
147
148                }
149            }
150        "
151        .unindent()
152        .into();
153        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
154    });
155    let buffer = buffer.read(cx);
156    assert_eq!(
157        buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
158        Some((
159            Point::new(0, 6)..Point::new(0, 7),
160            Point::new(4, 0)..Point::new(4, 1)
161        ))
162    );
163    assert_eq!(
164        buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
165        Some((
166            Point::new(1, 10)..Point::new(1, 11),
167            Point::new(3, 4)..Point::new(3, 5)
168        ))
169    );
170    assert_eq!(
171        buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
172        Some((
173            Point::new(1, 10)..Point::new(1, 11),
174            Point::new(3, 4)..Point::new(3, 5)
175        ))
176    );
177}
178
179#[gpui::test]
180fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
181    cx.add_model(|cx| {
182        let text = "fn a() {}".into();
183        let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
184
185        buffer.edit_with_autoindent([8..8], "\n\n", cx);
186        assert_eq!(buffer.text(), "fn a() {\n    \n}");
187
188        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
189        assert_eq!(buffer.text(), "fn a() {\n    b()\n    \n}");
190
191        buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
192        assert_eq!(buffer.text(), "fn a() {\n    b()\n        .c\n}");
193
194        buffer
195    });
196}
197
198#[gpui::test]
199fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
200    cx.add_model(|cx| {
201        let text = History::new("fn a() {}".into());
202        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
203
204        let selection_set_id = buffer.add_selection_set(Vec::new(), cx);
205        buffer.start_transaction(Some(selection_set_id)).unwrap();
206        buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
207        buffer
208            .update_selection_set(
209                selection_set_id,
210                vec![
211                    Selection {
212                        id: 0,
213                        start: buffer.anchor_before(Point::new(1, 0)),
214                        end: buffer.anchor_before(Point::new(1, 0)),
215                        reversed: false,
216                        goal: SelectionGoal::None,
217                    },
218                    Selection {
219                        id: 1,
220                        start: buffer.anchor_before(Point::new(4, 0)),
221                        end: buffer.anchor_before(Point::new(4, 0)),
222                        reversed: false,
223                        goal: SelectionGoal::None,
224                    },
225                ],
226                cx,
227            )
228            .unwrap();
229        assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
230
231        // Ending the transaction runs the auto-indent. The selection
232        // at the start of the auto-indented row is pushed to the right.
233        buffer.end_transaction(Some(selection_set_id), cx).unwrap();
234        assert_eq!(buffer.text(), "fn a(\n    \n) {}\n\n");
235        let selection_ranges = buffer
236            .selection_set(selection_set_id)
237            .unwrap()
238            .selections
239            .iter()
240            .map(|selection| selection.point_range(&buffer))
241            .collect::<Vec<_>>();
242
243        assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
244        assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
245
246        buffer
247    });
248}
249
250#[gpui::test]
251fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
252    cx.add_model(|cx| {
253        let text = "
254            fn a() {
255            c;
256            d;
257            }
258        "
259        .unindent()
260        .into();
261        let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
262
263        // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
264        // their indentation is not adjusted.
265        buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
266        assert_eq!(
267            buffer.text(),
268            "
269            fn a() {
270            c();
271            d();
272            }
273            "
274            .unindent()
275        );
276
277        // When appending new content after these lines, the indentation is based on the
278        // preceding lines' actual indentation.
279        buffer.edit_with_autoindent(
280            [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
281            "\n.f\n.g",
282            cx,
283        );
284        assert_eq!(
285            buffer.text(),
286            "
287            fn a() {
288            c
289                .f
290                .g();
291            d
292                .f
293                .g();
294            }
295            "
296            .unindent()
297        );
298        buffer
299    });
300}
301
302#[gpui::test]
303fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
304    cx.add_model(|cx| {
305        let text = History::new(
306            "
307                fn a() {}
308            "
309            .unindent()
310            .into(),
311        );
312        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
313
314        buffer.edit_with_autoindent([5..5], "\nb", cx);
315        assert_eq!(
316            buffer.text(),
317            "
318                fn a(
319                    b) {}
320            "
321            .unindent()
322        );
323
324        // The indentation suggestion changed because `@end` node (a close paren)
325        // is now at the beginning of the line.
326        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
327        assert_eq!(
328            buffer.text(),
329            "
330                fn a(
331                ) {}
332            "
333            .unindent()
334        );
335
336        buffer
337    });
338}
339
340#[test]
341fn test_contiguous_ranges() {
342    assert_eq!(
343        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
344        &[1..4, 5..7, 9..13]
345    );
346
347    // Respects the `max_len` parameter
348    assert_eq!(
349        contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
350        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
351    );
352}
353
354impl Buffer {
355    pub fn enclosing_bracket_point_ranges<T: ToOffset>(
356        &self,
357        range: Range<T>,
358    ) -> Option<(Range<Point>, Range<Point>)> {
359        self.enclosing_bracket_ranges(range).map(|(start, end)| {
360            let point_start = start.start.to_point(self)..start.end.to_point(self);
361            let point_end = end.start.to_point(self)..end.end.to_point(self);
362            (point_start, point_end)
363        })
364    }
365}
366
367fn rust_lang() -> Arc<Language> {
368    Arc::new(
369        Language::new(
370            LanguageConfig {
371                name: "Rust".to_string(),
372                path_suffixes: vec!["rs".to_string()],
373                ..Default::default()
374            },
375            tree_sitter_rust::language(),
376        )
377        .with_indents_query(
378            r#"
379                (call_expression) @indent
380                (field_expression) @indent
381                (_ "(" ")" @end) @indent
382                (_ "{" "}" @end) @indent
383            "#,
384        )
385        .unwrap()
386        .with_brackets_query(r#" ("{" @open "}" @close) "#)
387        .unwrap(),
388    )
389}
390
391fn empty(point: Point) -> Range<Point> {
392    point..point
393}