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