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