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_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
200    cx.add_model(|cx| {
201        let text = "
202            fn a() {
203            c;
204            d;
205            }
206        "
207        .unindent()
208        .into();
209        let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
210
211        // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
212        // their indentation is not adjusted.
213        buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
214        assert_eq!(
215            buffer.text(),
216            "
217            fn a() {
218            c();
219            d();
220            }
221            "
222            .unindent()
223        );
224
225        // When appending new content after these lines, the indentation is based on the
226        // preceding lines' actual indentation.
227        buffer.edit_with_autoindent(
228            [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
229            "\n.f\n.g",
230            cx,
231        );
232        assert_eq!(
233            buffer.text(),
234            "
235            fn a() {
236            c
237                .f
238                .g();
239            d
240                .f
241                .g();
242            }
243            "
244            .unindent()
245        );
246        buffer
247    });
248}
249
250#[gpui::test]
251fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
252    cx.add_model(|cx| {
253        let text = History::new(
254            "
255                fn a() {}
256            "
257            .unindent()
258            .into(),
259        );
260        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
261
262        buffer.edit_with_autoindent([5..5], "\nb", cx);
263        assert_eq!(
264            buffer.text(),
265            "
266                fn a(
267                    b) {}
268            "
269            .unindent()
270        );
271
272        // The indentation suggestion changed because `@end` node (a close paren)
273        // is now at the beginning of the line.
274        buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
275        assert_eq!(
276            buffer.text(),
277            "
278                fn a(
279                ) {}
280            "
281            .unindent()
282        );
283
284        buffer
285    });
286}
287
288fn empty(point: Point) -> Range<Point> {
289    point..point
290}
291
292#[test]
293fn test_contiguous_ranges() {
294    assert_eq!(
295        contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
296        &[1..4, 5..7, 9..13]
297    );
298
299    // Respects the `max_len` parameter
300    assert_eq!(
301        contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
302        &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
303    );
304}
305
306fn rust_lang() -> Arc<Language> {
307    Arc::new(
308        Language::new(
309            LanguageConfig {
310                name: "Rust".to_string(),
311                path_suffixes: vec!["rs".to_string()],
312                ..Default::default()
313            },
314            tree_sitter_rust::language(),
315        )
316        .with_indents_query(
317            r#"
318                (call_expression) @indent
319                (field_expression) @indent
320                (_ "(" ")" @end) @indent
321                (_ "{" "}" @end) @indent
322            "#,
323        )
324        .unwrap()
325        .with_brackets_query(r#" ("{" @open "}" @close) "#)
326        .unwrap(),
327    )
328}