1use super::*;
2use crate::language_settings::{
3 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
4};
5use crate::Buffer;
6use clock::ReplicaId;
7use collections::BTreeMap;
8use futures::FutureExt as _;
9use gpui::{AppContext, BorrowAppContext, Model};
10use gpui::{Context, TestAppContext};
11use indoc::indoc;
12use proto::deserialize_operation;
13use rand::prelude::*;
14use regex::RegexBuilder;
15use settings::SettingsStore;
16use std::{
17 env,
18 ops::Range,
19 time::{Duration, Instant},
20};
21use text::network::Network;
22use text::{BufferId, LineEnding};
23use text::{Point, ToPoint};
24use unindent::Unindent as _;
25use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
26
27lazy_static! {
28 static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$")
29 .multi_line(true)
30 .build()
31 .unwrap();
32}
33
34#[cfg(test)]
35#[ctor::ctor]
36fn init_logger() {
37 if std::env::var("RUST_LOG").is_ok() {
38 env_logger::init();
39 }
40}
41
42#[gpui::test]
43fn test_line_endings(cx: &mut gpui::AppContext) {
44 init_settings(cx, |_| {});
45
46 cx.new_model(|cx| {
47 let mut buffer =
48 Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
49 assert_eq!(buffer.text(), "one\ntwo\nthree");
50 assert_eq!(buffer.line_ending(), LineEnding::Windows);
51
52 buffer.check_invariants();
53 buffer.edit(
54 [(buffer.len()..buffer.len(), "\r\nfour")],
55 Some(AutoindentMode::EachLine),
56 cx,
57 );
58 buffer.edit([(0..0, "zero\r\n")], None, cx);
59 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
60 assert_eq!(buffer.line_ending(), LineEnding::Windows);
61 buffer.check_invariants();
62
63 buffer
64 });
65}
66
67#[gpui::test]
68fn test_select_language(cx: &mut AppContext) {
69 init_settings(cx, |_| {});
70
71 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
72 registry.add(Arc::new(Language::new(
73 LanguageConfig {
74 name: "Rust".into(),
75 matcher: LanguageMatcher {
76 path_suffixes: vec!["rs".to_string()],
77 ..Default::default()
78 },
79 ..Default::default()
80 },
81 Some(tree_sitter_rust::language()),
82 )));
83 registry.add(Arc::new(Language::new(
84 LanguageConfig {
85 name: "Make".into(),
86 matcher: LanguageMatcher {
87 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
88 ..Default::default()
89 },
90 ..Default::default()
91 },
92 Some(tree_sitter_rust::language()),
93 )));
94
95 // matching file extension
96 assert_eq!(
97 registry
98 .language_for_file(&file("src/lib.rs"), None, cx)
99 .now_or_never()
100 .and_then(|l| Some(l.ok()?.name())),
101 Some("Rust".into())
102 );
103 assert_eq!(
104 registry
105 .language_for_file(&file("src/lib.mk"), None, cx)
106 .now_or_never()
107 .and_then(|l| Some(l.ok()?.name())),
108 Some("Make".into())
109 );
110
111 // matching filename
112 assert_eq!(
113 registry
114 .language_for_file(&file("src/Makefile"), None, cx)
115 .now_or_never()
116 .and_then(|l| Some(l.ok()?.name())),
117 Some("Make".into())
118 );
119
120 // matching suffix that is not the full file extension or filename
121 assert_eq!(
122 registry
123 .language_for_file(&file("zed/cars"), None, cx)
124 .now_or_never()
125 .and_then(|l| Some(l.ok()?.name())),
126 None
127 );
128 assert_eq!(
129 registry
130 .language_for_file(&file("zed/a.cars"), None, cx)
131 .now_or_never()
132 .and_then(|l| Some(l.ok()?.name())),
133 None
134 );
135 assert_eq!(
136 registry
137 .language_for_file(&file("zed/sumk"), None, cx)
138 .now_or_never()
139 .and_then(|l| Some(l.ok()?.name())),
140 None
141 );
142}
143
144#[gpui::test(iterations = 10)]
145async fn test_first_line_pattern(cx: &mut TestAppContext) {
146 cx.update(|cx| init_settings(cx, |_| {}));
147
148 let languages = LanguageRegistry::test(cx.executor());
149 let languages = Arc::new(languages);
150
151 languages.register_test_language(LanguageConfig {
152 name: "JavaScript".into(),
153 matcher: LanguageMatcher {
154 path_suffixes: vec!["js".into()],
155 first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
156 },
157 ..Default::default()
158 });
159
160 cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
161 .await
162 .unwrap_err();
163 cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
164 .await
165 .unwrap_err();
166 assert_eq!(
167 cx.read(|cx| languages.language_for_file(
168 &file("the/script"),
169 Some(&"#!/bin/env node".into()),
170 cx
171 ))
172 .await
173 .unwrap()
174 .name()
175 .as_ref(),
176 "JavaScript"
177 );
178}
179
180#[gpui::test]
181async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {
182 cx.update(|cx| {
183 init_settings(cx, |settings| {
184 settings.file_types.extend([
185 ("TypeScript".into(), vec!["js".into()]),
186 ("C++".into(), vec!["c".into()]),
187 (
188 "Dockerfile".into(),
189 vec!["Dockerfile".into(), "Dockerfile.*".into()],
190 ),
191 ]);
192 })
193 });
194
195 let languages = Arc::new(LanguageRegistry::test(cx.executor()));
196
197 for config in [
198 LanguageConfig {
199 name: "JavaScript".into(),
200 matcher: LanguageMatcher {
201 path_suffixes: vec!["js".to_string()],
202 ..Default::default()
203 },
204 ..Default::default()
205 },
206 LanguageConfig {
207 name: "TypeScript".into(),
208 matcher: LanguageMatcher {
209 path_suffixes: vec!["js".to_string()],
210 ..Default::default()
211 },
212 ..Default::default()
213 },
214 LanguageConfig {
215 name: "C++".into(),
216 matcher: LanguageMatcher {
217 path_suffixes: vec!["cpp".to_string()],
218 ..Default::default()
219 },
220 ..Default::default()
221 },
222 LanguageConfig {
223 name: "C".into(),
224 matcher: LanguageMatcher {
225 path_suffixes: vec!["c".to_string()],
226 ..Default::default()
227 },
228 ..Default::default()
229 },
230 LanguageConfig {
231 name: "Dockerfile".into(),
232 matcher: LanguageMatcher {
233 path_suffixes: vec!["Dockerfile".to_string()],
234 ..Default::default()
235 },
236 ..Default::default()
237 },
238 ] {
239 languages.add(Arc::new(Language::new(config, None)));
240 }
241
242 let language = cx
243 .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
244 .await
245 .unwrap();
246 assert_eq!(language.name().as_ref(), "TypeScript");
247 let language = cx
248 .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
249 .await
250 .unwrap();
251 assert_eq!(language.name().as_ref(), "C++");
252 let language = cx
253 .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
254 .await
255 .unwrap();
256 assert_eq!(language.name().as_ref(), "Dockerfile");
257}
258
259fn file(path: &str) -> Arc<dyn File> {
260 Arc::new(TestFile {
261 path: Path::new(path).into(),
262 root_name: "zed".into(),
263 })
264}
265
266#[gpui::test]
267fn test_edit_events(cx: &mut gpui::AppContext) {
268 let mut now = Instant::now();
269 let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
270 let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
271
272 let buffer1 = cx.new_model(|cx| Buffer::local("abcdef", cx));
273 let buffer2 = cx.new_model(|cx| {
274 Buffer::remote(
275 BufferId::from(cx.entity_id().as_non_zero_u64()),
276 1,
277 Capability::ReadWrite,
278 "abcdef",
279 )
280 });
281 let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
282 buffer1.update(cx, {
283 let buffer1_ops = buffer1_ops.clone();
284 |buffer, cx| {
285 let buffer_1_events = buffer_1_events.clone();
286 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
287 Event::Operation(op) => buffer1_ops.lock().push(op),
288 event => buffer_1_events.lock().push(event),
289 })
290 .detach();
291 let buffer_2_events = buffer_2_events.clone();
292 cx.subscribe(&buffer2, move |_, _, event, _| {
293 buffer_2_events.lock().push(event.clone())
294 })
295 .detach();
296
297 // An edit emits an edited event, followed by a dirty changed event,
298 // since the buffer was previously in a clean state.
299 buffer.edit([(2..4, "XYZ")], None, cx);
300
301 // An empty transaction does not emit any events.
302 buffer.start_transaction();
303 buffer.end_transaction(cx);
304
305 // A transaction containing two edits emits one edited event.
306 now += Duration::from_secs(1);
307 buffer.start_transaction_at(now);
308 buffer.edit([(5..5, "u")], None, cx);
309 buffer.edit([(6..6, "w")], None, cx);
310 buffer.end_transaction_at(now, cx);
311
312 // Undoing a transaction emits one edited event.
313 buffer.undo(cx);
314 }
315 });
316
317 // Incorporating a set of remote ops emits a single edited event,
318 // followed by a dirty changed event.
319 buffer2.update(cx, |buffer, cx| {
320 buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
321 });
322 assert_eq!(
323 mem::take(&mut *buffer_1_events.lock()),
324 vec![
325 Event::Edited,
326 Event::DirtyChanged,
327 Event::Edited,
328 Event::Edited,
329 ]
330 );
331 assert_eq!(
332 mem::take(&mut *buffer_2_events.lock()),
333 vec![Event::Edited, Event::DirtyChanged]
334 );
335
336 buffer1.update(cx, |buffer, cx| {
337 // Undoing the first transaction emits edited event, followed by a
338 // dirty changed event, since the buffer is again in a clean state.
339 buffer.undo(cx);
340 });
341 // Incorporating the remote ops again emits a single edited event,
342 // followed by a dirty changed event.
343 buffer2.update(cx, |buffer, cx| {
344 buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
345 });
346 assert_eq!(
347 mem::take(&mut *buffer_1_events.lock()),
348 vec![Event::Edited, Event::DirtyChanged,]
349 );
350 assert_eq!(
351 mem::take(&mut *buffer_2_events.lock()),
352 vec![Event::Edited, Event::DirtyChanged]
353 );
354}
355
356#[gpui::test]
357async fn test_apply_diff(cx: &mut TestAppContext) {
358 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
359 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
360 let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
361
362 let text = "a\nccc\ndddd\nffffff\n";
363 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
364 buffer.update(cx, |buffer, cx| {
365 buffer.apply_diff(diff, cx).unwrap();
366 assert_eq!(buffer.text(), text);
367 assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
368 });
369
370 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
371 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
372 buffer.update(cx, |buffer, cx| {
373 buffer.apply_diff(diff, cx).unwrap();
374 assert_eq!(buffer.text(), text);
375 assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
376 });
377}
378
379#[gpui::test(iterations = 10)]
380async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
381 let text = [
382 "zero", //
383 "one ", // 2 trailing spaces
384 "two", //
385 "three ", // 3 trailing spaces
386 "four", //
387 "five ", // 4 trailing spaces
388 ]
389 .join("\n");
390
391 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
392
393 // Spawn a task to format the buffer's whitespace.
394 // Pause so that the foratting task starts running.
395 let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
396 smol::future::yield_now().await;
397
398 // Edit the buffer while the normalization task is running.
399 let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
400 buffer.update(cx, |buffer, cx| {
401 buffer.edit(
402 [
403 (Point::new(0, 1)..Point::new(0, 1), "EE"),
404 (Point::new(3, 5)..Point::new(3, 5), "EEE"),
405 ],
406 None,
407 cx,
408 );
409 });
410
411 let format_diff = format.await;
412 buffer.update(cx, |buffer, cx| {
413 let version_before_format = format_diff.base_version.clone();
414 buffer.apply_diff(format_diff, cx);
415
416 // The outcome depends on the order of concurrent tasks.
417 //
418 // If the edit occurred while searching for trailing whitespace ranges,
419 // then the trailing whitespace region touched by the edit is left intact.
420 if version_before_format == version_before_edit {
421 assert_eq!(
422 buffer.text(),
423 [
424 "zEEero", //
425 "one", //
426 "two", //
427 "threeEEE ", //
428 "four", //
429 "five", //
430 ]
431 .join("\n")
432 );
433 }
434 // Otherwise, all trailing whitespace is removed.
435 else {
436 assert_eq!(
437 buffer.text(),
438 [
439 "zEEero", //
440 "one", //
441 "two", //
442 "threeEEE", //
443 "four", //
444 "five", //
445 ]
446 .join("\n")
447 );
448 }
449 });
450}
451
452#[gpui::test]
453async fn test_reparse(cx: &mut gpui::TestAppContext) {
454 let text = "fn a() {}";
455 let buffer =
456 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
457
458 // Wait for the initial text to parse
459 cx.executor().run_until_parked();
460 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
461 assert_eq!(
462 get_tree_sexp(&buffer, cx),
463 concat!(
464 "(source_file (function_item name: (identifier) ",
465 "parameters: (parameters) ",
466 "body: (block)))"
467 )
468 );
469
470 buffer.update(cx, |buffer, _| {
471 buffer.set_sync_parse_timeout(Duration::ZERO)
472 });
473
474 // Perform some edits (add parameter and variable reference)
475 // Parsing doesn't begin until the transaction is complete
476 buffer.update(cx, |buf, cx| {
477 buf.start_transaction();
478
479 let offset = buf.text().find(')').unwrap();
480 buf.edit([(offset..offset, "b: C")], None, cx);
481 assert!(!buf.is_parsing());
482
483 let offset = buf.text().find('}').unwrap();
484 buf.edit([(offset..offset, " d; ")], None, cx);
485 assert!(!buf.is_parsing());
486
487 buf.end_transaction(cx);
488 assert_eq!(buf.text(), "fn a(b: C) { d; }");
489 assert!(buf.is_parsing());
490 });
491 cx.executor().run_until_parked();
492 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
493 assert_eq!(
494 get_tree_sexp(&buffer, cx),
495 concat!(
496 "(source_file (function_item name: (identifier) ",
497 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
498 "body: (block (expression_statement (identifier)))))"
499 )
500 );
501
502 // Perform a series of edits without waiting for the current parse to complete:
503 // * turn identifier into a field expression
504 // * turn field expression into a method call
505 // * add a turbofish to the method call
506 buffer.update(cx, |buf, cx| {
507 let offset = buf.text().find(';').unwrap();
508 buf.edit([(offset..offset, ".e")], None, cx);
509 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
510 assert!(buf.is_parsing());
511 });
512 buffer.update(cx, |buf, cx| {
513 let offset = buf.text().find(';').unwrap();
514 buf.edit([(offset..offset, "(f)")], None, cx);
515 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
516 assert!(buf.is_parsing());
517 });
518 buffer.update(cx, |buf, cx| {
519 let offset = buf.text().find("(f)").unwrap();
520 buf.edit([(offset..offset, "::<G>")], None, cx);
521 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
522 assert!(buf.is_parsing());
523 });
524 cx.executor().run_until_parked();
525 assert_eq!(
526 get_tree_sexp(&buffer, cx),
527 concat!(
528 "(source_file (function_item name: (identifier) ",
529 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
530 "body: (block (expression_statement (call_expression ",
531 "function: (generic_function ",
532 "function: (field_expression value: (identifier) field: (field_identifier)) ",
533 "type_arguments: (type_arguments (type_identifier))) ",
534 "arguments: (arguments (identifier)))))))",
535 )
536 );
537
538 buffer.update(cx, |buf, cx| {
539 buf.undo(cx);
540 buf.undo(cx);
541 buf.undo(cx);
542 buf.undo(cx);
543 assert_eq!(buf.text(), "fn a() {}");
544 assert!(buf.is_parsing());
545 });
546
547 cx.executor().run_until_parked();
548 assert_eq!(
549 get_tree_sexp(&buffer, cx),
550 concat!(
551 "(source_file (function_item name: (identifier) ",
552 "parameters: (parameters) ",
553 "body: (block)))"
554 )
555 );
556
557 buffer.update(cx, |buf, cx| {
558 buf.redo(cx);
559 buf.redo(cx);
560 buf.redo(cx);
561 buf.redo(cx);
562 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
563 assert!(buf.is_parsing());
564 });
565 cx.executor().run_until_parked();
566 assert_eq!(
567 get_tree_sexp(&buffer, cx),
568 concat!(
569 "(source_file (function_item name: (identifier) ",
570 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
571 "body: (block (expression_statement (call_expression ",
572 "function: (generic_function ",
573 "function: (field_expression value: (identifier) field: (field_identifier)) ",
574 "type_arguments: (type_arguments (type_identifier))) ",
575 "arguments: (arguments (identifier)))))))",
576 )
577 );
578}
579
580#[gpui::test]
581async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
582 let buffer = cx.new_model(|cx| {
583 let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
584 buffer.set_sync_parse_timeout(Duration::ZERO);
585 buffer
586 });
587
588 // Wait for the initial text to parse
589 cx.executor().run_until_parked();
590 assert_eq!(
591 get_tree_sexp(&buffer, cx),
592 "(source_file (expression_statement (block)))"
593 );
594
595 buffer.update(cx, |buffer, cx| {
596 buffer.set_language(Some(Arc::new(json_lang())), cx)
597 });
598 cx.executor().run_until_parked();
599 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
600}
601
602#[gpui::test]
603async fn test_outline(cx: &mut gpui::TestAppContext) {
604 let text = r#"
605 struct Person {
606 name: String,
607 age: usize,
608 }
609
610 mod module {
611 enum LoginState {
612 LoggedOut,
613 LoggingOn,
614 LoggedIn {
615 person: Person,
616 time: Instant,
617 }
618 }
619 }
620
621 impl Eq for Person {}
622
623 impl Drop for Person {
624 fn drop(&mut self) {
625 println!("bye");
626 }
627 }
628 "#
629 .unindent();
630
631 let buffer =
632 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
633 let outline = buffer
634 .update(cx, |buffer, _| buffer.snapshot().outline(None))
635 .unwrap();
636
637 assert_eq!(
638 outline
639 .items
640 .iter()
641 .map(|item| (item.text.as_str(), item.depth))
642 .collect::<Vec<_>>(),
643 &[
644 ("struct Person", 0),
645 ("name", 1),
646 ("age", 1),
647 ("mod module", 0),
648 ("enum LoginState", 1),
649 ("LoggedOut", 2),
650 ("LoggingOn", 2),
651 ("LoggedIn", 2),
652 ("person", 3),
653 ("time", 3),
654 ("impl Eq for Person", 0),
655 ("impl Drop for Person", 0),
656 ("fn drop", 1),
657 ]
658 );
659
660 // Without space, we only match on names
661 assert_eq!(
662 search(&outline, "oon", cx).await,
663 &[
664 ("mod module", vec![]), // included as the parent of a match
665 ("enum LoginState", vec![]), // included as the parent of a match
666 ("LoggingOn", vec![1, 7, 8]), // matches
667 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
668 ]
669 );
670
671 assert_eq!(
672 search(&outline, "dp p", cx).await,
673 &[
674 ("impl Drop for Person", vec![5, 8, 9, 14]),
675 ("fn drop", vec![]),
676 ]
677 );
678 assert_eq!(
679 search(&outline, "dpn", cx).await,
680 &[("impl Drop for Person", vec![5, 14, 19])]
681 );
682 assert_eq!(
683 search(&outline, "impl ", cx).await,
684 &[
685 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
686 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
687 ("fn drop", vec![]),
688 ]
689 );
690
691 async fn search<'a>(
692 outline: &'a Outline<Anchor>,
693 query: &'a str,
694 cx: &'a gpui::TestAppContext,
695 ) -> Vec<(&'a str, Vec<usize>)> {
696 let matches = cx
697 .update(|cx| outline.search(query, cx.background_executor().clone()))
698 .await;
699 matches
700 .into_iter()
701 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
702 .collect::<Vec<_>>()
703 }
704}
705
706#[gpui::test]
707async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
708 let text = r#"
709 impl A for B<
710 C
711 > {
712 };
713 "#
714 .unindent();
715
716 let buffer =
717 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
718 let outline = buffer
719 .update(cx, |buffer, _| buffer.snapshot().outline(None))
720 .unwrap();
721
722 assert_eq!(
723 outline
724 .items
725 .iter()
726 .map(|item| (item.text.as_str(), item.depth))
727 .collect::<Vec<_>>(),
728 &[("impl A for B<", 0)]
729 );
730}
731
732#[gpui::test]
733async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
734 let language = javascript_lang()
735 .with_outline_query(
736 r#"
737 (function_declaration
738 "function" @context
739 name: (_) @name
740 parameters: (formal_parameters
741 "(" @context.extra
742 ")" @context.extra)) @item
743 "#,
744 )
745 .unwrap();
746
747 let text = r#"
748 function a() {}
749 function b(c) {}
750 "#
751 .unindent();
752
753 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
754 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
755
756 // extra context nodes are included in the outline.
757 let outline = snapshot.outline(None).unwrap();
758 assert_eq!(
759 outline
760 .items
761 .iter()
762 .map(|item| (item.text.as_str(), item.depth))
763 .collect::<Vec<_>>(),
764 &[("function a()", 0), ("function b( )", 0),]
765 );
766
767 // extra context nodes do not appear in breadcrumbs.
768 let symbols = snapshot.symbols_containing(3, None).unwrap();
769 assert_eq!(
770 symbols
771 .iter()
772 .map(|item| (item.text.as_str(), item.depth))
773 .collect::<Vec<_>>(),
774 &[("function a", 0)]
775 );
776}
777
778#[gpui::test]
779async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
780 let text = r#"
781 impl Person {
782 fn one() {
783 1
784 }
785
786 fn two() {
787 2
788 }fn three() {
789 3
790 }
791 }
792 "#
793 .unindent();
794
795 let buffer =
796 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
797 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
798
799 // point is at the start of an item
800 assert_eq!(
801 symbols_containing(Point::new(1, 4), &snapshot),
802 vec![
803 (
804 "impl Person".to_string(),
805 Point::new(0, 0)..Point::new(10, 1)
806 ),
807 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
808 ]
809 );
810
811 // point is in the middle of an item
812 assert_eq!(
813 symbols_containing(Point::new(2, 8), &snapshot),
814 vec![
815 (
816 "impl Person".to_string(),
817 Point::new(0, 0)..Point::new(10, 1)
818 ),
819 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
820 ]
821 );
822
823 // point is at the end of an item
824 assert_eq!(
825 symbols_containing(Point::new(3, 5), &snapshot),
826 vec![
827 (
828 "impl Person".to_string(),
829 Point::new(0, 0)..Point::new(10, 1)
830 ),
831 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
832 ]
833 );
834
835 // point is in between two adjacent items
836 assert_eq!(
837 symbols_containing(Point::new(7, 5), &snapshot),
838 vec![
839 (
840 "impl Person".to_string(),
841 Point::new(0, 0)..Point::new(10, 1)
842 ),
843 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
844 ]
845 );
846
847 fn symbols_containing(
848 position: Point,
849 snapshot: &BufferSnapshot,
850 ) -> Vec<(String, Range<Point>)> {
851 snapshot
852 .symbols_containing(position, None)
853 .unwrap()
854 .into_iter()
855 .map(|item| {
856 (
857 item.text,
858 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
859 )
860 })
861 .collect()
862 }
863}
864
865#[gpui::test]
866fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
867 let mut assert = |selection_text, range_markers| {
868 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
869 };
870
871 assert(
872 indoc! {"
873 mod x {
874 moˇd y {
875
876 }
877 }
878 let foo = 1;"},
879 vec![indoc! {"
880 mod x «{»
881 mod y {
882
883 }
884 «}»
885 let foo = 1;"}],
886 );
887
888 assert(
889 indoc! {"
890 mod x {
891 mod y ˇ{
892
893 }
894 }
895 let foo = 1;"},
896 vec![
897 indoc! {"
898 mod x «{»
899 mod y {
900
901 }
902 «}»
903 let foo = 1;"},
904 indoc! {"
905 mod x {
906 mod y «{»
907
908 «}»
909 }
910 let foo = 1;"},
911 ],
912 );
913
914 assert(
915 indoc! {"
916 mod x {
917 mod y {
918
919 }ˇ
920 }
921 let foo = 1;"},
922 vec![
923 indoc! {"
924 mod x «{»
925 mod y {
926
927 }
928 «}»
929 let foo = 1;"},
930 indoc! {"
931 mod x {
932 mod y «{»
933
934 «}»
935 }
936 let foo = 1;"},
937 ],
938 );
939
940 assert(
941 indoc! {"
942 mod x {
943 mod y {
944
945 }
946 ˇ}
947 let foo = 1;"},
948 vec![indoc! {"
949 mod x «{»
950 mod y {
951
952 }
953 «}»
954 let foo = 1;"}],
955 );
956
957 assert(
958 indoc! {"
959 mod x {
960 mod y {
961
962 }
963 }
964 let fˇoo = 1;"},
965 vec![],
966 );
967
968 // Regression test: avoid crash when querying at the end of the buffer.
969 assert(
970 indoc! {"
971 mod x {
972 mod y {
973
974 }
975 }
976 let foo = 1;ˇ"},
977 vec![],
978 );
979}
980
981#[gpui::test]
982fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
983 let mut assert = |selection_text, bracket_pair_texts| {
984 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
985 };
986
987 assert(
988 indoc! {"
989 for (const a in b)ˇ {
990 // a comment that's longer than the for-loop header
991 }"},
992 vec![indoc! {"
993 for «(»const a in b«)» {
994 // a comment that's longer than the for-loop header
995 }"}],
996 );
997
998 // Regression test: even though the parent node of the parentheses (the for loop) does
999 // intersect the given range, the parentheses themselves do not contain the range, so
1000 // they should not be returned. Only the curly braces contain the range.
1001 assert(
1002 indoc! {"
1003 for (const a in b) {ˇ
1004 // a comment that's longer than the for-loop header
1005 }"},
1006 vec![indoc! {"
1007 for (const a in b) «{»
1008 // a comment that's longer than the for-loop header
1009 «}»"}],
1010 );
1011}
1012
1013#[gpui::test]
1014fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
1015 cx.new_model(|cx| {
1016 let text = "fn a() { b(|c| {}) }";
1017 let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1018 let snapshot = buffer.snapshot();
1019
1020 assert_eq!(
1021 snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
1022 Some(range_of(text, "|"))
1023 );
1024 assert_eq!(
1025 snapshot.range_for_syntax_ancestor(range_of(text, "|")),
1026 Some(range_of(text, "|c|"))
1027 );
1028 assert_eq!(
1029 snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
1030 Some(range_of(text, "|c| {}"))
1031 );
1032 assert_eq!(
1033 snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
1034 Some(range_of(text, "(|c| {})"))
1035 );
1036
1037 buffer
1038 });
1039
1040 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1041 let start = text.find(part).unwrap();
1042 start..start
1043 }
1044
1045 fn range_of(text: &str, part: &str) -> Range<usize> {
1046 let start = text.find(part).unwrap();
1047 start..start + part.len()
1048 }
1049}
1050
1051#[gpui::test]
1052fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
1053 init_settings(cx, |_| {});
1054
1055 cx.new_model(|cx| {
1056 let text = "fn a() {}";
1057 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1058
1059 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1060 assert_eq!(buffer.text(), "fn a() {\n \n}");
1061
1062 buffer.edit(
1063 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1064 Some(AutoindentMode::EachLine),
1065 cx,
1066 );
1067 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1068
1069 // Create a field expression on a new line, causing that line
1070 // to be indented.
1071 buffer.edit(
1072 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1073 Some(AutoindentMode::EachLine),
1074 cx,
1075 );
1076 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1077
1078 // Remove the dot so that the line is no longer a field expression,
1079 // causing the line to be outdented.
1080 buffer.edit(
1081 [(Point::new(2, 8)..Point::new(2, 9), "")],
1082 Some(AutoindentMode::EachLine),
1083 cx,
1084 );
1085 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1086
1087 buffer
1088 });
1089}
1090
1091#[gpui::test]
1092fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
1093 init_settings(cx, |settings| {
1094 settings.defaults.hard_tabs = Some(true);
1095 });
1096
1097 cx.new_model(|cx| {
1098 let text = "fn a() {}";
1099 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1100
1101 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1102 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1103
1104 buffer.edit(
1105 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1106 Some(AutoindentMode::EachLine),
1107 cx,
1108 );
1109 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1110
1111 // Create a field expression on a new line, causing that line
1112 // to be indented.
1113 buffer.edit(
1114 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1115 Some(AutoindentMode::EachLine),
1116 cx,
1117 );
1118 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1119
1120 // Remove the dot so that the line is no longer a field expression,
1121 // causing the line to be outdented.
1122 buffer.edit(
1123 [(Point::new(2, 2)..Point::new(2, 3), "")],
1124 Some(AutoindentMode::EachLine),
1125 cx,
1126 );
1127 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1128
1129 buffer
1130 });
1131}
1132
1133#[gpui::test]
1134fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
1135 init_settings(cx, |_| {});
1136
1137 cx.new_model(|cx| {
1138 let mut buffer = Buffer::local(
1139 "
1140 fn a() {
1141 c;
1142 d;
1143 }
1144 "
1145 .unindent(),
1146 cx,
1147 )
1148 .with_language(Arc::new(rust_lang()), cx);
1149
1150 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1151 // their indentation is not adjusted.
1152 buffer.edit_via_marked_text(
1153 &"
1154 fn a() {
1155 c«()»;
1156 d«()»;
1157 }
1158 "
1159 .unindent(),
1160 Some(AutoindentMode::EachLine),
1161 cx,
1162 );
1163 assert_eq!(
1164 buffer.text(),
1165 "
1166 fn a() {
1167 c();
1168 d();
1169 }
1170 "
1171 .unindent()
1172 );
1173
1174 // When appending new content after these lines, the indentation is based on the
1175 // preceding lines' actual indentation.
1176 buffer.edit_via_marked_text(
1177 &"
1178 fn a() {
1179 c«
1180 .f
1181 .g()»;
1182 d«
1183 .f
1184 .g()»;
1185 }
1186 "
1187 .unindent(),
1188 Some(AutoindentMode::EachLine),
1189 cx,
1190 );
1191
1192 assert_eq!(
1193 buffer.text(),
1194 "
1195 fn a() {
1196 c
1197 .f
1198 .g();
1199 d
1200 .f
1201 .g();
1202 }
1203 "
1204 .unindent()
1205 );
1206 buffer
1207 });
1208
1209 cx.new_model(|cx| {
1210 eprintln!("second buffer: {:?}", cx.entity_id());
1211
1212 let mut buffer = Buffer::local(
1213 "
1214 fn a() {
1215 b();
1216 |
1217 "
1218 .replace('|', "") // marker to preserve trailing whitespace
1219 .unindent(),
1220 cx,
1221 )
1222 .with_language(Arc::new(rust_lang()), cx);
1223
1224 // Insert a closing brace. It is outdented.
1225 buffer.edit_via_marked_text(
1226 &"
1227 fn a() {
1228 b();
1229 «}»
1230 "
1231 .unindent(),
1232 Some(AutoindentMode::EachLine),
1233 cx,
1234 );
1235 assert_eq!(
1236 buffer.text(),
1237 "
1238 fn a() {
1239 b();
1240 }
1241 "
1242 .unindent()
1243 );
1244
1245 // Manually edit the leading whitespace. The edit is preserved.
1246 buffer.edit_via_marked_text(
1247 &"
1248 fn a() {
1249 b();
1250 « »}
1251 "
1252 .unindent(),
1253 Some(AutoindentMode::EachLine),
1254 cx,
1255 );
1256 assert_eq!(
1257 buffer.text(),
1258 "
1259 fn a() {
1260 b();
1261 }
1262 "
1263 .unindent()
1264 );
1265 buffer
1266 });
1267
1268 eprintln!("DONE");
1269}
1270
1271#[gpui::test]
1272fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
1273 init_settings(cx, |_| {});
1274
1275 cx.new_model(|cx| {
1276 let mut buffer = Buffer::local(
1277 "
1278 fn a() {
1279 i
1280 }
1281 "
1282 .unindent(),
1283 cx,
1284 )
1285 .with_language(Arc::new(rust_lang()), cx);
1286
1287 // Regression test: line does not get outdented due to syntax error
1288 buffer.edit_via_marked_text(
1289 &"
1290 fn a() {
1291 i«f let Some(x) = y»
1292 }
1293 "
1294 .unindent(),
1295 Some(AutoindentMode::EachLine),
1296 cx,
1297 );
1298 assert_eq!(
1299 buffer.text(),
1300 "
1301 fn a() {
1302 if let Some(x) = y
1303 }
1304 "
1305 .unindent()
1306 );
1307
1308 buffer.edit_via_marked_text(
1309 &"
1310 fn a() {
1311 if let Some(x) = y« {»
1312 }
1313 "
1314 .unindent(),
1315 Some(AutoindentMode::EachLine),
1316 cx,
1317 );
1318 assert_eq!(
1319 buffer.text(),
1320 "
1321 fn a() {
1322 if let Some(x) = y {
1323 }
1324 "
1325 .unindent()
1326 );
1327
1328 buffer
1329 });
1330}
1331
1332#[gpui::test]
1333fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
1334 init_settings(cx, |_| {});
1335
1336 cx.new_model(|cx| {
1337 let mut buffer = Buffer::local(
1338 "
1339 fn a() {}
1340 "
1341 .unindent(),
1342 cx,
1343 )
1344 .with_language(Arc::new(rust_lang()), cx);
1345
1346 buffer.edit_via_marked_text(
1347 &"
1348 fn a(«
1349 b») {}
1350 "
1351 .unindent(),
1352 Some(AutoindentMode::EachLine),
1353 cx,
1354 );
1355 assert_eq!(
1356 buffer.text(),
1357 "
1358 fn a(
1359 b) {}
1360 "
1361 .unindent()
1362 );
1363
1364 // The indentation suggestion changed because `@end` node (a close paren)
1365 // is now at the beginning of the line.
1366 buffer.edit_via_marked_text(
1367 &"
1368 fn a(
1369 ˇ) {}
1370 "
1371 .unindent(),
1372 Some(AutoindentMode::EachLine),
1373 cx,
1374 );
1375 assert_eq!(
1376 buffer.text(),
1377 "
1378 fn a(
1379 ) {}
1380 "
1381 .unindent()
1382 );
1383
1384 buffer
1385 });
1386}
1387
1388#[gpui::test]
1389fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
1390 init_settings(cx, |_| {});
1391
1392 cx.new_model(|cx| {
1393 let text = "a\nb";
1394 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1395 buffer.edit(
1396 [(0..1, "\n"), (2..3, "\n")],
1397 Some(AutoindentMode::EachLine),
1398 cx,
1399 );
1400 assert_eq!(buffer.text(), "\n\n\n");
1401 buffer
1402 });
1403}
1404
1405#[gpui::test]
1406fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
1407 init_settings(cx, |_| {});
1408
1409 cx.new_model(|cx| {
1410 let text = "
1411 const a: usize = 1;
1412 fn b() {
1413 if c {
1414 let d = 2;
1415 }
1416 }
1417 "
1418 .unindent();
1419
1420 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1421 buffer.edit(
1422 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1423 Some(AutoindentMode::EachLine),
1424 cx,
1425 );
1426 assert_eq!(
1427 buffer.text(),
1428 "
1429 const a: usize = 1;
1430 fn b() {
1431 if c {
1432 e(
1433 f()
1434 );
1435 let d = 2;
1436 }
1437 }
1438 "
1439 .unindent()
1440 );
1441
1442 buffer
1443 });
1444}
1445
1446#[gpui::test]
1447fn test_autoindent_block_mode(cx: &mut AppContext) {
1448 init_settings(cx, |_| {});
1449
1450 cx.new_model(|cx| {
1451 let text = r#"
1452 fn a() {
1453 b();
1454 }
1455 "#
1456 .unindent();
1457 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1458
1459 // When this text was copied, both of the quotation marks were at the same
1460 // indent level, but the indentation of the first line was not included in
1461 // the copied text. This information is retained in the
1462 // 'original_indent_columns' vector.
1463 let original_indent_columns = vec![4];
1464 let inserted_text = r#"
1465 "
1466 c
1467 d
1468 e
1469 "
1470 "#
1471 .unindent();
1472
1473 // Insert the block at column zero. The entire block is indented
1474 // so that the first line matches the previous line's indentation.
1475 buffer.edit(
1476 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1477 Some(AutoindentMode::Block {
1478 original_indent_columns: original_indent_columns.clone(),
1479 }),
1480 cx,
1481 );
1482 assert_eq!(
1483 buffer.text(),
1484 r#"
1485 fn a() {
1486 b();
1487 "
1488 c
1489 d
1490 e
1491 "
1492 }
1493 "#
1494 .unindent()
1495 );
1496
1497 // Grouping is disabled in tests, so we need 2 undos
1498 buffer.undo(cx); // Undo the auto-indent
1499 buffer.undo(cx); // Undo the original edit
1500
1501 // Insert the block at a deeper indent level. The entire block is outdented.
1502 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1503 buffer.edit(
1504 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1505 Some(AutoindentMode::Block {
1506 original_indent_columns: original_indent_columns.clone(),
1507 }),
1508 cx,
1509 );
1510 assert_eq!(
1511 buffer.text(),
1512 r#"
1513 fn a() {
1514 b();
1515 "
1516 c
1517 d
1518 e
1519 "
1520 }
1521 "#
1522 .unindent()
1523 );
1524
1525 buffer
1526 });
1527}
1528
1529#[gpui::test]
1530fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
1531 init_settings(cx, |_| {});
1532
1533 cx.new_model(|cx| {
1534 let text = r#"
1535 fn a() {
1536 if b() {
1537
1538 }
1539 }
1540 "#
1541 .unindent();
1542 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1543
1544 // The original indent columns are not known, so this text is
1545 // auto-indented in a block as if the first line was copied in
1546 // its entirety.
1547 let original_indent_columns = Vec::new();
1548 let inserted_text = " c\n .d()\n .e();";
1549
1550 // Insert the block at column zero. The entire block is indented
1551 // so that the first line matches the previous line's indentation.
1552 buffer.edit(
1553 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1554 Some(AutoindentMode::Block {
1555 original_indent_columns: original_indent_columns.clone(),
1556 }),
1557 cx,
1558 );
1559 assert_eq!(
1560 buffer.text(),
1561 r#"
1562 fn a() {
1563 if b() {
1564 c
1565 .d()
1566 .e();
1567 }
1568 }
1569 "#
1570 .unindent()
1571 );
1572
1573 // Grouping is disabled in tests, so we need 2 undos
1574 buffer.undo(cx); // Undo the auto-indent
1575 buffer.undo(cx); // Undo the original edit
1576
1577 // Insert the block at a deeper indent level. The entire block is outdented.
1578 buffer.edit(
1579 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1580 None,
1581 cx,
1582 );
1583 buffer.edit(
1584 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1585 Some(AutoindentMode::Block {
1586 original_indent_columns: Vec::new(),
1587 }),
1588 cx,
1589 );
1590 assert_eq!(
1591 buffer.text(),
1592 r#"
1593 fn a() {
1594 if b() {
1595 c
1596 .d()
1597 .e();
1598 }
1599 }
1600 "#
1601 .unindent()
1602 );
1603
1604 buffer
1605 });
1606}
1607
1608#[gpui::test]
1609fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
1610 init_settings(cx, |_| {});
1611
1612 cx.new_model(|cx| {
1613 let text = "
1614 * one
1615 - a
1616 - b
1617 * two
1618 "
1619 .unindent();
1620
1621 let mut buffer = Buffer::local(text, cx).with_language(
1622 Arc::new(Language::new(
1623 LanguageConfig {
1624 name: "Markdown".into(),
1625 auto_indent_using_last_non_empty_line: false,
1626 ..Default::default()
1627 },
1628 Some(tree_sitter_json::language()),
1629 )),
1630 cx,
1631 );
1632 buffer.edit(
1633 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1634 Some(AutoindentMode::EachLine),
1635 cx,
1636 );
1637 assert_eq!(
1638 buffer.text(),
1639 "
1640 * one
1641 - a
1642 - b
1643
1644 * two
1645 "
1646 .unindent()
1647 );
1648 buffer
1649 });
1650}
1651
1652#[gpui::test]
1653fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
1654 init_settings(cx, |settings| {
1655 settings.languages.extend([
1656 (
1657 "HTML".into(),
1658 LanguageSettingsContent {
1659 tab_size: Some(2.try_into().unwrap()),
1660 ..Default::default()
1661 },
1662 ),
1663 (
1664 "JavaScript".into(),
1665 LanguageSettingsContent {
1666 tab_size: Some(8.try_into().unwrap()),
1667 ..Default::default()
1668 },
1669 ),
1670 ])
1671 });
1672
1673 let html_language = Arc::new(html_lang());
1674
1675 let javascript_language = Arc::new(javascript_lang());
1676
1677 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1678 language_registry.add(html_language.clone());
1679 language_registry.add(javascript_language.clone());
1680
1681 cx.new_model(|cx| {
1682 let (text, ranges) = marked_text_ranges(
1683 &"
1684 <div>ˇ
1685 </div>
1686 <script>
1687 init({ˇ
1688 })
1689 </script>
1690 <span>ˇ
1691 </span>
1692 "
1693 .unindent(),
1694 false,
1695 );
1696
1697 let mut buffer = Buffer::local(text, cx);
1698 buffer.set_language_registry(language_registry);
1699 buffer.set_language(Some(html_language), cx);
1700 buffer.edit(
1701 ranges.into_iter().map(|range| (range, "\na")),
1702 Some(AutoindentMode::EachLine),
1703 cx,
1704 );
1705 assert_eq!(
1706 buffer.text(),
1707 "
1708 <div>
1709 a
1710 </div>
1711 <script>
1712 init({
1713 a
1714 })
1715 </script>
1716 <span>
1717 a
1718 </span>
1719 "
1720 .unindent()
1721 );
1722 buffer
1723 });
1724}
1725
1726#[gpui::test]
1727fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
1728 init_settings(cx, |settings| {
1729 settings.defaults.tab_size = Some(2.try_into().unwrap());
1730 });
1731
1732 cx.new_model(|cx| {
1733 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
1734
1735 let text = r#"
1736 class C
1737 def a(b, c)
1738 puts b
1739 puts c
1740 rescue
1741 puts "errored"
1742 exit 1
1743 end
1744 end
1745 "#
1746 .unindent();
1747
1748 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1749
1750 assert_eq!(
1751 buffer.text(),
1752 r#"
1753 class C
1754 def a(b, c)
1755 puts b
1756 puts c
1757 rescue
1758 puts "errored"
1759 exit 1
1760 end
1761 end
1762 "#
1763 .unindent()
1764 );
1765
1766 buffer
1767 });
1768}
1769
1770#[gpui::test]
1771fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
1772 init_settings(cx, |_| {});
1773
1774 cx.new_model(|cx| {
1775 let language = Language::new(
1776 LanguageConfig {
1777 name: "JavaScript".into(),
1778 line_comments: vec!["// ".into()],
1779 brackets: BracketPairConfig {
1780 pairs: vec![
1781 BracketPair {
1782 start: "{".into(),
1783 end: "}".into(),
1784 close: true,
1785 newline: false,
1786 },
1787 BracketPair {
1788 start: "'".into(),
1789 end: "'".into(),
1790 close: true,
1791 newline: false,
1792 },
1793 ],
1794 disabled_scopes_by_bracket_ix: vec![
1795 Vec::new(), //
1796 vec!["string".into()],
1797 ],
1798 },
1799 overrides: [(
1800 "element".into(),
1801 LanguageConfigOverride {
1802 line_comments: Override::Remove { remove: true },
1803 block_comment: Override::Set(("{/*".into(), "*/}".into())),
1804 ..Default::default()
1805 },
1806 )]
1807 .into_iter()
1808 .collect(),
1809 ..Default::default()
1810 },
1811 Some(tree_sitter_typescript::language_tsx()),
1812 )
1813 .with_override_query(
1814 r#"
1815 (jsx_element) @element
1816 (string) @string
1817 [
1818 (jsx_opening_element)
1819 (jsx_closing_element)
1820 (jsx_expression)
1821 ] @default
1822 "#,
1823 )
1824 .unwrap();
1825
1826 let text = r#"
1827 a["b"] = <C d="e">
1828 <F></F>
1829 { g() }
1830 </C>;
1831 "#
1832 .unindent();
1833
1834 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
1835 let snapshot = buffer.snapshot();
1836
1837 let config = snapshot.language_scope_at(0).unwrap();
1838 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
1839 // Both bracket pairs are enabled
1840 assert_eq!(
1841 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1842 &[true, true]
1843 );
1844
1845 let string_config = snapshot
1846 .language_scope_at(text.find("b\"").unwrap())
1847 .unwrap();
1848 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
1849 // Second bracket pair is disabled
1850 assert_eq!(
1851 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1852 &[true, false]
1853 );
1854
1855 // In between JSX tags: use the `element` override.
1856 let element_config = snapshot
1857 .language_scope_at(text.find("<F>").unwrap())
1858 .unwrap();
1859 assert_eq!(element_config.line_comment_prefixes(), &[]);
1860 assert_eq!(
1861 element_config.block_comment_delimiters(),
1862 Some((&"{/*".into(), &"*/}".into()))
1863 );
1864 assert_eq!(
1865 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1866 &[true, true]
1867 );
1868
1869 // Within a JSX tag: use the default config.
1870 let tag_config = snapshot
1871 .language_scope_at(text.find(" d=").unwrap() + 1)
1872 .unwrap();
1873 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
1874 assert_eq!(
1875 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1876 &[true, true]
1877 );
1878
1879 // In a JSX expression: use the default config.
1880 let expression_in_element_config = snapshot
1881 .language_scope_at(text.find('{').unwrap() + 1)
1882 .unwrap();
1883 assert_eq!(
1884 expression_in_element_config.line_comment_prefixes(),
1885 &[Arc::from("// ")]
1886 );
1887 assert_eq!(
1888 expression_in_element_config
1889 .brackets()
1890 .map(|e| e.1)
1891 .collect::<Vec<_>>(),
1892 &[true, true]
1893 );
1894
1895 buffer
1896 });
1897}
1898
1899#[gpui::test]
1900fn test_language_scope_at_with_rust(cx: &mut AppContext) {
1901 init_settings(cx, |_| {});
1902
1903 cx.new_model(|cx| {
1904 let language = Language::new(
1905 LanguageConfig {
1906 name: "Rust".into(),
1907 brackets: BracketPairConfig {
1908 pairs: vec![
1909 BracketPair {
1910 start: "{".into(),
1911 end: "}".into(),
1912 close: true,
1913 newline: false,
1914 },
1915 BracketPair {
1916 start: "'".into(),
1917 end: "'".into(),
1918 close: true,
1919 newline: false,
1920 },
1921 ],
1922 disabled_scopes_by_bracket_ix: vec![
1923 Vec::new(), //
1924 vec!["string".into()],
1925 ],
1926 },
1927 ..Default::default()
1928 },
1929 Some(tree_sitter_rust::language()),
1930 )
1931 .with_override_query(
1932 r#"
1933 (string_literal) @string
1934 "#,
1935 )
1936 .unwrap();
1937
1938 let text = r#"
1939 const S: &'static str = "hello";
1940 "#
1941 .unindent();
1942
1943 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
1944 let snapshot = buffer.snapshot();
1945
1946 // By default, all brackets are enabled
1947 let config = snapshot.language_scope_at(0).unwrap();
1948 assert_eq!(
1949 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1950 &[true, true]
1951 );
1952
1953 // Within a string, the quotation brackets are disabled.
1954 let string_config = snapshot
1955 .language_scope_at(text.find("ello").unwrap())
1956 .unwrap();
1957 assert_eq!(
1958 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1959 &[true, false]
1960 );
1961
1962 buffer
1963 });
1964}
1965
1966#[gpui::test]
1967fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
1968 init_settings(cx, |_| {});
1969
1970 cx.new_model(|cx| {
1971 let text = r#"
1972 <ol>
1973 <% people.each do |person| %>
1974 <li>
1975 <%= person.name %>
1976 </li>
1977 <% end %>
1978 </ol>
1979 "#
1980 .unindent();
1981
1982 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1983 language_registry.add(Arc::new(ruby_lang()));
1984 language_registry.add(Arc::new(html_lang()));
1985 language_registry.add(Arc::new(erb_lang()));
1986
1987 let mut buffer = Buffer::local(text, cx);
1988 buffer.set_language_registry(language_registry.clone());
1989 buffer.set_language(
1990 language_registry
1991 .language_for_name("ERB")
1992 .now_or_never()
1993 .unwrap()
1994 .ok(),
1995 cx,
1996 );
1997
1998 let snapshot = buffer.snapshot();
1999 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2000 assert_eq!(html_config.line_comment_prefixes(), &[]);
2001 assert_eq!(
2002 html_config.block_comment_delimiters(),
2003 Some((&"<!--".into(), &"-->".into()))
2004 );
2005
2006 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2007 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2008 assert_eq!(ruby_config.block_comment_delimiters(), None);
2009
2010 buffer
2011 });
2012}
2013
2014#[gpui::test]
2015fn test_serialization(cx: &mut gpui::AppContext) {
2016 let mut now = Instant::now();
2017
2018 let buffer1 = cx.new_model(|cx| {
2019 let mut buffer = Buffer::local("abc", cx);
2020 buffer.edit([(3..3, "D")], None, cx);
2021
2022 now += Duration::from_secs(1);
2023 buffer.start_transaction_at(now);
2024 buffer.edit([(4..4, "E")], None, cx);
2025 buffer.end_transaction_at(now, cx);
2026 assert_eq!(buffer.text(), "abcDE");
2027
2028 buffer.undo(cx);
2029 assert_eq!(buffer.text(), "abcD");
2030
2031 buffer.edit([(4..4, "F")], None, cx);
2032 assert_eq!(buffer.text(), "abcDF");
2033 buffer
2034 });
2035 assert_eq!(buffer1.read(cx).text(), "abcDF");
2036
2037 let state = buffer1.read(cx).to_proto();
2038 let ops = cx
2039 .background_executor()
2040 .block(buffer1.read(cx).serialize_ops(None, cx));
2041 let buffer2 = cx.new_model(|cx| {
2042 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2043 buffer
2044 .apply_ops(
2045 ops.into_iter()
2046 .map(|op| proto::deserialize_operation(op).unwrap()),
2047 cx,
2048 )
2049 .unwrap();
2050 buffer
2051 });
2052 assert_eq!(buffer2.read(cx).text(), "abcDF");
2053}
2054
2055#[gpui::test(iterations = 100)]
2056fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2057 let min_peers = env::var("MIN_PEERS")
2058 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2059 .unwrap_or(1);
2060 let max_peers = env::var("MAX_PEERS")
2061 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2062 .unwrap_or(5);
2063 let operations = env::var("OPERATIONS")
2064 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2065 .unwrap_or(10);
2066
2067 let base_text_len = rng.gen_range(0..10);
2068 let base_text = RandomCharIter::new(&mut rng)
2069 .take(base_text_len)
2070 .collect::<String>();
2071 let mut replica_ids = Vec::new();
2072 let mut buffers = Vec::new();
2073 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2074 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2075
2076 for i in 0..rng.gen_range(min_peers..=max_peers) {
2077 let buffer = cx.new_model(|cx| {
2078 let state = base_buffer.read(cx).to_proto();
2079 let ops = cx
2080 .background_executor()
2081 .block(base_buffer.read(cx).serialize_ops(None, cx));
2082 let mut buffer =
2083 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2084 buffer
2085 .apply_ops(
2086 ops.into_iter()
2087 .map(|op| proto::deserialize_operation(op).unwrap()),
2088 cx,
2089 )
2090 .unwrap();
2091 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2092 let network = network.clone();
2093 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2094 if let Event::Operation(op) = event {
2095 network
2096 .lock()
2097 .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
2098 }
2099 })
2100 .detach();
2101 buffer
2102 });
2103
2104 buffers.push(buffer);
2105 replica_ids.push(i as ReplicaId);
2106 network.lock().add_peer(i as ReplicaId);
2107 log::info!("Adding initial peer with replica id {}", i);
2108 }
2109
2110 log::info!("initial text: {:?}", base_text);
2111
2112 let mut now = Instant::now();
2113 let mut mutation_count = operations;
2114 let mut next_diagnostic_id = 0;
2115 let mut active_selections = BTreeMap::default();
2116 loop {
2117 let replica_index = rng.gen_range(0..replica_ids.len());
2118 let replica_id = replica_ids[replica_index];
2119 let buffer = &mut buffers[replica_index];
2120 let mut new_buffer = None;
2121 match rng.gen_range(0..100) {
2122 0..=29 if mutation_count != 0 => {
2123 buffer.update(cx, |buffer, cx| {
2124 buffer.start_transaction_at(now);
2125 buffer.randomly_edit(&mut rng, 5, cx);
2126 buffer.end_transaction_at(now, cx);
2127 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2128 });
2129 mutation_count -= 1;
2130 }
2131 30..=39 if mutation_count != 0 => {
2132 buffer.update(cx, |buffer, cx| {
2133 if rng.gen_bool(0.2) {
2134 log::info!("peer {} clearing active selections", replica_id);
2135 active_selections.remove(&replica_id);
2136 buffer.remove_active_selections(cx);
2137 } else {
2138 let mut selections = Vec::new();
2139 for id in 0..rng.gen_range(1..=5) {
2140 let range = buffer.random_byte_range(0, &mut rng);
2141 selections.push(Selection {
2142 id,
2143 start: buffer.anchor_before(range.start),
2144 end: buffer.anchor_before(range.end),
2145 reversed: false,
2146 goal: SelectionGoal::None,
2147 });
2148 }
2149 let selections: Arc<[Selection<Anchor>]> = selections.into();
2150 log::info!(
2151 "peer {} setting active selections: {:?}",
2152 replica_id,
2153 selections
2154 );
2155 active_selections.insert(replica_id, selections.clone());
2156 buffer.set_active_selections(selections, false, Default::default(), cx);
2157 }
2158 });
2159 mutation_count -= 1;
2160 }
2161 40..=49 if mutation_count != 0 && replica_id == 0 => {
2162 let entry_count = rng.gen_range(1..=5);
2163 buffer.update(cx, |buffer, cx| {
2164 let diagnostics = DiagnosticSet::new(
2165 (0..entry_count).map(|_| {
2166 let range = buffer.random_byte_range(0, &mut rng);
2167 let range = range.to_point_utf16(buffer);
2168 let range = range.start..range.end;
2169 DiagnosticEntry {
2170 range,
2171 diagnostic: Diagnostic {
2172 message: post_inc(&mut next_diagnostic_id).to_string(),
2173 ..Default::default()
2174 },
2175 }
2176 }),
2177 buffer,
2178 );
2179 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2180 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2181 });
2182 mutation_count -= 1;
2183 }
2184 50..=59 if replica_ids.len() < max_peers => {
2185 let old_buffer_state = buffer.read(cx).to_proto();
2186 let old_buffer_ops = cx
2187 .background_executor()
2188 .block(buffer.read(cx).serialize_ops(None, cx));
2189 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2190 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2191 .choose(&mut rng)
2192 .unwrap();
2193 log::info!(
2194 "Adding new replica {} (replicating from {})",
2195 new_replica_id,
2196 replica_id
2197 );
2198 new_buffer = Some(cx.new_model(|cx| {
2199 let mut new_buffer = Buffer::from_proto(
2200 new_replica_id,
2201 Capability::ReadWrite,
2202 old_buffer_state,
2203 None,
2204 )
2205 .unwrap();
2206 new_buffer
2207 .apply_ops(
2208 old_buffer_ops
2209 .into_iter()
2210 .map(|op| deserialize_operation(op).unwrap()),
2211 cx,
2212 )
2213 .unwrap();
2214 log::info!(
2215 "New replica {} text: {:?}",
2216 new_buffer.replica_id(),
2217 new_buffer.text()
2218 );
2219 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2220 let network = network.clone();
2221 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2222 if let Event::Operation(op) = event {
2223 network.lock().broadcast(
2224 buffer.replica_id(),
2225 vec![proto::serialize_operation(op)],
2226 );
2227 }
2228 })
2229 .detach();
2230 new_buffer
2231 }));
2232 network.lock().replicate(replica_id, new_replica_id);
2233
2234 if new_replica_id as usize == replica_ids.len() {
2235 replica_ids.push(new_replica_id);
2236 } else {
2237 let new_buffer = new_buffer.take().unwrap();
2238 while network.lock().has_unreceived(new_replica_id) {
2239 let ops = network
2240 .lock()
2241 .receive(new_replica_id)
2242 .into_iter()
2243 .map(|op| proto::deserialize_operation(op).unwrap());
2244 if ops.len() > 0 {
2245 log::info!(
2246 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2247 new_replica_id,
2248 buffer.read(cx).version(),
2249 ops.len(),
2250 ops
2251 );
2252 new_buffer.update(cx, |new_buffer, cx| {
2253 new_buffer.apply_ops(ops, cx).unwrap();
2254 });
2255 }
2256 }
2257 buffers[new_replica_id as usize] = new_buffer;
2258 }
2259 }
2260 60..=69 if mutation_count != 0 => {
2261 buffer.update(cx, |buffer, cx| {
2262 buffer.randomly_undo_redo(&mut rng, cx);
2263 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2264 });
2265 mutation_count -= 1;
2266 }
2267 _ if network.lock().has_unreceived(replica_id) => {
2268 let ops = network
2269 .lock()
2270 .receive(replica_id)
2271 .into_iter()
2272 .map(|op| proto::deserialize_operation(op).unwrap());
2273 if ops.len() > 0 {
2274 log::info!(
2275 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2276 replica_id,
2277 buffer.read(cx).version(),
2278 ops.len(),
2279 ops
2280 );
2281 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
2282 }
2283 }
2284 _ => {}
2285 }
2286
2287 now += Duration::from_millis(rng.gen_range(0..=200));
2288 buffers.extend(new_buffer);
2289
2290 for buffer in &buffers {
2291 buffer.read(cx).check_invariants();
2292 }
2293
2294 if mutation_count == 0 && network.lock().is_idle() {
2295 break;
2296 }
2297 }
2298
2299 let first_buffer = buffers[0].read(cx).snapshot();
2300 for buffer in &buffers[1..] {
2301 let buffer = buffer.read(cx).snapshot();
2302 assert_eq!(
2303 buffer.version(),
2304 first_buffer.version(),
2305 "Replica {} version != Replica 0 version",
2306 buffer.replica_id()
2307 );
2308 assert_eq!(
2309 buffer.text(),
2310 first_buffer.text(),
2311 "Replica {} text != Replica 0 text",
2312 buffer.replica_id()
2313 );
2314 assert_eq!(
2315 buffer
2316 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2317 .collect::<Vec<_>>(),
2318 first_buffer
2319 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2320 .collect::<Vec<_>>(),
2321 "Replica {} diagnostics != Replica 0 diagnostics",
2322 buffer.replica_id()
2323 );
2324 }
2325
2326 for buffer in &buffers {
2327 let buffer = buffer.read(cx).snapshot();
2328 let actual_remote_selections = buffer
2329 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
2330 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
2331 .collect::<Vec<_>>();
2332 let expected_remote_selections = active_selections
2333 .iter()
2334 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
2335 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
2336 .collect::<Vec<_>>();
2337 assert_eq!(
2338 actual_remote_selections,
2339 expected_remote_selections,
2340 "Replica {} remote selections != expected selections",
2341 buffer.replica_id()
2342 );
2343 }
2344}
2345
2346#[test]
2347fn test_contiguous_ranges() {
2348 assert_eq!(
2349 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
2350 &[1..4, 5..7, 9..13]
2351 );
2352
2353 // Respects the `max_len` parameter
2354 assert_eq!(
2355 contiguous_ranges(
2356 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
2357 3
2358 )
2359 .collect::<Vec<_>>(),
2360 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
2361 );
2362}
2363
2364#[gpui::test(iterations = 500)]
2365fn test_trailing_whitespace_ranges(mut rng: StdRng) {
2366 // Generate a random multi-line string containing
2367 // some lines with trailing whitespace.
2368 let mut text = String::new();
2369 for _ in 0..rng.gen_range(0..16) {
2370 for _ in 0..rng.gen_range(0..36) {
2371 text.push(match rng.gen_range(0..10) {
2372 0..=1 => ' ',
2373 3 => '\t',
2374 _ => rng.gen_range('a'..'z'),
2375 });
2376 }
2377 text.push('\n');
2378 }
2379
2380 match rng.gen_range(0..10) {
2381 // sometimes remove the last newline
2382 0..=1 => drop(text.pop()), //
2383
2384 // sometimes add extra newlines
2385 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
2386 _ => {}
2387 }
2388
2389 let rope = Rope::from(text.as_str());
2390 let actual_ranges = trailing_whitespace_ranges(&rope);
2391 let expected_ranges = TRAILING_WHITESPACE_REGEX
2392 .find_iter(&text)
2393 .map(|m| m.range())
2394 .collect::<Vec<_>>();
2395 assert_eq!(
2396 actual_ranges,
2397 expected_ranges,
2398 "wrong ranges for text lines:\n{:?}",
2399 text.split('\n').collect::<Vec<_>>()
2400 );
2401}
2402
2403fn ruby_lang() -> Language {
2404 Language::new(
2405 LanguageConfig {
2406 name: "Ruby".into(),
2407 matcher: LanguageMatcher {
2408 path_suffixes: vec!["rb".to_string()],
2409 ..Default::default()
2410 },
2411 line_comments: vec!["# ".into()],
2412 ..Default::default()
2413 },
2414 Some(tree_sitter_ruby::language()),
2415 )
2416 .with_indents_query(
2417 r#"
2418 (class "end" @end) @indent
2419 (method "end" @end) @indent
2420 (rescue) @outdent
2421 (then) @indent
2422 "#,
2423 )
2424 .unwrap()
2425}
2426
2427fn html_lang() -> Language {
2428 Language::new(
2429 LanguageConfig {
2430 name: "HTML".into(),
2431 block_comment: Some(("<!--".into(), "-->".into())),
2432 ..Default::default()
2433 },
2434 Some(tree_sitter_html::language()),
2435 )
2436 .with_indents_query(
2437 "
2438 (element
2439 (start_tag) @start
2440 (end_tag)? @end) @indent
2441 ",
2442 )
2443 .unwrap()
2444 .with_injection_query(
2445 r#"
2446 (script_element
2447 (raw_text) @content
2448 (#set! "language" "javascript"))
2449 "#,
2450 )
2451 .unwrap()
2452}
2453
2454fn erb_lang() -> Language {
2455 Language::new(
2456 LanguageConfig {
2457 name: "ERB".into(),
2458 matcher: LanguageMatcher {
2459 path_suffixes: vec!["erb".to_string()],
2460 ..Default::default()
2461 },
2462 block_comment: Some(("<%#".into(), "%>".into())),
2463 ..Default::default()
2464 },
2465 Some(tree_sitter_embedded_template::language()),
2466 )
2467 .with_injection_query(
2468 r#"
2469 (
2470 (code) @content
2471 (#set! "language" "ruby")
2472 (#set! "combined")
2473 )
2474
2475 (
2476 (content) @content
2477 (#set! "language" "html")
2478 (#set! "combined")
2479 )
2480 "#,
2481 )
2482 .unwrap()
2483}
2484
2485fn rust_lang() -> Language {
2486 Language::new(
2487 LanguageConfig {
2488 name: "Rust".into(),
2489 matcher: LanguageMatcher {
2490 path_suffixes: vec!["rs".to_string()],
2491 ..Default::default()
2492 },
2493 ..Default::default()
2494 },
2495 Some(tree_sitter_rust::language()),
2496 )
2497 .with_indents_query(
2498 r#"
2499 (call_expression) @indent
2500 (field_expression) @indent
2501 (_ "(" ")" @end) @indent
2502 (_ "{" "}" @end) @indent
2503 "#,
2504 )
2505 .unwrap()
2506 .with_brackets_query(
2507 r#"
2508 ("{" @open "}" @close)
2509 "#,
2510 )
2511 .unwrap()
2512 .with_outline_query(
2513 r#"
2514 (struct_item
2515 "struct" @context
2516 name: (_) @name) @item
2517 (enum_item
2518 "enum" @context
2519 name: (_) @name) @item
2520 (enum_variant
2521 name: (_) @name) @item
2522 (field_declaration
2523 name: (_) @name) @item
2524 (impl_item
2525 "impl" @context
2526 trait: (_)? @name
2527 "for"? @context
2528 type: (_) @name) @item
2529 (function_item
2530 "fn" @context
2531 name: (_) @name) @item
2532 (mod_item
2533 "mod" @context
2534 name: (_) @name) @item
2535 "#,
2536 )
2537 .unwrap()
2538}
2539
2540fn json_lang() -> Language {
2541 Language::new(
2542 LanguageConfig {
2543 name: "Json".into(),
2544 matcher: LanguageMatcher {
2545 path_suffixes: vec!["js".to_string()],
2546 ..Default::default()
2547 },
2548 ..Default::default()
2549 },
2550 Some(tree_sitter_json::language()),
2551 )
2552}
2553
2554fn javascript_lang() -> Language {
2555 Language::new(
2556 LanguageConfig {
2557 name: "JavaScript".into(),
2558 ..Default::default()
2559 },
2560 Some(tree_sitter_typescript::language_tsx()),
2561 )
2562 .with_brackets_query(
2563 r#"
2564 ("{" @open "}" @close)
2565 ("(" @open ")" @close)
2566 "#,
2567 )
2568 .unwrap()
2569 .with_indents_query(
2570 r#"
2571 (object "}" @end) @indent
2572 "#,
2573 )
2574 .unwrap()
2575}
2576
2577fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
2578 buffer.update(cx, |buffer, _| {
2579 let snapshot = buffer.snapshot();
2580 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
2581 layers[0].node().to_sexp()
2582 })
2583}
2584
2585// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
2586fn assert_bracket_pairs(
2587 selection_text: &'static str,
2588 bracket_pair_texts: Vec<&'static str>,
2589 language: Language,
2590 cx: &mut AppContext,
2591) {
2592 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
2593 let buffer = cx.new_model(|cx| {
2594 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
2595 });
2596 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
2597
2598 let selection_range = selection_ranges[0].clone();
2599
2600 let bracket_pairs = bracket_pair_texts
2601 .into_iter()
2602 .map(|pair_text| {
2603 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
2604 assert_eq!(bracket_text, expected_text);
2605 (ranges[0].clone(), ranges[1].clone())
2606 })
2607 .collect::<Vec<_>>();
2608
2609 assert_set_eq!(
2610 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
2611 bracket_pairs
2612 );
2613}
2614
2615fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
2616 let settings_store = SettingsStore::test(cx);
2617 cx.set_global(settings_store);
2618 crate::init(cx);
2619 cx.update_global::<SettingsStore, _>(|settings, cx| {
2620 settings.update_user_settings::<AllLanguageSettings>(cx, f);
2621 });
2622}