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