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 git::diff::assert_hunks;
10use gpui::{AppContext, BorrowAppContext, Model};
11use gpui::{Context, TestAppContext};
12use indoc::indoc;
13use proto::deserialize_operation;
14use rand::prelude::*;
15use regex::RegexBuilder;
16use settings::SettingsStore;
17use std::{
18 env,
19 ops::Range,
20 sync::LazyLock,
21 time::{Duration, Instant},
22};
23use syntax_map::TreeSitterOptions;
24use text::network::Network;
25use text::{BufferId, LineEnding, LineIndent};
26use text::{Point, ToPoint};
27use unindent::Unindent as _;
28use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
29
30pub static TRAILING_WHITESPACE_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
31 RegexBuilder::new(r"[ \t]+$")
32 .multi_line(true)
33 .build()
34 .expect("Failed to create TRAILING_WHITESPACE_REGEX")
35});
36
37#[cfg(test)]
38#[ctor::ctor]
39fn init_logger() {
40 if std::env::var("RUST_LOG").is_ok() {
41 env_logger::init();
42 }
43}
44
45#[gpui::test]
46fn test_line_endings(cx: &mut gpui::AppContext) {
47 init_settings(cx, |_| {});
48
49 cx.new_model(|cx| {
50 let mut buffer =
51 Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
52 assert_eq!(buffer.text(), "one\ntwo\nthree");
53 assert_eq!(buffer.line_ending(), LineEnding::Windows);
54
55 buffer.check_invariants();
56 buffer.edit(
57 [(buffer.len()..buffer.len(), "\r\nfour")],
58 Some(AutoindentMode::EachLine),
59 cx,
60 );
61 buffer.edit([(0..0, "zero\r\n")], None, cx);
62 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
63 assert_eq!(buffer.line_ending(), LineEnding::Windows);
64 buffer.check_invariants();
65
66 buffer
67 });
68}
69
70#[gpui::test]
71fn test_select_language(cx: &mut AppContext) {
72 init_settings(cx, |_| {});
73
74 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
75 registry.add(Arc::new(Language::new(
76 LanguageConfig {
77 name: LanguageName::new("Rust"),
78 matcher: LanguageMatcher {
79 path_suffixes: vec!["rs".to_string()],
80 ..Default::default()
81 },
82 ..Default::default()
83 },
84 Some(tree_sitter_rust::LANGUAGE.into()),
85 )));
86 registry.add(Arc::new(Language::new(
87 LanguageConfig {
88 name: LanguageName::new("Make"),
89 matcher: LanguageMatcher {
90 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
91 ..Default::default()
92 },
93 ..Default::default()
94 },
95 Some(tree_sitter_rust::LANGUAGE.into()),
96 )));
97
98 // matching file extension
99 assert_eq!(
100 registry
101 .language_for_file(&file("src/lib.rs"), None, cx)
102 .map(|l| l.name()),
103 Some("Rust".into())
104 );
105 assert_eq!(
106 registry
107 .language_for_file(&file("src/lib.mk"), None, cx)
108 .map(|l| l.name()),
109 Some("Make".into())
110 );
111
112 // matching filename
113 assert_eq!(
114 registry
115 .language_for_file(&file("src/Makefile"), None, cx)
116 .map(|l| l.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 .map(|l| l.name()),
125 None
126 );
127 assert_eq!(
128 registry
129 .language_for_file(&file("zed/a.cars"), None, cx)
130 .map(|l| l.name()),
131 None
132 );
133 assert_eq!(
134 registry
135 .language_for_file(&file("zed/sumk"), None, cx)
136 .map(|l| l.name()),
137 None
138 );
139}
140
141#[gpui::test(iterations = 10)]
142async fn test_first_line_pattern(cx: &mut TestAppContext) {
143 cx.update(|cx| init_settings(cx, |_| {}));
144
145 let languages = LanguageRegistry::test(cx.executor());
146 let languages = Arc::new(languages);
147
148 languages.register_test_language(LanguageConfig {
149 name: "JavaScript".into(),
150 matcher: LanguageMatcher {
151 path_suffixes: vec!["js".into()],
152 first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
153 },
154 ..Default::default()
155 });
156
157 assert!(cx
158 .read(|cx| languages.language_for_file(&file("the/script"), None, cx))
159 .is_none());
160 assert!(cx
161 .read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
162 .is_none());
163
164 assert_eq!(
165 cx.read(|cx| languages.language_for_file(
166 &file("the/script"),
167 Some(&"#!/bin/env node".into()),
168 cx
169 ))
170 .unwrap()
171 .name(),
172 "JavaScript".into()
173 );
174}
175
176#[gpui::test]
177async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {
178 cx.update(|cx| {
179 init_settings(cx, |settings| {
180 settings.file_types.extend([
181 ("TypeScript".into(), vec!["js".into()]),
182 ("C++".into(), vec!["c".into()]),
183 (
184 "Dockerfile".into(),
185 vec!["Dockerfile".into(), "Dockerfile.*".into()],
186 ),
187 ]);
188 })
189 });
190
191 let languages = Arc::new(LanguageRegistry::test(cx.executor()));
192
193 for config in [
194 LanguageConfig {
195 name: "JavaScript".into(),
196 matcher: LanguageMatcher {
197 path_suffixes: vec!["js".to_string()],
198 ..Default::default()
199 },
200 ..Default::default()
201 },
202 LanguageConfig {
203 name: "TypeScript".into(),
204 matcher: LanguageMatcher {
205 path_suffixes: vec!["js".to_string()],
206 ..Default::default()
207 },
208 ..Default::default()
209 },
210 LanguageConfig {
211 name: "C++".into(),
212 matcher: LanguageMatcher {
213 path_suffixes: vec!["cpp".to_string()],
214 ..Default::default()
215 },
216 ..Default::default()
217 },
218 LanguageConfig {
219 name: "C".into(),
220 matcher: LanguageMatcher {
221 path_suffixes: vec!["c".to_string()],
222 ..Default::default()
223 },
224 ..Default::default()
225 },
226 LanguageConfig {
227 name: "Dockerfile".into(),
228 matcher: LanguageMatcher {
229 path_suffixes: vec!["Dockerfile".to_string()],
230 ..Default::default()
231 },
232 ..Default::default()
233 },
234 ] {
235 languages.add(Arc::new(Language::new(config, None)));
236 }
237
238 let language = cx
239 .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
240 .unwrap();
241 assert_eq!(language.name(), "TypeScript".into());
242 let language = cx
243 .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
244 .unwrap();
245 assert_eq!(language.name(), "C++".into());
246 let language = cx
247 .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
248 .unwrap();
249 assert_eq!(language.name(), "Dockerfile".into());
250}
251
252fn file(path: &str) -> Arc<dyn File> {
253 Arc::new(TestFile {
254 path: Path::new(path).into(),
255 root_name: "zed".into(),
256 })
257}
258
259#[gpui::test]
260fn test_edit_events(cx: &mut gpui::AppContext) {
261 let mut now = Instant::now();
262 let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
263 let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
264
265 let buffer1 = cx.new_model(|cx| Buffer::local("abcdef", cx));
266 let buffer2 = cx.new_model(|cx| {
267 Buffer::remote(
268 BufferId::from(cx.entity_id().as_non_zero_u64()),
269 1,
270 Capability::ReadWrite,
271 "abcdef",
272 )
273 });
274 let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
275 buffer1.update(cx, {
276 let buffer1_ops = buffer1_ops.clone();
277 |buffer, cx| {
278 let buffer_1_events = buffer_1_events.clone();
279 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
280 BufferEvent::Operation {
281 operation,
282 is_local: true,
283 } => buffer1_ops.lock().push(operation),
284 event => buffer_1_events.lock().push(event),
285 })
286 .detach();
287 let buffer_2_events = buffer_2_events.clone();
288 cx.subscribe(&buffer2, move |_, _, event, _| match event.clone() {
289 BufferEvent::Operation {
290 is_local: false, ..
291 } => {}
292 event => buffer_2_events.lock().push(event),
293 })
294 .detach();
295
296 // An edit emits an edited event, followed by a dirty changed event,
297 // since the buffer was previously in a clean state.
298 buffer.edit([(2..4, "XYZ")], None, cx);
299
300 // An empty transaction does not emit any events.
301 buffer.start_transaction();
302 buffer.end_transaction(cx);
303
304 // A transaction containing two edits emits one edited event.
305 now += Duration::from_secs(1);
306 buffer.start_transaction_at(now);
307 buffer.edit([(5..5, "u")], None, cx);
308 buffer.edit([(6..6, "w")], None, cx);
309 buffer.end_transaction_at(now, cx);
310
311 // Undoing a transaction emits one edited event.
312 buffer.undo(cx);
313 }
314 });
315
316 // Incorporating a set of remote ops emits a single edited event,
317 // followed by a dirty changed event.
318 buffer2.update(cx, |buffer, cx| {
319 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
320 });
321 assert_eq!(
322 mem::take(&mut *buffer_1_events.lock()),
323 vec![
324 BufferEvent::Edited,
325 BufferEvent::DirtyChanged,
326 BufferEvent::Edited,
327 BufferEvent::Edited,
328 ]
329 );
330 assert_eq!(
331 mem::take(&mut *buffer_2_events.lock()),
332 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
333 );
334
335 buffer1.update(cx, |buffer, cx| {
336 // Undoing the first transaction emits edited event, followed by a
337 // dirty changed event, since the buffer is again in a clean state.
338 buffer.undo(cx);
339 });
340 // Incorporating the remote ops again emits a single edited event,
341 // followed by a dirty changed event.
342 buffer2.update(cx, |buffer, cx| {
343 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
344 });
345 assert_eq!(
346 mem::take(&mut *buffer_1_events.lock()),
347 vec![BufferEvent::Edited, BufferEvent::DirtyChanged,]
348 );
349 assert_eq!(
350 mem::take(&mut *buffer_2_events.lock()),
351 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
352 );
353}
354
355#[gpui::test]
356async fn test_apply_diff(cx: &mut TestAppContext) {
357 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
358 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
359 let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
360
361 let text = "a\nccc\ndddd\nffffff\n";
362 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
363 buffer.update(cx, |buffer, cx| {
364 buffer.apply_diff(diff, cx).unwrap();
365 assert_eq!(buffer.text(), text);
366 assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
367 });
368
369 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
370 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
371 buffer.update(cx, |buffer, cx| {
372 buffer.apply_diff(diff, cx).unwrap();
373 assert_eq!(buffer.text(), text);
374 assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
375 });
376}
377
378#[gpui::test(iterations = 10)]
379async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
380 let text = [
381 "zero", //
382 "one ", // 2 trailing spaces
383 "two", //
384 "three ", // 3 trailing spaces
385 "four", //
386 "five ", // 4 trailing spaces
387 ]
388 .join("\n");
389
390 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
391
392 // Spawn a task to format the buffer's whitespace.
393 // Pause so that the formatting task starts running.
394 let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
395 smol::future::yield_now().await;
396
397 // Edit the buffer while the normalization task is running.
398 let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
399 buffer.update(cx, |buffer, cx| {
400 buffer.edit(
401 [
402 (Point::new(0, 1)..Point::new(0, 1), "EE"),
403 (Point::new(3, 5)..Point::new(3, 5), "EEE"),
404 ],
405 None,
406 cx,
407 );
408 });
409
410 let format_diff = format.await;
411 buffer.update(cx, |buffer, cx| {
412 let version_before_format = format_diff.base_version.clone();
413 buffer.apply_diff(format_diff, cx);
414
415 // The outcome depends on the order of concurrent tasks.
416 //
417 // If the edit occurred while searching for trailing whitespace ranges,
418 // then the trailing whitespace region touched by the edit is left intact.
419 if version_before_format == version_before_edit {
420 assert_eq!(
421 buffer.text(),
422 [
423 "zEEero", //
424 "one", //
425 "two", //
426 "threeEEE ", //
427 "four", //
428 "five", //
429 ]
430 .join("\n")
431 );
432 }
433 // Otherwise, all trailing whitespace is removed.
434 else {
435 assert_eq!(
436 buffer.text(),
437 [
438 "zEEero", //
439 "one", //
440 "two", //
441 "threeEEE", //
442 "four", //
443 "five", //
444 ]
445 .join("\n")
446 );
447 }
448 });
449}
450
451#[gpui::test]
452async fn test_reparse(cx: &mut gpui::TestAppContext) {
453 let text = "fn a() {}";
454 let buffer =
455 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
456
457 // Wait for the initial text to parse
458 cx.executor().run_until_parked();
459 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
460 assert_eq!(
461 get_tree_sexp(&buffer, cx),
462 concat!(
463 "(source_file (function_item name: (identifier) ",
464 "parameters: (parameters) ",
465 "body: (block)))"
466 )
467 );
468
469 buffer.update(cx, |buffer, _| {
470 buffer.set_sync_parse_timeout(Duration::ZERO)
471 });
472
473 // Perform some edits (add parameter and variable reference)
474 // Parsing doesn't begin until the transaction is complete
475 buffer.update(cx, |buf, cx| {
476 buf.start_transaction();
477
478 let offset = buf.text().find(')').unwrap();
479 buf.edit([(offset..offset, "b: C")], None, cx);
480 assert!(!buf.is_parsing());
481
482 let offset = buf.text().find('}').unwrap();
483 buf.edit([(offset..offset, " d; ")], None, cx);
484 assert!(!buf.is_parsing());
485
486 buf.end_transaction(cx);
487 assert_eq!(buf.text(), "fn a(b: C) { d; }");
488 assert!(buf.is_parsing());
489 });
490 cx.executor().run_until_parked();
491 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
492 assert_eq!(
493 get_tree_sexp(&buffer, cx),
494 concat!(
495 "(source_file (function_item name: (identifier) ",
496 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
497 "body: (block (expression_statement (identifier)))))"
498 )
499 );
500
501 // Perform a series of edits without waiting for the current parse to complete:
502 // * turn identifier into a field expression
503 // * turn field expression into a method call
504 // * add a turbofish to the method call
505 buffer.update(cx, |buf, cx| {
506 let offset = buf.text().find(';').unwrap();
507 buf.edit([(offset..offset, ".e")], None, cx);
508 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
509 assert!(buf.is_parsing());
510 });
511 buffer.update(cx, |buf, cx| {
512 let offset = buf.text().find(';').unwrap();
513 buf.edit([(offset..offset, "(f)")], None, cx);
514 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
515 assert!(buf.is_parsing());
516 });
517 buffer.update(cx, |buf, cx| {
518 let offset = buf.text().find("(f)").unwrap();
519 buf.edit([(offset..offset, "::<G>")], None, cx);
520 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
521 assert!(buf.is_parsing());
522 });
523 cx.executor().run_until_parked();
524 assert_eq!(
525 get_tree_sexp(&buffer, cx),
526 concat!(
527 "(source_file (function_item name: (identifier) ",
528 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
529 "body: (block (expression_statement (call_expression ",
530 "function: (generic_function ",
531 "function: (field_expression value: (identifier) field: (field_identifier)) ",
532 "type_arguments: (type_arguments (type_identifier))) ",
533 "arguments: (arguments (identifier)))))))",
534 )
535 );
536
537 buffer.update(cx, |buf, cx| {
538 buf.undo(cx);
539 buf.undo(cx);
540 buf.undo(cx);
541 buf.undo(cx);
542 assert_eq!(buf.text(), "fn a() {}");
543 assert!(buf.is_parsing());
544 });
545
546 cx.executor().run_until_parked();
547 assert_eq!(
548 get_tree_sexp(&buffer, cx),
549 concat!(
550 "(source_file (function_item name: (identifier) ",
551 "parameters: (parameters) ",
552 "body: (block)))"
553 )
554 );
555
556 buffer.update(cx, |buf, cx| {
557 buf.redo(cx);
558 buf.redo(cx);
559 buf.redo(cx);
560 buf.redo(cx);
561 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
562 assert!(buf.is_parsing());
563 });
564 cx.executor().run_until_parked();
565 assert_eq!(
566 get_tree_sexp(&buffer, cx),
567 concat!(
568 "(source_file (function_item name: (identifier) ",
569 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
570 "body: (block (expression_statement (call_expression ",
571 "function: (generic_function ",
572 "function: (field_expression value: (identifier) field: (field_identifier)) ",
573 "type_arguments: (type_arguments (type_identifier))) ",
574 "arguments: (arguments (identifier)))))))",
575 )
576 );
577}
578
579#[gpui::test]
580async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
581 let buffer = cx.new_model(|cx| {
582 let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
583 buffer.set_sync_parse_timeout(Duration::ZERO);
584 buffer
585 });
586
587 // Wait for the initial text to parse
588 cx.executor().run_until_parked();
589 assert_eq!(
590 get_tree_sexp(&buffer, cx),
591 "(source_file (expression_statement (block)))"
592 );
593
594 buffer.update(cx, |buffer, cx| {
595 buffer.set_language(Some(Arc::new(json_lang())), cx)
596 });
597 cx.executor().run_until_parked();
598 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
599}
600
601#[gpui::test]
602async fn test_outline(cx: &mut gpui::TestAppContext) {
603 let text = r#"
604 struct Person {
605 name: String,
606 age: usize,
607 }
608
609 mod module {
610 enum LoginState {
611 LoggedOut,
612 LoggingOn,
613 LoggedIn {
614 person: Person,
615 time: Instant,
616 }
617 }
618 }
619
620 impl Eq for Person {}
621
622 impl Drop for Person {
623 fn drop(&mut self) {
624 println!("bye");
625 }
626 }
627 "#
628 .unindent();
629
630 let buffer =
631 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
632 let outline = buffer
633 .update(cx, |buffer, _| buffer.snapshot().outline(None))
634 .unwrap();
635
636 assert_eq!(
637 outline
638 .items
639 .iter()
640 .map(|item| (item.text.as_str(), item.depth))
641 .collect::<Vec<_>>(),
642 &[
643 ("struct Person", 0),
644 ("name", 1),
645 ("age", 1),
646 ("mod module", 0),
647 ("enum LoginState", 1),
648 ("LoggedOut", 2),
649 ("LoggingOn", 2),
650 ("LoggedIn", 2),
651 ("person", 3),
652 ("time", 3),
653 ("impl Eq for Person", 0),
654 ("impl Drop for Person", 0),
655 ("fn drop", 1),
656 ]
657 );
658
659 // Without space, we only match on names
660 assert_eq!(
661 search(&outline, "oon", cx).await,
662 &[
663 ("mod module", vec![]), // included as the parent of a match
664 ("enum LoginState", vec![]), // included as the parent of a match
665 ("LoggingOn", vec![1, 7, 8]), // matches
666 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
667 ]
668 );
669
670 assert_eq!(
671 search(&outline, "dp p", cx).await,
672 &[
673 ("impl Drop for Person", vec![5, 8, 9, 14]),
674 ("fn drop", vec![]),
675 ]
676 );
677 assert_eq!(
678 search(&outline, "dpn", cx).await,
679 &[("impl Drop for Person", vec![5, 14, 19])]
680 );
681 assert_eq!(
682 search(&outline, "impl ", cx).await,
683 &[
684 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
685 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
686 ("fn drop", vec![]),
687 ]
688 );
689
690 async fn search<'a>(
691 outline: &'a Outline<Anchor>,
692 query: &'a str,
693 cx: &'a gpui::TestAppContext,
694 ) -> Vec<(&'a str, Vec<usize>)> {
695 let matches = cx
696 .update(|cx| outline.search(query, cx.background_executor().clone()))
697 .await;
698 matches
699 .into_iter()
700 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
701 .collect::<Vec<_>>()
702 }
703}
704
705#[gpui::test]
706async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
707 let text = r#"
708 impl A for B<
709 C
710 > {
711 };
712 "#
713 .unindent();
714
715 let buffer =
716 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
717 let outline = buffer
718 .update(cx, |buffer, _| buffer.snapshot().outline(None))
719 .unwrap();
720
721 assert_eq!(
722 outline
723 .items
724 .iter()
725 .map(|item| (item.text.as_str(), item.depth))
726 .collect::<Vec<_>>(),
727 &[("impl A for B<", 0)]
728 );
729}
730
731#[gpui::test]
732async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
733 let language = javascript_lang()
734 .with_outline_query(
735 r#"
736 (function_declaration
737 "function" @context
738 name: (_) @name
739 parameters: (formal_parameters
740 "(" @context.extra
741 ")" @context.extra)) @item
742 "#,
743 )
744 .unwrap();
745
746 let text = r#"
747 function a() {}
748 function b(c) {}
749 "#
750 .unindent();
751
752 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
753 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
754
755 // extra context nodes are included in the outline.
756 let outline = snapshot.outline(None).unwrap();
757 assert_eq!(
758 outline
759 .items
760 .iter()
761 .map(|item| (item.text.as_str(), item.depth))
762 .collect::<Vec<_>>(),
763 &[("function a()", 0), ("function b( )", 0),]
764 );
765
766 // extra context nodes do not appear in breadcrumbs.
767 let symbols = snapshot.symbols_containing(3, None).unwrap();
768 assert_eq!(
769 symbols
770 .iter()
771 .map(|item| (item.text.as_str(), item.depth))
772 .collect::<Vec<_>>(),
773 &[("function a", 0)]
774 );
775}
776
777#[gpui::test]
778fn test_outline_annotations(cx: &mut AppContext) {
779 // Add this new test case
780 let text = r#"
781 /// This is a doc comment
782 /// that spans multiple lines
783 fn annotated_function() {
784 // This is not an annotation
785 }
786
787 // This is a single-line annotation
788 fn another_function() {}
789
790 fn unannotated_function() {}
791
792 // This comment is not an annotation
793
794 fn function_after_blank_line() {}
795 "#
796 .unindent();
797
798 let buffer =
799 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
800 let outline = buffer
801 .update(cx, |buffer, _| buffer.snapshot().outline(None))
802 .unwrap();
803
804 assert_eq!(
805 outline
806 .items
807 .into_iter()
808 .map(|item| (
809 item.text,
810 item.depth,
811 item.annotation_range
812 .map(|range| { buffer.read(cx).text_for_range(range).collect::<String>() })
813 ))
814 .collect::<Vec<_>>(),
815 &[
816 (
817 "fn annotated_function".to_string(),
818 0,
819 Some("/// This is a doc comment\n/// that spans multiple lines".to_string())
820 ),
821 (
822 "fn another_function".to_string(),
823 0,
824 Some("// This is a single-line annotation".to_string())
825 ),
826 ("fn unannotated_function".to_string(), 0, None),
827 ("fn function_after_blank_line".to_string(), 0, None),
828 ]
829 );
830}
831
832#[gpui::test]
833async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
834 let text = r#"
835 impl Person {
836 fn one() {
837 1
838 }
839
840 fn two() {
841 2
842 }fn three() {
843 3
844 }
845 }
846 "#
847 .unindent();
848
849 let buffer =
850 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
851 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
852
853 // point is at the start of an item
854 assert_eq!(
855 symbols_containing(Point::new(1, 4), &snapshot),
856 vec![
857 (
858 "impl Person".to_string(),
859 Point::new(0, 0)..Point::new(10, 1)
860 ),
861 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
862 ]
863 );
864
865 // point is in the middle of an item
866 assert_eq!(
867 symbols_containing(Point::new(2, 8), &snapshot),
868 vec![
869 (
870 "impl Person".to_string(),
871 Point::new(0, 0)..Point::new(10, 1)
872 ),
873 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
874 ]
875 );
876
877 // point is at the end of an item
878 assert_eq!(
879 symbols_containing(Point::new(3, 5), &snapshot),
880 vec![
881 (
882 "impl Person".to_string(),
883 Point::new(0, 0)..Point::new(10, 1)
884 ),
885 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
886 ]
887 );
888
889 // point is in between two adjacent items
890 assert_eq!(
891 symbols_containing(Point::new(7, 5), &snapshot),
892 vec![
893 (
894 "impl Person".to_string(),
895 Point::new(0, 0)..Point::new(10, 1)
896 ),
897 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
898 ]
899 );
900
901 fn symbols_containing(
902 position: Point,
903 snapshot: &BufferSnapshot,
904 ) -> Vec<(String, Range<Point>)> {
905 snapshot
906 .symbols_containing(position, None)
907 .unwrap()
908 .into_iter()
909 .map(|item| {
910 (
911 item.text,
912 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
913 )
914 })
915 .collect()
916 }
917}
918
919#[gpui::test]
920fn test_text_objects(cx: &mut AppContext) {
921 let (text, ranges) = marked_text_ranges(
922 indoc! {r#"
923 impl Hello {
924 fn say() -> u8 { return /* ˇhi */ 1 }
925 }"#
926 },
927 false,
928 );
929
930 let buffer =
931 cx.new_model(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(rust_lang()), cx));
932 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
933
934 let matches = snapshot
935 .text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
936 .map(|(range, text_object)| (&text[range], text_object))
937 .collect::<Vec<_>>();
938
939 assert_eq!(
940 matches,
941 &[
942 ("/* hi */", TextObject::AroundComment),
943 ("return /* hi */ 1", TextObject::InsideFunction),
944 (
945 "fn say() -> u8 { return /* hi */ 1 }",
946 TextObject::AroundFunction
947 ),
948 ],
949 )
950}
951
952#[gpui::test]
953fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
954 let mut assert = |selection_text, range_markers| {
955 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
956 };
957
958 assert(
959 indoc! {"
960 mod x {
961 moˇd y {
962
963 }
964 }
965 let foo = 1;"},
966 vec![indoc! {"
967 mod x «{»
968 mod y {
969
970 }
971 «}»
972 let foo = 1;"}],
973 );
974
975 assert(
976 indoc! {"
977 mod x {
978 mod y ˇ{
979
980 }
981 }
982 let foo = 1;"},
983 vec![
984 indoc! {"
985 mod x «{»
986 mod y {
987
988 }
989 «}»
990 let foo = 1;"},
991 indoc! {"
992 mod x {
993 mod y «{»
994
995 «}»
996 }
997 let foo = 1;"},
998 ],
999 );
1000
1001 assert(
1002 indoc! {"
1003 mod x {
1004 mod y {
1005
1006 }ˇ
1007 }
1008 let foo = 1;"},
1009 vec![
1010 indoc! {"
1011 mod x «{»
1012 mod y {
1013
1014 }
1015 «}»
1016 let foo = 1;"},
1017 indoc! {"
1018 mod x {
1019 mod y «{»
1020
1021 «}»
1022 }
1023 let foo = 1;"},
1024 ],
1025 );
1026
1027 assert(
1028 indoc! {"
1029 mod x {
1030 mod y {
1031
1032 }
1033 ˇ}
1034 let foo = 1;"},
1035 vec![indoc! {"
1036 mod x «{»
1037 mod y {
1038
1039 }
1040 «}»
1041 let foo = 1;"}],
1042 );
1043
1044 assert(
1045 indoc! {"
1046 mod x {
1047 mod y {
1048
1049 }
1050 }
1051 let fˇoo = 1;"},
1052 vec![],
1053 );
1054
1055 // Regression test: avoid crash when querying at the end of the buffer.
1056 assert(
1057 indoc! {"
1058 mod x {
1059 mod y {
1060
1061 }
1062 }
1063 let foo = 1;ˇ"},
1064 vec![],
1065 );
1066}
1067
1068#[gpui::test]
1069fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
1070 let mut assert = |selection_text, bracket_pair_texts| {
1071 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
1072 };
1073
1074 assert(
1075 indoc! {"
1076 for (const a in b)ˇ {
1077 // a comment that's longer than the for-loop header
1078 }"},
1079 vec![indoc! {"
1080 for «(»const a in b«)» {
1081 // a comment that's longer than the for-loop header
1082 }"}],
1083 );
1084
1085 // Regression test: even though the parent node of the parentheses (the for loop) does
1086 // intersect the given range, the parentheses themselves do not contain the range, so
1087 // they should not be returned. Only the curly braces contain the range.
1088 assert(
1089 indoc! {"
1090 for (const a in b) {ˇ
1091 // a comment that's longer than the for-loop header
1092 }"},
1093 vec![indoc! {"
1094 for (const a in b) «{»
1095 // a comment that's longer than the for-loop header
1096 «}»"}],
1097 );
1098}
1099
1100#[gpui::test]
1101fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
1102 cx.new_model(|cx| {
1103 let text = "fn a() { b(|c| {}) }";
1104 let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1105 let snapshot = buffer.snapshot();
1106
1107 assert_eq!(
1108 snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
1109 Some(range_of(text, "|"))
1110 );
1111 assert_eq!(
1112 snapshot.range_for_syntax_ancestor(range_of(text, "|")),
1113 Some(range_of(text, "|c|"))
1114 );
1115 assert_eq!(
1116 snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
1117 Some(range_of(text, "|c| {}"))
1118 );
1119 assert_eq!(
1120 snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
1121 Some(range_of(text, "(|c| {})"))
1122 );
1123
1124 buffer
1125 });
1126
1127 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1128 let start = text.find(part).unwrap();
1129 start..start
1130 }
1131
1132 fn range_of(text: &str, part: &str) -> Range<usize> {
1133 let start = text.find(part).unwrap();
1134 start..start + part.len()
1135 }
1136}
1137
1138#[gpui::test]
1139fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
1140 init_settings(cx, |_| {});
1141
1142 cx.new_model(|cx| {
1143 let text = "fn a() {}";
1144 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1145
1146 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1147 assert_eq!(buffer.text(), "fn a() {\n \n}");
1148
1149 buffer.edit(
1150 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1151 Some(AutoindentMode::EachLine),
1152 cx,
1153 );
1154 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1155
1156 // Create a field expression on a new line, causing that line
1157 // to be indented.
1158 buffer.edit(
1159 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1160 Some(AutoindentMode::EachLine),
1161 cx,
1162 );
1163 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1164
1165 // Remove the dot so that the line is no longer a field expression,
1166 // causing the line to be outdented.
1167 buffer.edit(
1168 [(Point::new(2, 8)..Point::new(2, 9), "")],
1169 Some(AutoindentMode::EachLine),
1170 cx,
1171 );
1172 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1173
1174 buffer
1175 });
1176}
1177
1178#[gpui::test]
1179fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
1180 init_settings(cx, |settings| {
1181 settings.defaults.hard_tabs = Some(true);
1182 });
1183
1184 cx.new_model(|cx| {
1185 let text = "fn a() {}";
1186 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1187
1188 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1189 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1190
1191 buffer.edit(
1192 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1193 Some(AutoindentMode::EachLine),
1194 cx,
1195 );
1196 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1197
1198 // Create a field expression on a new line, causing that line
1199 // to be indented.
1200 buffer.edit(
1201 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1202 Some(AutoindentMode::EachLine),
1203 cx,
1204 );
1205 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1206
1207 // Remove the dot so that the line is no longer a field expression,
1208 // causing the line to be outdented.
1209 buffer.edit(
1210 [(Point::new(2, 2)..Point::new(2, 3), "")],
1211 Some(AutoindentMode::EachLine),
1212 cx,
1213 );
1214 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1215
1216 buffer
1217 });
1218}
1219
1220#[gpui::test]
1221fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
1222 init_settings(cx, |_| {});
1223
1224 cx.new_model(|cx| {
1225 let mut buffer = Buffer::local(
1226 "
1227 fn a() {
1228 c;
1229 d;
1230 }
1231 "
1232 .unindent(),
1233 cx,
1234 )
1235 .with_language(Arc::new(rust_lang()), cx);
1236
1237 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1238 // their indentation is not adjusted.
1239 buffer.edit_via_marked_text(
1240 &"
1241 fn a() {
1242 c«()»;
1243 d«()»;
1244 }
1245 "
1246 .unindent(),
1247 Some(AutoindentMode::EachLine),
1248 cx,
1249 );
1250 assert_eq!(
1251 buffer.text(),
1252 "
1253 fn a() {
1254 c();
1255 d();
1256 }
1257 "
1258 .unindent()
1259 );
1260
1261 // When appending new content after these lines, the indentation is based on the
1262 // preceding lines' actual indentation.
1263 buffer.edit_via_marked_text(
1264 &"
1265 fn a() {
1266 c«
1267 .f
1268 .g()»;
1269 d«
1270 .f
1271 .g()»;
1272 }
1273 "
1274 .unindent(),
1275 Some(AutoindentMode::EachLine),
1276 cx,
1277 );
1278 assert_eq!(
1279 buffer.text(),
1280 "
1281 fn a() {
1282 c
1283 .f
1284 .g();
1285 d
1286 .f
1287 .g();
1288 }
1289 "
1290 .unindent()
1291 );
1292
1293 // Insert a newline after the open brace. It is auto-indented
1294 buffer.edit_via_marked_text(
1295 &"
1296 fn a() {«
1297 »
1298 c
1299 .f
1300 .g();
1301 d
1302 .f
1303 .g();
1304 }
1305 "
1306 .unindent(),
1307 Some(AutoindentMode::EachLine),
1308 cx,
1309 );
1310 assert_eq!(
1311 buffer.text(),
1312 "
1313 fn a() {
1314 ˇ
1315 c
1316 .f
1317 .g();
1318 d
1319 .f
1320 .g();
1321 }
1322 "
1323 .unindent()
1324 .replace("ˇ", "")
1325 );
1326
1327 // Manually outdent the line. It stays outdented.
1328 buffer.edit_via_marked_text(
1329 &"
1330 fn a() {
1331 «»
1332 c
1333 .f
1334 .g();
1335 d
1336 .f
1337 .g();
1338 }
1339 "
1340 .unindent(),
1341 Some(AutoindentMode::EachLine),
1342 cx,
1343 );
1344 assert_eq!(
1345 buffer.text(),
1346 "
1347 fn a() {
1348
1349 c
1350 .f
1351 .g();
1352 d
1353 .f
1354 .g();
1355 }
1356 "
1357 .unindent()
1358 );
1359
1360 buffer
1361 });
1362
1363 cx.new_model(|cx| {
1364 eprintln!("second buffer: {:?}", cx.entity_id());
1365
1366 let mut buffer = Buffer::local(
1367 "
1368 fn a() {
1369 b();
1370 |
1371 "
1372 .replace('|', "") // marker to preserve trailing whitespace
1373 .unindent(),
1374 cx,
1375 )
1376 .with_language(Arc::new(rust_lang()), cx);
1377
1378 // Insert a closing brace. It is outdented.
1379 buffer.edit_via_marked_text(
1380 &"
1381 fn a() {
1382 b();
1383 «}»
1384 "
1385 .unindent(),
1386 Some(AutoindentMode::EachLine),
1387 cx,
1388 );
1389 assert_eq!(
1390 buffer.text(),
1391 "
1392 fn a() {
1393 b();
1394 }
1395 "
1396 .unindent()
1397 );
1398
1399 // Manually edit the leading whitespace. The edit is preserved.
1400 buffer.edit_via_marked_text(
1401 &"
1402 fn a() {
1403 b();
1404 « »}
1405 "
1406 .unindent(),
1407 Some(AutoindentMode::EachLine),
1408 cx,
1409 );
1410 assert_eq!(
1411 buffer.text(),
1412 "
1413 fn a() {
1414 b();
1415 }
1416 "
1417 .unindent()
1418 );
1419 buffer
1420 });
1421
1422 eprintln!("DONE");
1423}
1424
1425#[gpui::test]
1426fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
1427 init_settings(cx, |_| {});
1428
1429 cx.new_model(|cx| {
1430 let mut buffer = Buffer::local(
1431 "
1432 fn a() {
1433 i
1434 }
1435 "
1436 .unindent(),
1437 cx,
1438 )
1439 .with_language(Arc::new(rust_lang()), cx);
1440
1441 // Regression test: line does not get outdented due to syntax error
1442 buffer.edit_via_marked_text(
1443 &"
1444 fn a() {
1445 i«f let Some(x) = y»
1446 }
1447 "
1448 .unindent(),
1449 Some(AutoindentMode::EachLine),
1450 cx,
1451 );
1452 assert_eq!(
1453 buffer.text(),
1454 "
1455 fn a() {
1456 if let Some(x) = y
1457 }
1458 "
1459 .unindent()
1460 );
1461
1462 buffer.edit_via_marked_text(
1463 &"
1464 fn a() {
1465 if let Some(x) = y« {»
1466 }
1467 "
1468 .unindent(),
1469 Some(AutoindentMode::EachLine),
1470 cx,
1471 );
1472 assert_eq!(
1473 buffer.text(),
1474 "
1475 fn a() {
1476 if let Some(x) = y {
1477 }
1478 "
1479 .unindent()
1480 );
1481
1482 buffer
1483 });
1484}
1485
1486#[gpui::test]
1487fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
1488 init_settings(cx, |_| {});
1489
1490 cx.new_model(|cx| {
1491 let mut buffer = Buffer::local(
1492 "
1493 fn a() {}
1494 "
1495 .unindent(),
1496 cx,
1497 )
1498 .with_language(Arc::new(rust_lang()), cx);
1499
1500 buffer.edit_via_marked_text(
1501 &"
1502 fn a(«
1503 b») {}
1504 "
1505 .unindent(),
1506 Some(AutoindentMode::EachLine),
1507 cx,
1508 );
1509 assert_eq!(
1510 buffer.text(),
1511 "
1512 fn a(
1513 b) {}
1514 "
1515 .unindent()
1516 );
1517
1518 // The indentation suggestion changed because `@end` node (a close paren)
1519 // is now at the beginning of the line.
1520 buffer.edit_via_marked_text(
1521 &"
1522 fn a(
1523 ˇ) {}
1524 "
1525 .unindent(),
1526 Some(AutoindentMode::EachLine),
1527 cx,
1528 );
1529 assert_eq!(
1530 buffer.text(),
1531 "
1532 fn a(
1533 ) {}
1534 "
1535 .unindent()
1536 );
1537
1538 buffer
1539 });
1540}
1541
1542#[gpui::test]
1543fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
1544 init_settings(cx, |_| {});
1545
1546 cx.new_model(|cx| {
1547 let text = "a\nb";
1548 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1549 buffer.edit(
1550 [(0..1, "\n"), (2..3, "\n")],
1551 Some(AutoindentMode::EachLine),
1552 cx,
1553 );
1554 assert_eq!(buffer.text(), "\n\n\n");
1555 buffer
1556 });
1557}
1558
1559#[gpui::test]
1560fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
1561 init_settings(cx, |_| {});
1562
1563 cx.new_model(|cx| {
1564 let text = "
1565 const a: usize = 1;
1566 fn b() {
1567 if c {
1568 let d = 2;
1569 }
1570 }
1571 "
1572 .unindent();
1573
1574 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1575 buffer.edit(
1576 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1577 Some(AutoindentMode::EachLine),
1578 cx,
1579 );
1580 assert_eq!(
1581 buffer.text(),
1582 "
1583 const a: usize = 1;
1584 fn b() {
1585 if c {
1586 e(
1587 f()
1588 );
1589 let d = 2;
1590 }
1591 }
1592 "
1593 .unindent()
1594 );
1595
1596 buffer
1597 });
1598}
1599
1600#[gpui::test]
1601fn test_autoindent_block_mode(cx: &mut AppContext) {
1602 init_settings(cx, |_| {});
1603
1604 cx.new_model(|cx| {
1605 let text = r#"
1606 fn a() {
1607 b();
1608 }
1609 "#
1610 .unindent();
1611 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1612
1613 // When this text was copied, both of the quotation marks were at the same
1614 // indent level, but the indentation of the first line was not included in
1615 // the copied text. This information is retained in the
1616 // 'original_indent_columns' vector.
1617 let original_indent_columns = vec![4];
1618 let inserted_text = r#"
1619 "
1620 c
1621 d
1622 e
1623 "
1624 "#
1625 .unindent();
1626
1627 // Insert the block at column zero. The entire block is indented
1628 // so that the first line matches the previous line's indentation.
1629 buffer.edit(
1630 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1631 Some(AutoindentMode::Block {
1632 original_indent_columns: original_indent_columns.clone(),
1633 }),
1634 cx,
1635 );
1636 assert_eq!(
1637 buffer.text(),
1638 r#"
1639 fn a() {
1640 b();
1641 "
1642 c
1643 d
1644 e
1645 "
1646 }
1647 "#
1648 .unindent()
1649 );
1650
1651 // Grouping is disabled in tests, so we need 2 undos
1652 buffer.undo(cx); // Undo the auto-indent
1653 buffer.undo(cx); // Undo the original edit
1654
1655 // Insert the block at a deeper indent level. The entire block is outdented.
1656 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1657 buffer.edit(
1658 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1659 Some(AutoindentMode::Block {
1660 original_indent_columns: original_indent_columns.clone(),
1661 }),
1662 cx,
1663 );
1664 assert_eq!(
1665 buffer.text(),
1666 r#"
1667 fn a() {
1668 b();
1669 "
1670 c
1671 d
1672 e
1673 "
1674 }
1675 "#
1676 .unindent()
1677 );
1678
1679 buffer
1680 });
1681}
1682
1683#[gpui::test]
1684fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
1685 init_settings(cx, |_| {});
1686
1687 cx.new_model(|cx| {
1688 let text = r#"
1689 fn a() {
1690 if b() {
1691
1692 }
1693 }
1694 "#
1695 .unindent();
1696 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1697
1698 // The original indent columns are not known, so this text is
1699 // auto-indented in a block as if the first line was copied in
1700 // its entirety.
1701 let original_indent_columns = Vec::new();
1702 let inserted_text = " c\n .d()\n .e();";
1703
1704 // Insert the block at column zero. The entire block is indented
1705 // so that the first line matches the previous line's indentation.
1706 buffer.edit(
1707 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1708 Some(AutoindentMode::Block {
1709 original_indent_columns: original_indent_columns.clone(),
1710 }),
1711 cx,
1712 );
1713 assert_eq!(
1714 buffer.text(),
1715 r#"
1716 fn a() {
1717 if b() {
1718 c
1719 .d()
1720 .e();
1721 }
1722 }
1723 "#
1724 .unindent()
1725 );
1726
1727 // Grouping is disabled in tests, so we need 2 undos
1728 buffer.undo(cx); // Undo the auto-indent
1729 buffer.undo(cx); // Undo the original edit
1730
1731 // Insert the block at a deeper indent level. The entire block is outdented.
1732 buffer.edit(
1733 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1734 None,
1735 cx,
1736 );
1737 buffer.edit(
1738 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1739 Some(AutoindentMode::Block {
1740 original_indent_columns: Vec::new(),
1741 }),
1742 cx,
1743 );
1744 assert_eq!(
1745 buffer.text(),
1746 r#"
1747 fn a() {
1748 if b() {
1749 c
1750 .d()
1751 .e();
1752 }
1753 }
1754 "#
1755 .unindent()
1756 );
1757
1758 buffer
1759 });
1760}
1761
1762#[gpui::test]
1763fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut AppContext) {
1764 init_settings(cx, |_| {});
1765
1766 cx.new_model(|cx| {
1767 let (text, ranges_to_replace) = marked_text_ranges(
1768 &"
1769 mod numbers {
1770 «fn one() {
1771 1
1772 }
1773 »
1774 «fn two() {
1775 2
1776 }
1777 »
1778 «fn three() {
1779 3
1780 }
1781 »}
1782 "
1783 .unindent(),
1784 false,
1785 );
1786
1787 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1788
1789 buffer.edit(
1790 [
1791 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
1792 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
1793 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
1794 ],
1795 Some(AutoindentMode::Block {
1796 original_indent_columns: vec![0, 0, 0],
1797 }),
1798 cx,
1799 );
1800
1801 pretty_assertions::assert_eq!(
1802 buffer.text(),
1803 "
1804 mod numbers {
1805 fn one() {
1806 101
1807 }
1808
1809 fn two() {
1810 102
1811 }
1812
1813 fn three() {
1814 103
1815 }
1816 }
1817 "
1818 .unindent()
1819 );
1820
1821 buffer
1822 });
1823}
1824
1825#[gpui::test]
1826fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
1827 init_settings(cx, |_| {});
1828
1829 cx.new_model(|cx| {
1830 let text = "
1831 * one
1832 - a
1833 - b
1834 * two
1835 "
1836 .unindent();
1837
1838 let mut buffer = Buffer::local(text, cx).with_language(
1839 Arc::new(Language::new(
1840 LanguageConfig {
1841 name: "Markdown".into(),
1842 auto_indent_using_last_non_empty_line: false,
1843 ..Default::default()
1844 },
1845 Some(tree_sitter_json::LANGUAGE.into()),
1846 )),
1847 cx,
1848 );
1849 buffer.edit(
1850 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1851 Some(AutoindentMode::EachLine),
1852 cx,
1853 );
1854 assert_eq!(
1855 buffer.text(),
1856 "
1857 * one
1858 - a
1859 - b
1860
1861 * two
1862 "
1863 .unindent()
1864 );
1865 buffer
1866 });
1867}
1868
1869#[gpui::test]
1870fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
1871 init_settings(cx, |settings| {
1872 settings.languages.extend([
1873 (
1874 "HTML".into(),
1875 LanguageSettingsContent {
1876 tab_size: Some(2.try_into().unwrap()),
1877 ..Default::default()
1878 },
1879 ),
1880 (
1881 "JavaScript".into(),
1882 LanguageSettingsContent {
1883 tab_size: Some(8.try_into().unwrap()),
1884 ..Default::default()
1885 },
1886 ),
1887 ])
1888 });
1889
1890 let html_language = Arc::new(html_lang());
1891
1892 let javascript_language = Arc::new(javascript_lang());
1893
1894 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1895 language_registry.add(html_language.clone());
1896 language_registry.add(javascript_language.clone());
1897
1898 cx.new_model(|cx| {
1899 let (text, ranges) = marked_text_ranges(
1900 &"
1901 <div>ˇ
1902 </div>
1903 <script>
1904 init({ˇ
1905 })
1906 </script>
1907 <span>ˇ
1908 </span>
1909 "
1910 .unindent(),
1911 false,
1912 );
1913
1914 let mut buffer = Buffer::local(text, cx);
1915 buffer.set_language_registry(language_registry);
1916 buffer.set_language(Some(html_language), cx);
1917 buffer.edit(
1918 ranges.into_iter().map(|range| (range, "\na")),
1919 Some(AutoindentMode::EachLine),
1920 cx,
1921 );
1922 assert_eq!(
1923 buffer.text(),
1924 "
1925 <div>
1926 a
1927 </div>
1928 <script>
1929 init({
1930 a
1931 })
1932 </script>
1933 <span>
1934 a
1935 </span>
1936 "
1937 .unindent()
1938 );
1939 buffer
1940 });
1941}
1942
1943#[gpui::test]
1944fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
1945 init_settings(cx, |settings| {
1946 settings.defaults.tab_size = Some(2.try_into().unwrap());
1947 });
1948
1949 cx.new_model(|cx| {
1950 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
1951
1952 let text = r#"
1953 class C
1954 def a(b, c)
1955 puts b
1956 puts c
1957 rescue
1958 puts "errored"
1959 exit 1
1960 end
1961 end
1962 "#
1963 .unindent();
1964
1965 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1966
1967 assert_eq!(
1968 buffer.text(),
1969 r#"
1970 class C
1971 def a(b, c)
1972 puts b
1973 puts c
1974 rescue
1975 puts "errored"
1976 exit 1
1977 end
1978 end
1979 "#
1980 .unindent()
1981 );
1982
1983 buffer
1984 });
1985}
1986
1987#[gpui::test]
1988async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
1989 cx.update(|cx| init_settings(cx, |_| {}));
1990
1991 // First we insert some newlines to request an auto-indent (asynchronously).
1992 // Then we request that a preview tab be preserved for the new version, even though it's edited.
1993 let buffer = cx.new_model(|cx| {
1994 let text = "fn a() {}";
1995 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1996
1997 // This causes autoindent to be async.
1998 buffer.set_sync_parse_timeout(Duration::ZERO);
1999
2000 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
2001 buffer.refresh_preview();
2002
2003 // Synchronously, we haven't auto-indented and we're still preserving the preview.
2004 assert_eq!(buffer.text(), "fn a() {\n\n}");
2005 assert!(buffer.preserve_preview());
2006 buffer
2007 });
2008
2009 // Now let the autoindent finish
2010 cx.executor().run_until_parked();
2011
2012 // The auto-indent applied, but didn't dismiss our preview
2013 buffer.update(cx, |buffer, cx| {
2014 assert_eq!(buffer.text(), "fn a() {\n \n}");
2015 assert!(buffer.preserve_preview());
2016
2017 // Edit inserting another line. It will autoindent async.
2018 // Then refresh the preview version.
2019 buffer.edit(
2020 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
2021 Some(AutoindentMode::EachLine),
2022 cx,
2023 );
2024 buffer.refresh_preview();
2025 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
2026 assert!(buffer.preserve_preview());
2027
2028 // Then perform another edit, this time without refreshing the preview version.
2029 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
2030 // This causes the preview to not be preserved.
2031 assert!(!buffer.preserve_preview());
2032 });
2033
2034 // Let the async autoindent from the first edit finish.
2035 cx.executor().run_until_parked();
2036
2037 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
2038 buffer.update(cx, |buffer, _| {
2039 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
2040 assert!(!buffer.preserve_preview());
2041 });
2042}
2043
2044#[gpui::test]
2045fn test_insert_empty_line(cx: &mut AppContext) {
2046 init_settings(cx, |_| {});
2047
2048 // Insert empty line at the beginning, requesting an empty line above
2049 cx.new_model(|cx| {
2050 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2051 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
2052 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
2053 assert_eq!(point, Point::new(0, 0));
2054 buffer
2055 });
2056
2057 // Insert empty line at the beginning, requesting an empty line above and below
2058 cx.new_model(|cx| {
2059 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2060 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
2061 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
2062 assert_eq!(point, Point::new(0, 0));
2063 buffer
2064 });
2065
2066 // Insert empty line at the start of a line, requesting empty lines above and below
2067 cx.new_model(|cx| {
2068 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2069 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
2070 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
2071 assert_eq!(point, Point::new(3, 0));
2072 buffer
2073 });
2074
2075 // Insert empty line in the middle of a line, requesting empty lines above and below
2076 cx.new_model(|cx| {
2077 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2078 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
2079 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
2080 assert_eq!(point, Point::new(3, 0));
2081 buffer
2082 });
2083
2084 // Insert empty line in the middle of a line, requesting empty line above only
2085 cx.new_model(|cx| {
2086 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2087 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
2088 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2089 assert_eq!(point, Point::new(3, 0));
2090 buffer
2091 });
2092
2093 // Insert empty line in the middle of a line, requesting empty line below only
2094 cx.new_model(|cx| {
2095 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2096 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
2097 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2098 assert_eq!(point, Point::new(2, 0));
2099 buffer
2100 });
2101
2102 // Insert empty line at the end, requesting empty lines above and below
2103 cx.new_model(|cx| {
2104 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2105 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2106 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2107 assert_eq!(point, Point::new(4, 0));
2108 buffer
2109 });
2110
2111 // Insert empty line at the end, requesting empty line above only
2112 cx.new_model(|cx| {
2113 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2114 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2115 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2116 assert_eq!(point, Point::new(4, 0));
2117 buffer
2118 });
2119
2120 // Insert empty line at the end, requesting empty line below only
2121 cx.new_model(|cx| {
2122 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2123 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2124 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2125 assert_eq!(point, Point::new(3, 0));
2126 buffer
2127 });
2128}
2129
2130#[gpui::test]
2131fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
2132 init_settings(cx, |_| {});
2133
2134 cx.new_model(|cx| {
2135 let language = Language::new(
2136 LanguageConfig {
2137 name: "JavaScript".into(),
2138 line_comments: vec!["// ".into()],
2139 brackets: BracketPairConfig {
2140 pairs: vec![
2141 BracketPair {
2142 start: "{".into(),
2143 end: "}".into(),
2144 close: true,
2145 surround: true,
2146 newline: false,
2147 },
2148 BracketPair {
2149 start: "'".into(),
2150 end: "'".into(),
2151 close: true,
2152 surround: true,
2153 newline: false,
2154 },
2155 ],
2156 disabled_scopes_by_bracket_ix: vec![
2157 Vec::new(), //
2158 vec!["string".into(), "comment".into()], // single quotes disabled
2159 ],
2160 },
2161 overrides: [(
2162 "element".into(),
2163 LanguageConfigOverride {
2164 line_comments: Override::Remove { remove: true },
2165 block_comment: Override::Set(("{/*".into(), "*/}".into())),
2166 ..Default::default()
2167 },
2168 )]
2169 .into_iter()
2170 .collect(),
2171 ..Default::default()
2172 },
2173 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2174 )
2175 .with_override_query(
2176 r#"
2177 (jsx_element) @element
2178 (string) @string
2179 (comment) @comment.inclusive
2180 [
2181 (jsx_opening_element)
2182 (jsx_closing_element)
2183 (jsx_expression)
2184 ] @default
2185 "#,
2186 )
2187 .unwrap();
2188
2189 let text = r#"
2190 a["b"] = <C d="e">
2191 <F></F>
2192 { g() }
2193 </C>; // a comment
2194 "#
2195 .unindent();
2196
2197 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2198 let snapshot = buffer.snapshot();
2199
2200 let config = snapshot.language_scope_at(0).unwrap();
2201 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2202 // Both bracket pairs are enabled
2203 assert_eq!(
2204 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2205 &[true, true]
2206 );
2207
2208 let comment_config = snapshot
2209 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2210 .unwrap();
2211 assert_eq!(
2212 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2213 &[true, false]
2214 );
2215
2216 let string_config = snapshot
2217 .language_scope_at(text.find("b\"").unwrap())
2218 .unwrap();
2219 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2220 // Second bracket pair is disabled
2221 assert_eq!(
2222 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2223 &[true, false]
2224 );
2225
2226 // In between JSX tags: use the `element` override.
2227 let element_config = snapshot
2228 .language_scope_at(text.find("<F>").unwrap())
2229 .unwrap();
2230 // TODO nested blocks after newlines are captured with all whitespaces
2231 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2232 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2233 // assert_eq!(
2234 // element_config.block_comment_delimiters(),
2235 // Some((&"{/*".into(), &"*/}".into()))
2236 // );
2237 assert_eq!(
2238 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2239 &[true, true]
2240 );
2241
2242 // Within a JSX tag: use the default config.
2243 let tag_config = snapshot
2244 .language_scope_at(text.find(" d=").unwrap() + 1)
2245 .unwrap();
2246 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2247 assert_eq!(
2248 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2249 &[true, true]
2250 );
2251
2252 // In a JSX expression: use the default config.
2253 let expression_in_element_config = snapshot
2254 .language_scope_at(text.find('{').unwrap() + 1)
2255 .unwrap();
2256 assert_eq!(
2257 expression_in_element_config.line_comment_prefixes(),
2258 &[Arc::from("// ")]
2259 );
2260 assert_eq!(
2261 expression_in_element_config
2262 .brackets()
2263 .map(|e| e.1)
2264 .collect::<Vec<_>>(),
2265 &[true, true]
2266 );
2267
2268 buffer
2269 });
2270}
2271
2272#[gpui::test]
2273fn test_language_scope_at_with_rust(cx: &mut AppContext) {
2274 init_settings(cx, |_| {});
2275
2276 cx.new_model(|cx| {
2277 let language = Language::new(
2278 LanguageConfig {
2279 name: "Rust".into(),
2280 brackets: BracketPairConfig {
2281 pairs: vec![
2282 BracketPair {
2283 start: "{".into(),
2284 end: "}".into(),
2285 close: true,
2286 surround: true,
2287 newline: false,
2288 },
2289 BracketPair {
2290 start: "'".into(),
2291 end: "'".into(),
2292 close: true,
2293 surround: true,
2294 newline: false,
2295 },
2296 ],
2297 disabled_scopes_by_bracket_ix: vec![
2298 Vec::new(), //
2299 vec!["string".into()],
2300 ],
2301 },
2302 ..Default::default()
2303 },
2304 Some(tree_sitter_rust::LANGUAGE.into()),
2305 )
2306 .with_override_query(
2307 r#"
2308 (string_literal) @string
2309 "#,
2310 )
2311 .unwrap();
2312
2313 let text = r#"
2314 const S: &'static str = "hello";
2315 "#
2316 .unindent();
2317
2318 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2319 let snapshot = buffer.snapshot();
2320
2321 // By default, all brackets are enabled
2322 let config = snapshot.language_scope_at(0).unwrap();
2323 assert_eq!(
2324 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2325 &[true, true]
2326 );
2327
2328 // Within a string, the quotation brackets are disabled.
2329 let string_config = snapshot
2330 .language_scope_at(text.find("ello").unwrap())
2331 .unwrap();
2332 assert_eq!(
2333 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2334 &[true, false]
2335 );
2336
2337 buffer
2338 });
2339}
2340
2341#[gpui::test]
2342fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
2343 init_settings(cx, |_| {});
2344
2345 cx.new_model(|cx| {
2346 let text = r#"
2347 <ol>
2348 <% people.each do |person| %>
2349 <li>
2350 <%= person.name %>
2351 </li>
2352 <% end %>
2353 </ol>
2354 "#
2355 .unindent();
2356
2357 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2358 language_registry.add(Arc::new(ruby_lang()));
2359 language_registry.add(Arc::new(html_lang()));
2360 language_registry.add(Arc::new(erb_lang()));
2361
2362 let mut buffer = Buffer::local(text, cx);
2363 buffer.set_language_registry(language_registry.clone());
2364 buffer.set_language(
2365 language_registry
2366 .language_for_name("ERB")
2367 .now_or_never()
2368 .unwrap()
2369 .ok(),
2370 cx,
2371 );
2372
2373 let snapshot = buffer.snapshot();
2374 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2375 assert_eq!(html_config.line_comment_prefixes(), &[]);
2376 assert_eq!(
2377 html_config.block_comment_delimiters(),
2378 Some((&"<!--".into(), &"-->".into()))
2379 );
2380
2381 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2382 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2383 assert_eq!(ruby_config.block_comment_delimiters(), None);
2384
2385 buffer
2386 });
2387}
2388
2389#[gpui::test]
2390fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
2391 init_settings(cx, |_| {});
2392
2393 cx.new_model(|cx| {
2394 let text = r#"
2395 this is an *emphasized* word.
2396 "#
2397 .unindent();
2398
2399 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2400 language_registry.add(Arc::new(markdown_lang()));
2401 language_registry.add(Arc::new(markdown_inline_lang()));
2402
2403 let mut buffer = Buffer::local(text, cx);
2404 buffer.set_language_registry(language_registry.clone());
2405 buffer.set_language(
2406 language_registry
2407 .language_for_name("Markdown")
2408 .now_or_never()
2409 .unwrap()
2410 .ok(),
2411 cx,
2412 );
2413
2414 let snapshot = buffer.snapshot();
2415
2416 for point in [Point::new(0, 4), Point::new(0, 16)] {
2417 let config = snapshot.language_scope_at(point).unwrap();
2418 assert_eq!(config.language_name(), "Markdown".into());
2419
2420 let language = snapshot.language_at(point).unwrap();
2421 assert_eq!(language.name().0.as_ref(), "Markdown");
2422 }
2423
2424 buffer
2425 });
2426}
2427
2428#[gpui::test]
2429fn test_serialization(cx: &mut gpui::AppContext) {
2430 let mut now = Instant::now();
2431
2432 let buffer1 = cx.new_model(|cx| {
2433 let mut buffer = Buffer::local("abc", cx);
2434 buffer.edit([(3..3, "D")], None, cx);
2435
2436 now += Duration::from_secs(1);
2437 buffer.start_transaction_at(now);
2438 buffer.edit([(4..4, "E")], None, cx);
2439 buffer.end_transaction_at(now, cx);
2440 assert_eq!(buffer.text(), "abcDE");
2441
2442 buffer.undo(cx);
2443 assert_eq!(buffer.text(), "abcD");
2444
2445 buffer.edit([(4..4, "F")], None, cx);
2446 assert_eq!(buffer.text(), "abcDF");
2447 buffer
2448 });
2449 assert_eq!(buffer1.read(cx).text(), "abcDF");
2450
2451 let state = buffer1.read(cx).to_proto(cx);
2452 let ops = cx
2453 .background_executor()
2454 .block(buffer1.read(cx).serialize_ops(None, cx));
2455 let buffer2 = cx.new_model(|cx| {
2456 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2457 buffer.apply_ops(
2458 ops.into_iter()
2459 .map(|op| proto::deserialize_operation(op).unwrap()),
2460 cx,
2461 );
2462 buffer
2463 });
2464 assert_eq!(buffer2.read(cx).text(), "abcDF");
2465}
2466
2467#[gpui::test]
2468async fn test_find_matching_indent(cx: &mut TestAppContext) {
2469 cx.update(|cx| init_settings(cx, |_| {}));
2470
2471 async fn enclosing_indent(
2472 text: impl Into<String>,
2473 buffer_row: u32,
2474 cx: &mut TestAppContext,
2475 ) -> Option<(Range<u32>, LineIndent)> {
2476 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2477 let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
2478 snapshot.enclosing_indent(buffer_row).await
2479 }
2480
2481 assert_eq!(
2482 enclosing_indent(
2483 "
2484 fn b() {
2485 if c {
2486 let d = 2;
2487 }
2488 }"
2489 .unindent(),
2490 1,
2491 cx,
2492 )
2493 .await,
2494 Some((
2495 1..2,
2496 LineIndent {
2497 tabs: 0,
2498 spaces: 4,
2499 line_blank: false,
2500 }
2501 ))
2502 );
2503
2504 assert_eq!(
2505 enclosing_indent(
2506 "
2507 fn b() {
2508 if c {
2509 let d = 2;
2510 }
2511 }"
2512 .unindent(),
2513 2,
2514 cx,
2515 )
2516 .await,
2517 Some((
2518 1..2,
2519 LineIndent {
2520 tabs: 0,
2521 spaces: 4,
2522 line_blank: false,
2523 }
2524 ))
2525 );
2526
2527 assert_eq!(
2528 enclosing_indent(
2529 "
2530 fn b() {
2531 if c {
2532 let d = 2;
2533
2534 let e = 5;
2535 }
2536 }"
2537 .unindent(),
2538 3,
2539 cx,
2540 )
2541 .await,
2542 Some((
2543 1..4,
2544 LineIndent {
2545 tabs: 0,
2546 spaces: 4,
2547 line_blank: false,
2548 }
2549 ))
2550 );
2551}
2552
2553#[gpui::test]
2554fn test_branch_and_merge(cx: &mut TestAppContext) {
2555 cx.update(|cx| init_settings(cx, |_| {}));
2556
2557 let base = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2558
2559 // Create a remote replica of the base buffer.
2560 let base_replica = cx.new_model(|cx| {
2561 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2562 });
2563 base.update(cx, |_buffer, cx| {
2564 cx.subscribe(&base_replica, |this, _, event, cx| {
2565 if let BufferEvent::Operation {
2566 operation,
2567 is_local: true,
2568 } = event
2569 {
2570 this.apply_ops([operation.clone()], cx);
2571 }
2572 })
2573 .detach();
2574 });
2575
2576 // Create a branch, which initially has the same state as the base buffer.
2577 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2578 branch.read_with(cx, |buffer, _| {
2579 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2580 });
2581
2582 // Edits to the branch are not applied to the base.
2583 branch.update(cx, |buffer, cx| {
2584 buffer.edit(
2585 [
2586 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2587 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2588 ],
2589 None,
2590 cx,
2591 )
2592 });
2593 branch.read_with(cx, |buffer, cx| {
2594 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2595 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2596 });
2597
2598 // Convert from branch buffer ranges to the corresoponing ranges in the
2599 // base buffer.
2600 branch.read_with(cx, |buffer, cx| {
2601 assert_eq!(
2602 buffer.range_to_version(4..7, &base.read(cx).version()),
2603 4..4
2604 );
2605 assert_eq!(
2606 buffer.range_to_version(2..9, &base.read(cx).version()),
2607 2..5
2608 );
2609 });
2610
2611 // The branch buffer maintains a diff with respect to its base buffer.
2612 start_recalculating_diff(&branch, cx);
2613 cx.run_until_parked();
2614 assert_diff_hunks(
2615 &branch,
2616 cx,
2617 &[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
2618 );
2619
2620 // Edits to the base are applied to the branch.
2621 base.update(cx, |buffer, cx| {
2622 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2623 });
2624 branch.read_with(cx, |buffer, cx| {
2625 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2626 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2627 });
2628
2629 // Until the git diff recalculation is complete, the git diff references
2630 // the previous content of the base buffer, so that it stays in sync.
2631 start_recalculating_diff(&branch, cx);
2632 assert_diff_hunks(
2633 &branch,
2634 cx,
2635 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2636 );
2637 cx.run_until_parked();
2638 assert_diff_hunks(
2639 &branch,
2640 cx,
2641 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2642 );
2643
2644 // Edits to any replica of the base are applied to the branch.
2645 base_replica.update(cx, |buffer, cx| {
2646 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2647 });
2648 branch.read_with(cx, |buffer, cx| {
2649 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2650 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2651 });
2652
2653 // Merging the branch applies all of its changes to the base.
2654 branch.update(cx, |buffer, cx| {
2655 buffer.merge_into_base(Vec::new(), cx);
2656 });
2657
2658 branch.update(cx, |buffer, cx| {
2659 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2660 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2661 });
2662}
2663
2664#[gpui::test]
2665fn test_merge_into_base(cx: &mut TestAppContext) {
2666 cx.update(|cx| init_settings(cx, |_| {}));
2667
2668 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2669 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2670
2671 // Make 3 edits, merge one into the base.
2672 branch.update(cx, |branch, cx| {
2673 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2674 branch.merge_into_base(vec![5..8], cx);
2675 });
2676
2677 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2678 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2679
2680 // Undo the one already-merged edit. Merge that into the base.
2681 branch.update(cx, |branch, cx| {
2682 branch.edit([(7..9, "hi")], None, cx);
2683 branch.merge_into_base(vec![5..8], cx);
2684 });
2685 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2686
2687 // Merge an insertion into the base.
2688 branch.update(cx, |branch, cx| {
2689 branch.merge_into_base(vec![11..11], cx);
2690 });
2691
2692 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2693 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2694
2695 // Deleted the inserted text and merge that into the base.
2696 branch.update(cx, |branch, cx| {
2697 branch.edit([(11..14, "")], None, cx);
2698 branch.merge_into_base(vec![10..11], cx);
2699 });
2700
2701 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2702}
2703
2704#[gpui::test]
2705fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2706 cx.update(|cx| init_settings(cx, |_| {}));
2707
2708 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2709 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2710
2711 // Make 2 edits, merge one into the base.
2712 branch.update(cx, |branch, cx| {
2713 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2714 branch.merge_into_base(vec![7..7], cx);
2715 });
2716 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2717 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2718
2719 // Undo the merge in the base buffer.
2720 base.update(cx, |base, cx| {
2721 base.undo(cx);
2722 });
2723 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2724 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2725
2726 // Merge that operation into the base again.
2727 branch.update(cx, |branch, cx| {
2728 branch.merge_into_base(vec![7..7], cx);
2729 });
2730 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2731 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2732}
2733
2734fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
2735 buffer
2736 .update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
2737 .detach();
2738}
2739
2740#[track_caller]
2741fn assert_diff_hunks(
2742 buffer: &Model<Buffer>,
2743 cx: &mut TestAppContext,
2744 expected_hunks: &[(Range<u32>, &str, &str)],
2745) {
2746 let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
2747 (buffer.snapshot(), buffer.diff_base().unwrap().to_string())
2748 });
2749 assert_hunks(
2750 snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
2751 &snapshot,
2752 &diff_base,
2753 expected_hunks,
2754 );
2755}
2756
2757#[gpui::test(iterations = 100)]
2758fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2759 let min_peers = env::var("MIN_PEERS")
2760 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2761 .unwrap_or(1);
2762 let max_peers = env::var("MAX_PEERS")
2763 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2764 .unwrap_or(5);
2765 let operations = env::var("OPERATIONS")
2766 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2767 .unwrap_or(10);
2768
2769 let base_text_len = rng.gen_range(0..10);
2770 let base_text = RandomCharIter::new(&mut rng)
2771 .take(base_text_len)
2772 .collect::<String>();
2773 let mut replica_ids = Vec::new();
2774 let mut buffers = Vec::new();
2775 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2776 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2777
2778 for i in 0..rng.gen_range(min_peers..=max_peers) {
2779 let buffer = cx.new_model(|cx| {
2780 let state = base_buffer.read(cx).to_proto(cx);
2781 let ops = cx
2782 .background_executor()
2783 .block(base_buffer.read(cx).serialize_ops(None, cx));
2784 let mut buffer =
2785 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2786 buffer.apply_ops(
2787 ops.into_iter()
2788 .map(|op| proto::deserialize_operation(op).unwrap()),
2789 cx,
2790 );
2791 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2792 let network = network.clone();
2793 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2794 if let BufferEvent::Operation {
2795 operation,
2796 is_local: true,
2797 } = event
2798 {
2799 network.lock().broadcast(
2800 buffer.replica_id(),
2801 vec![proto::serialize_operation(operation)],
2802 );
2803 }
2804 })
2805 .detach();
2806 buffer
2807 });
2808
2809 buffers.push(buffer);
2810 replica_ids.push(i as ReplicaId);
2811 network.lock().add_peer(i as ReplicaId);
2812 log::info!("Adding initial peer with replica id {}", i);
2813 }
2814
2815 log::info!("initial text: {:?}", base_text);
2816
2817 let mut now = Instant::now();
2818 let mut mutation_count = operations;
2819 let mut next_diagnostic_id = 0;
2820 let mut active_selections = BTreeMap::default();
2821 loop {
2822 let replica_index = rng.gen_range(0..replica_ids.len());
2823 let replica_id = replica_ids[replica_index];
2824 let buffer = &mut buffers[replica_index];
2825 let mut new_buffer = None;
2826 match rng.gen_range(0..100) {
2827 0..=29 if mutation_count != 0 => {
2828 buffer.update(cx, |buffer, cx| {
2829 buffer.start_transaction_at(now);
2830 buffer.randomly_edit(&mut rng, 5, cx);
2831 buffer.end_transaction_at(now, cx);
2832 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2833 });
2834 mutation_count -= 1;
2835 }
2836 30..=39 if mutation_count != 0 => {
2837 buffer.update(cx, |buffer, cx| {
2838 if rng.gen_bool(0.2) {
2839 log::info!("peer {} clearing active selections", replica_id);
2840 active_selections.remove(&replica_id);
2841 buffer.remove_active_selections(cx);
2842 } else {
2843 let mut selections = Vec::new();
2844 for id in 0..rng.gen_range(1..=5) {
2845 let range = buffer.random_byte_range(0, &mut rng);
2846 selections.push(Selection {
2847 id,
2848 start: buffer.anchor_before(range.start),
2849 end: buffer.anchor_before(range.end),
2850 reversed: false,
2851 goal: SelectionGoal::None,
2852 });
2853 }
2854 let selections: Arc<[Selection<Anchor>]> = selections.into();
2855 log::info!(
2856 "peer {} setting active selections: {:?}",
2857 replica_id,
2858 selections
2859 );
2860 active_selections.insert(replica_id, selections.clone());
2861 buffer.set_active_selections(selections, false, Default::default(), cx);
2862 }
2863 });
2864 mutation_count -= 1;
2865 }
2866 40..=49 if mutation_count != 0 && replica_id == 0 => {
2867 let entry_count = rng.gen_range(1..=5);
2868 buffer.update(cx, |buffer, cx| {
2869 let diagnostics = DiagnosticSet::new(
2870 (0..entry_count).map(|_| {
2871 let range = buffer.random_byte_range(0, &mut rng);
2872 let range = range.to_point_utf16(buffer);
2873 let range = range.start..range.end;
2874 DiagnosticEntry {
2875 range,
2876 diagnostic: Diagnostic {
2877 message: post_inc(&mut next_diagnostic_id).to_string(),
2878 ..Default::default()
2879 },
2880 }
2881 }),
2882 buffer,
2883 );
2884 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2885 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2886 });
2887 mutation_count -= 1;
2888 }
2889 50..=59 if replica_ids.len() < max_peers => {
2890 let old_buffer_state = buffer.read(cx).to_proto(cx);
2891 let old_buffer_ops = cx
2892 .background_executor()
2893 .block(buffer.read(cx).serialize_ops(None, cx));
2894 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2895 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2896 .choose(&mut rng)
2897 .unwrap();
2898 log::info!(
2899 "Adding new replica {} (replicating from {})",
2900 new_replica_id,
2901 replica_id
2902 );
2903 new_buffer = Some(cx.new_model(|cx| {
2904 let mut new_buffer = Buffer::from_proto(
2905 new_replica_id,
2906 Capability::ReadWrite,
2907 old_buffer_state,
2908 None,
2909 )
2910 .unwrap();
2911 new_buffer.apply_ops(
2912 old_buffer_ops
2913 .into_iter()
2914 .map(|op| deserialize_operation(op).unwrap()),
2915 cx,
2916 );
2917 log::info!(
2918 "New replica {} text: {:?}",
2919 new_buffer.replica_id(),
2920 new_buffer.text()
2921 );
2922 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2923 let network = network.clone();
2924 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2925 if let BufferEvent::Operation {
2926 operation,
2927 is_local: true,
2928 } = event
2929 {
2930 network.lock().broadcast(
2931 buffer.replica_id(),
2932 vec![proto::serialize_operation(operation)],
2933 );
2934 }
2935 })
2936 .detach();
2937 new_buffer
2938 }));
2939 network.lock().replicate(replica_id, new_replica_id);
2940
2941 if new_replica_id as usize == replica_ids.len() {
2942 replica_ids.push(new_replica_id);
2943 } else {
2944 let new_buffer = new_buffer.take().unwrap();
2945 while network.lock().has_unreceived(new_replica_id) {
2946 let ops = network
2947 .lock()
2948 .receive(new_replica_id)
2949 .into_iter()
2950 .map(|op| proto::deserialize_operation(op).unwrap());
2951 if ops.len() > 0 {
2952 log::info!(
2953 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2954 new_replica_id,
2955 buffer.read(cx).version(),
2956 ops.len(),
2957 ops
2958 );
2959 new_buffer.update(cx, |new_buffer, cx| {
2960 new_buffer.apply_ops(ops, cx);
2961 });
2962 }
2963 }
2964 buffers[new_replica_id as usize] = new_buffer;
2965 }
2966 }
2967 60..=69 if mutation_count != 0 => {
2968 buffer.update(cx, |buffer, cx| {
2969 buffer.randomly_undo_redo(&mut rng, cx);
2970 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2971 });
2972 mutation_count -= 1;
2973 }
2974 _ if network.lock().has_unreceived(replica_id) => {
2975 let ops = network
2976 .lock()
2977 .receive(replica_id)
2978 .into_iter()
2979 .map(|op| proto::deserialize_operation(op).unwrap());
2980 if ops.len() > 0 {
2981 log::info!(
2982 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2983 replica_id,
2984 buffer.read(cx).version(),
2985 ops.len(),
2986 ops
2987 );
2988 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
2989 }
2990 }
2991 _ => {}
2992 }
2993
2994 now += Duration::from_millis(rng.gen_range(0..=200));
2995 buffers.extend(new_buffer);
2996
2997 for buffer in &buffers {
2998 buffer.read(cx).check_invariants();
2999 }
3000
3001 if mutation_count == 0 && network.lock().is_idle() {
3002 break;
3003 }
3004 }
3005
3006 let first_buffer = buffers[0].read(cx).snapshot();
3007 for buffer in &buffers[1..] {
3008 let buffer = buffer.read(cx).snapshot();
3009 assert_eq!(
3010 buffer.version(),
3011 first_buffer.version(),
3012 "Replica {} version != Replica 0 version",
3013 buffer.replica_id()
3014 );
3015 assert_eq!(
3016 buffer.text(),
3017 first_buffer.text(),
3018 "Replica {} text != Replica 0 text",
3019 buffer.replica_id()
3020 );
3021 assert_eq!(
3022 buffer
3023 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3024 .collect::<Vec<_>>(),
3025 first_buffer
3026 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3027 .collect::<Vec<_>>(),
3028 "Replica {} diagnostics != Replica 0 diagnostics",
3029 buffer.replica_id()
3030 );
3031 }
3032
3033 for buffer in &buffers {
3034 let buffer = buffer.read(cx).snapshot();
3035 let actual_remote_selections = buffer
3036 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3037 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3038 .collect::<Vec<_>>();
3039 let expected_remote_selections = active_selections
3040 .iter()
3041 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3042 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3043 .collect::<Vec<_>>();
3044 assert_eq!(
3045 actual_remote_selections,
3046 expected_remote_selections,
3047 "Replica {} remote selections != expected selections",
3048 buffer.replica_id()
3049 );
3050 }
3051}
3052
3053#[test]
3054fn test_contiguous_ranges() {
3055 assert_eq!(
3056 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3057 &[1..4, 5..7, 9..13]
3058 );
3059
3060 // Respects the `max_len` parameter
3061 assert_eq!(
3062 contiguous_ranges(
3063 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3064 3
3065 )
3066 .collect::<Vec<_>>(),
3067 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3068 );
3069}
3070
3071#[gpui::test(iterations = 500)]
3072fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3073 // Generate a random multi-line string containing
3074 // some lines with trailing whitespace.
3075 let mut text = String::new();
3076 for _ in 0..rng.gen_range(0..16) {
3077 for _ in 0..rng.gen_range(0..36) {
3078 text.push(match rng.gen_range(0..10) {
3079 0..=1 => ' ',
3080 3 => '\t',
3081 _ => rng.gen_range('a'..='z'),
3082 });
3083 }
3084 text.push('\n');
3085 }
3086
3087 match rng.gen_range(0..10) {
3088 // sometimes remove the last newline
3089 0..=1 => drop(text.pop()), //
3090
3091 // sometimes add extra newlines
3092 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
3093 _ => {}
3094 }
3095
3096 let rope = Rope::from(text.as_str());
3097 let actual_ranges = trailing_whitespace_ranges(&rope);
3098 let expected_ranges = TRAILING_WHITESPACE_REGEX
3099 .find_iter(&text)
3100 .map(|m| m.range())
3101 .collect::<Vec<_>>();
3102 assert_eq!(
3103 actual_ranges,
3104 expected_ranges,
3105 "wrong ranges for text lines:\n{:?}",
3106 text.split('\n').collect::<Vec<_>>()
3107 );
3108}
3109
3110fn ruby_lang() -> Language {
3111 Language::new(
3112 LanguageConfig {
3113 name: "Ruby".into(),
3114 matcher: LanguageMatcher {
3115 path_suffixes: vec!["rb".to_string()],
3116 ..Default::default()
3117 },
3118 line_comments: vec!["# ".into()],
3119 ..Default::default()
3120 },
3121 Some(tree_sitter_ruby::LANGUAGE.into()),
3122 )
3123 .with_indents_query(
3124 r#"
3125 (class "end" @end) @indent
3126 (method "end" @end) @indent
3127 (rescue) @outdent
3128 (then) @indent
3129 "#,
3130 )
3131 .unwrap()
3132}
3133
3134fn html_lang() -> Language {
3135 Language::new(
3136 LanguageConfig {
3137 name: LanguageName::new("HTML"),
3138 block_comment: Some(("<!--".into(), "-->".into())),
3139 ..Default::default()
3140 },
3141 Some(tree_sitter_html::language()),
3142 )
3143 .with_indents_query(
3144 "
3145 (element
3146 (start_tag) @start
3147 (end_tag)? @end) @indent
3148 ",
3149 )
3150 .unwrap()
3151 .with_injection_query(
3152 r#"
3153 (script_element
3154 (raw_text) @content
3155 (#set! "language" "javascript"))
3156 "#,
3157 )
3158 .unwrap()
3159}
3160
3161fn erb_lang() -> Language {
3162 Language::new(
3163 LanguageConfig {
3164 name: "ERB".into(),
3165 matcher: LanguageMatcher {
3166 path_suffixes: vec!["erb".to_string()],
3167 ..Default::default()
3168 },
3169 block_comment: Some(("<%#".into(), "%>".into())),
3170 ..Default::default()
3171 },
3172 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3173 )
3174 .with_injection_query(
3175 r#"
3176 (
3177 (code) @content
3178 (#set! "language" "ruby")
3179 (#set! "combined")
3180 )
3181
3182 (
3183 (content) @content
3184 (#set! "language" "html")
3185 (#set! "combined")
3186 )
3187 "#,
3188 )
3189 .unwrap()
3190}
3191
3192fn rust_lang() -> Language {
3193 Language::new(
3194 LanguageConfig {
3195 name: "Rust".into(),
3196 matcher: LanguageMatcher {
3197 path_suffixes: vec!["rs".to_string()],
3198 ..Default::default()
3199 },
3200 ..Default::default()
3201 },
3202 Some(tree_sitter_rust::LANGUAGE.into()),
3203 )
3204 .with_indents_query(
3205 r#"
3206 (call_expression) @indent
3207 (field_expression) @indent
3208 (_ "(" ")" @end) @indent
3209 (_ "{" "}" @end) @indent
3210 "#,
3211 )
3212 .unwrap()
3213 .with_brackets_query(
3214 r#"
3215 ("{" @open "}" @close)
3216 "#,
3217 )
3218 .unwrap()
3219 .with_text_object_query(
3220 r#"
3221 (function_item
3222 body: (_
3223 "{"
3224 (_)* @function.inside
3225 "}" )) @function.around
3226
3227 (line_comment)+ @comment.around
3228
3229 (block_comment) @comment.around
3230 "#,
3231 )
3232 .unwrap()
3233 .with_outline_query(
3234 r#"
3235 (line_comment) @annotation
3236
3237 (struct_item
3238 "struct" @context
3239 name: (_) @name) @item
3240 (enum_item
3241 "enum" @context
3242 name: (_) @name) @item
3243 (enum_variant
3244 name: (_) @name) @item
3245 (field_declaration
3246 name: (_) @name) @item
3247 (impl_item
3248 "impl" @context
3249 trait: (_)? @name
3250 "for"? @context
3251 type: (_) @name
3252 body: (_ "{" (_)* "}")) @item
3253 (function_item
3254 "fn" @context
3255 name: (_) @name) @item
3256 (mod_item
3257 "mod" @context
3258 name: (_) @name) @item
3259 "#,
3260 )
3261 .unwrap()
3262}
3263
3264fn json_lang() -> Language {
3265 Language::new(
3266 LanguageConfig {
3267 name: "Json".into(),
3268 matcher: LanguageMatcher {
3269 path_suffixes: vec!["js".to_string()],
3270 ..Default::default()
3271 },
3272 ..Default::default()
3273 },
3274 Some(tree_sitter_json::LANGUAGE.into()),
3275 )
3276}
3277
3278fn javascript_lang() -> Language {
3279 Language::new(
3280 LanguageConfig {
3281 name: "JavaScript".into(),
3282 ..Default::default()
3283 },
3284 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3285 )
3286 .with_brackets_query(
3287 r#"
3288 ("{" @open "}" @close)
3289 ("(" @open ")" @close)
3290 "#,
3291 )
3292 .unwrap()
3293 .with_indents_query(
3294 r#"
3295 (object "}" @end) @indent
3296 "#,
3297 )
3298 .unwrap()
3299}
3300
3301pub fn markdown_lang() -> Language {
3302 Language::new(
3303 LanguageConfig {
3304 name: "Markdown".into(),
3305 matcher: LanguageMatcher {
3306 path_suffixes: vec!["md".into()],
3307 ..Default::default()
3308 },
3309 ..Default::default()
3310 },
3311 Some(tree_sitter_md::LANGUAGE.into()),
3312 )
3313 .with_injection_query(
3314 r#"
3315 (fenced_code_block
3316 (info_string
3317 (language) @language)
3318 (code_fence_content) @content)
3319
3320 ((inline) @content
3321 (#set! "language" "markdown-inline"))
3322 "#,
3323 )
3324 .unwrap()
3325}
3326
3327pub fn markdown_inline_lang() -> Language {
3328 Language::new(
3329 LanguageConfig {
3330 name: "Markdown-Inline".into(),
3331 hidden: true,
3332 ..LanguageConfig::default()
3333 },
3334 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3335 )
3336 .with_highlights_query("(emphasis) @emphasis")
3337 .unwrap()
3338}
3339
3340fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3341 buffer.update(cx, |buffer, _| {
3342 let snapshot = buffer.snapshot();
3343 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3344 layers[0].node().to_sexp()
3345 })
3346}
3347
3348// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3349fn assert_bracket_pairs(
3350 selection_text: &'static str,
3351 bracket_pair_texts: Vec<&'static str>,
3352 language: Language,
3353 cx: &mut AppContext,
3354) {
3355 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3356 let buffer = cx.new_model(|cx| {
3357 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
3358 });
3359 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3360
3361 let selection_range = selection_ranges[0].clone();
3362
3363 let bracket_pairs = bracket_pair_texts
3364 .into_iter()
3365 .map(|pair_text| {
3366 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3367 assert_eq!(bracket_text, expected_text);
3368 (ranges[0].clone(), ranges[1].clone())
3369 })
3370 .collect::<Vec<_>>();
3371
3372 assert_set_eq!(
3373 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
3374 bracket_pairs
3375 );
3376}
3377
3378fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
3379 let settings_store = SettingsStore::test(cx);
3380 cx.set_global(settings_store);
3381 crate::init(cx);
3382 cx.update_global::<SettingsStore, _>(|settings, cx| {
3383 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3384 });
3385}