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