Skip to repository content1643 lines · 47.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:53:13.791Z 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
syntax_map_tests.rs
1use super::*;
2use crate::{
3 LanguageConfig, LanguageMatcher, LanguageQueries, buffer_tests::markdown_inline_lang,
4 markdown_lang, rust_lang,
5};
6use gpui::App;
7use pretty_assertions::assert_eq;
8use rand::rngs::StdRng;
9use std::borrow::Cow;
10use std::{env, ops::Range, sync::Arc};
11use text::{Buffer, BufferId, ReplicaId};
12use tree_sitter::Node;
13use unindent::Unindent as _;
14use util::test::marked_text_ranges;
15
16#[test]
17fn test_splice_included_ranges() {
18 let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)];
19
20 let (new_ranges, change) = splice_included_ranges(
21 ranges.clone(),
22 &[54..56, 58..68],
23 &[ts_range(50..54), ts_range(59..67)],
24 );
25 assert_eq!(
26 new_ranges,
27 &[
28 ts_range(20..30),
29 ts_range(50..54),
30 ts_range(59..67),
31 ts_range(80..90),
32 ]
33 );
34 assert_eq!(change, 1..3);
35
36 let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]);
37 assert_eq!(
38 new_ranges,
39 &[ts_range(20..30), ts_range(50..60), ts_range(80..90)]
40 );
41 assert_eq!(change, 2..3);
42
43 let (new_ranges, change) =
44 splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]);
45 assert_eq!(
46 new_ranges,
47 &[
48 ts_range(0..2),
49 ts_range(20..30),
50 ts_range(50..60),
51 ts_range(70..75),
52 ts_range(80..90)
53 ]
54 );
55 assert_eq!(change, 0..4);
56
57 let (new_ranges, change) =
58 splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
59 assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
60 assert_eq!(change, 0..1);
61
62 // does not create overlapping ranges
63 let (new_ranges, change) = splice_included_ranges(ranges, &[0..18], &[ts_range(20..32)]);
64 assert_eq!(
65 new_ranges,
66 &[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
67 );
68 assert_eq!(change, 0..1);
69
70 fn ts_range(range: Range<usize>) -> tree_sitter::Range {
71 tree_sitter::Range {
72 start_byte: range.start,
73 start_point: tree_sitter::Point {
74 row: 0,
75 column: range.start,
76 },
77 end_byte: range.end,
78 end_point: tree_sitter::Point {
79 row: 0,
80 column: range.end,
81 },
82 }
83 }
84}
85
86#[gpui::test]
87fn test_syntax_map_layers_for_range(cx: &mut App) {
88 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
89 let language = rust_lang();
90 registry.add(language.clone());
91
92 let mut buffer = Buffer::new(
93 ReplicaId::LOCAL,
94 BufferId::new(1).unwrap(),
95 r#"
96 fn a() {
97 assert_eq!(
98 b(vec![C {}]),
99 vec![d.e],
100 );
101 println!("{}", f(|_| true));
102 }
103 "#
104 .unindent(),
105 );
106
107 let mut syntax_map = SyntaxMap::new(&buffer);
108 syntax_map.set_language_registry(registry);
109 syntax_map.reparse(language.clone(), &buffer);
110
111 assert_layers_for_range(
112 &syntax_map,
113 &buffer,
114 Point::new(2, 0)..Point::new(2, 0),
115 &[
116 "...(function_item ... (block (expression_statement (macro_invocation...",
117 "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
118 ],
119 );
120 assert_layers_for_range(
121 &syntax_map,
122 &buffer,
123 Point::new(2, 14)..Point::new(2, 16),
124 &[
125 "...(function_item ...",
126 "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
127 "...(array_expression (struct_expression ...",
128 ],
129 );
130 assert_layers_for_range(
131 &syntax_map,
132 &buffer,
133 Point::new(3, 14)..Point::new(3, 16),
134 &[
135 "...(function_item ...",
136 "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
137 "...(array_expression (field_expression ...",
138 ],
139 );
140 assert_layers_for_range(
141 &syntax_map,
142 &buffer,
143 Point::new(5, 12)..Point::new(5, 16),
144 &[
145 "...(function_item ...",
146 "...(call_expression ... (arguments (closure_expression ...",
147 ],
148 );
149
150 // Replace a vec! macro invocation with a plain slice, removing a syntactic layer.
151 let macro_name_range = range_for_text(&buffer, "vec!");
152 buffer.edit([(macro_name_range, "&")]);
153 syntax_map.interpolate(&buffer);
154 syntax_map.reparse(language.clone(), &buffer);
155
156 assert_layers_for_range(
157 &syntax_map,
158 &buffer,
159 Point::new(2, 14)..Point::new(2, 16),
160 &[
161 "...(function_item ...",
162 "...(tuple_expression (call_expression ... arguments: (arguments (reference_expression value: (array_expression...",
163 ],
164 );
165
166 // Put the vec! macro back, adding back the syntactic layer.
167 buffer.undo();
168 syntax_map.interpolate(&buffer);
169 syntax_map.reparse(language, &buffer);
170
171 assert_layers_for_range(
172 &syntax_map,
173 &buffer,
174 Point::new(2, 14)..Point::new(2, 16),
175 &[
176 "...(function_item ...",
177 "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...",
178 "...(array_expression (struct_expression ...",
179 ],
180 );
181}
182
183#[gpui::test]
184fn test_syntax_map_languages_match_layers_for_range(cx: &mut App) {
185 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
186 let markdown = markdown_lang();
187 let markdown_inline = Arc::new(markdown_inline_lang());
188 registry.add(markdown.clone());
189 registry.add(markdown_inline);
190 registry.add(rust_lang());
191
192 let buffer = Buffer::new(
193 ReplicaId::LOCAL,
194 BufferId::new(1).unwrap(),
195 r#"
196 This is `inline`.
197
198 ```rs
199 fn a() {}
200 ```
201 "#
202 .unindent(),
203 );
204
205 let mut syntax_map = SyntaxMap::new(&buffer);
206 syntax_map.set_language_registry(registry);
207 syntax_map.reparse(markdown, &buffer);
208
209 let all_language_names = syntax_map
210 .languages(&buffer, true)
211 .map(|language| language.name().to_string())
212 .collect::<Vec<_>>();
213 let all_layer_language_names = syntax_map
214 .layers_for_range(0..buffer.len(), &buffer, true)
215 .map(|layer| layer.language.name().to_string())
216 .collect::<Vec<_>>();
217
218 assert_eq!(all_language_names, all_layer_language_names);
219 assert!(
220 all_language_names
221 .iter()
222 .any(|language_name| language_name == "Markdown-Inline"),
223 "expected hidden languages to be included when include_hidden is true"
224 );
225 assert!(
226 all_language_names
227 .iter()
228 .any(|language_name| language_name == "Markdown")
229 );
230 assert!(
231 all_language_names
232 .iter()
233 .any(|language_name| language_name == "Rust")
234 );
235
236 let visible_language_names = syntax_map
237 .languages(&buffer, false)
238 .map(|language| language.name().to_string())
239 .collect::<Vec<_>>();
240 let visible_layer_language_names = syntax_map
241 .layers_for_range(0..buffer.len(), &buffer, false)
242 .map(|layer| layer.language.name().to_string())
243 .collect::<Vec<_>>();
244
245 assert_eq!(visible_language_names, visible_layer_language_names);
246 assert!(
247 !visible_language_names
248 .iter()
249 .any(|language_name| language_name == "Markdown-Inline"),
250 "expected hidden languages to be excluded when include_hidden is false"
251 );
252 assert!(
253 visible_language_names
254 .iter()
255 .any(|language_name| language_name == "Markdown")
256 );
257 assert!(
258 visible_language_names
259 .iter()
260 .any(|language_name| language_name == "Rust")
261 );
262}
263
264#[gpui::test]
265fn test_dynamic_language_injection(cx: &mut App) {
266 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
267 let markdown = markdown_lang();
268 let markdown_inline = Arc::new(markdown_inline_lang());
269 registry.add(markdown.clone());
270 registry.add(markdown_inline.clone());
271 registry.add(rust_lang());
272 registry.add(Arc::new(ruby_lang()));
273
274 let mut buffer = Buffer::new(
275 ReplicaId::LOCAL,
276 BufferId::new(1).unwrap(),
277 r#"
278 This is a code block:
279
280 ```rs
281 fn foo() {}
282 ```
283 "#
284 .unindent(),
285 );
286
287 let mut syntax_map = SyntaxMap::new(&buffer);
288 syntax_map.set_language_registry(registry.clone());
289 syntax_map.reparse(markdown.clone(), &buffer);
290 syntax_map.reparse(markdown_inline.clone(), &buffer);
291 assert_layers_for_range(
292 &syntax_map,
293 &buffer,
294 Point::new(3, 0)..Point::new(3, 0),
295 &[
296 "(document (section (paragraph (inline)) (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter))))",
297 "(inline (code_span (code_span_delimiter) (code_span_delimiter)))",
298 "...(function_item name: (identifier) parameters: (parameters) body: (block)...",
299 ],
300 );
301
302 // Replace `rs` with a path to ending in `.rb` in code block.
303 let macro_name_range = range_for_text(&buffer, "rs");
304 buffer.edit([(macro_name_range, "foo/bar/baz.rb")]);
305 syntax_map.interpolate(&buffer);
306 syntax_map.reparse(markdown.clone(), &buffer);
307 syntax_map.reparse(markdown_inline.clone(), &buffer);
308 assert_layers_for_range(
309 &syntax_map,
310 &buffer,
311 Point::new(3, 0)..Point::new(3, 0),
312 &[
313 "(document (section (paragraph (inline)) (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter))))",
314 "(inline (code_span (code_span_delimiter) (code_span_delimiter)))",
315 "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...",
316 ],
317 );
318
319 // Replace Ruby with a language that hasn't been loaded yet.
320 let macro_name_range = range_for_text(&buffer, "foo/bar/baz.rb");
321 buffer.edit([(macro_name_range, "html")]);
322 syntax_map.interpolate(&buffer);
323 syntax_map.reparse(markdown.clone(), &buffer);
324 syntax_map.reparse(markdown_inline.clone(), &buffer);
325 assert_layers_for_range(
326 &syntax_map,
327 &buffer,
328 Point::new(3, 0)..Point::new(3, 0),
329 &[
330 "(document (section (paragraph (inline)) (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter))))",
331 "(inline (code_span (code_span_delimiter) (code_span_delimiter)))",
332 ],
333 );
334 assert!(syntax_map.contains_unknown_injections());
335
336 registry.add(Arc::new(html_lang()));
337 syntax_map.reparse(markdown, &buffer);
338 syntax_map.reparse(markdown_inline, &buffer);
339 assert_layers_for_range(
340 &syntax_map,
341 &buffer,
342 Point::new(3, 0)..Point::new(3, 0),
343 &[
344 "(document (section (paragraph (inline)) (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter))))",
345 "(inline (code_span (code_span_delimiter) (code_span_delimiter)))",
346 "(document (text))",
347 ],
348 );
349 assert!(!syntax_map.contains_unknown_injections());
350}
351
352#[gpui::test]
353fn test_rust_json_macro_empty_string_highlighting(cx: &mut App) {
354 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
355 let language = rust_lang();
356 registry.add(language.clone());
357
358 let buffer = Buffer::new(
359 ReplicaId::LOCAL,
360 BufferId::new(1).unwrap(),
361 r#"
362 serde_json::json!({
363 "email": "",
364 "password": "password123",
365 "requires2FA": false
366 })
367 "#
368 .unindent(),
369 );
370
371 let mut syntax_map = SyntaxMap::new(&buffer);
372 syntax_map.set_language_registry(registry);
373 syntax_map.reparse(language, &buffer);
374
375 assert_capture_ranges(
376 &syntax_map,
377 &buffer,
378 &["string"],
379 r#"
380 serde_json::json!({
381 «"email"»: «""»,
382 «"password"»: «"password123"»,
383 «"requires2FA"»: false
384 })
385 "#,
386 );
387
388 assert_capture_ranges(
389 &syntax_map,
390 &buffer,
391 &["boolean"],
392 r#"
393 serde_json::json!({
394 "email": "",
395 "password": "password123",
396 "requires2FA": «false»
397 })
398 "#,
399 );
400}
401
402#[gpui::test]
403fn test_typing_multiple_new_injections(cx: &mut App) {
404 let (buffer, syntax_map) = test_edit_sequence(
405 "Rust",
406 &[
407 "fn a() { test_macro }",
408 "fn a() { test_macro«!» }",
409 "fn a() { test_macro!«()» }",
410 "fn a() { test_macro!(«b») }",
411 "fn a() { test_macro!(b«.») }",
412 "fn a() { test_macro!(b.«c») }",
413 "fn a() { test_macro!(b.c«()») }",
414 "fn a() { test_macro!(b.c(«vec»)) }",
415 "fn a() { test_macro!(b.c(vec«!»)) }",
416 "fn a() { test_macro!(b.c(vec!«[]»)) }",
417 "fn a() { test_macro!(b.c(vec![«d»])) }",
418 "fn a() { test_macro!(b.c(vec![d«.»])) }",
419 "fn a() { test_macro!(b.c(vec![d.«e»])) }",
420 ],
421 cx,
422 );
423
424 assert_capture_ranges(
425 &syntax_map,
426 &buffer,
427 &["property"],
428 "fn a() { test_macro!(b.«c»(vec![d.«e»])) }",
429 );
430}
431
432#[gpui::test]
433fn test_pasting_new_injection_line_between_others(cx: &mut App) {
434 let (buffer, syntax_map) = test_edit_sequence(
435 "Rust",
436 &[
437 "
438 fn a() {
439 b!(B {});
440 c!(C {});
441 d!(D {});
442 e!(E {});
443 f!(F {});
444 g!(G {});
445 }
446 ",
447 "
448 fn a() {
449 b!(B {});
450 c!(C {});
451 d!(D {});
452 « h!(H {});
453 » e!(E {});
454 f!(F {});
455 g!(G {});
456 }
457 ",
458 ],
459 cx,
460 );
461
462 assert_capture_ranges(
463 &syntax_map,
464 &buffer,
465 &["type"],
466 "
467 fn a() {
468 b!(«B» {});
469 c!(«C» {});
470 d!(«D» {});
471 h!(«H» {});
472 e!(«E» {});
473 f!(«F» {});
474 g!(«G» {});
475 }
476 ",
477 );
478}
479
480#[gpui::test]
481fn test_joining_injections_with_child_injections(cx: &mut App) {
482 let (buffer, syntax_map) = test_edit_sequence(
483 "Rust",
484 &[
485 "
486 fn a() {
487 b!(
488 c![one.two.three],
489 d![four.five.six],
490 );
491 e!(
492 f![seven.eight],
493 );
494 }
495 ",
496 "
497 fn a() {
498 b!(
499 c![one.two.three],
500 d![four.five.six],
501 ˇ f![seven.eight],
502 );
503 }
504 ",
505 ],
506 cx,
507 );
508
509 assert_capture_ranges(
510 &syntax_map,
511 &buffer,
512 &["property"],
513 "
514 fn a() {
515 b!(
516 c![one.«two».«three»],
517 d![four.«five».«six»],
518 f![seven.«eight»],
519 );
520 }
521 ",
522 );
523}
524
525#[gpui::test]
526fn test_editing_edges_of_injection(cx: &mut App) {
527 test_edit_sequence(
528 "Rust",
529 &[
530 "
531 fn a() {
532 b!(c!())
533 }
534 ",
535 "
536 fn a() {
537 «d»!(c!())
538 }
539 ",
540 "
541 fn a() {
542 «e»d!(c!())
543 }
544 ",
545 "
546 fn a() {
547 ed!«[»c!()«]»
548 }
549 ",
550 ],
551 cx,
552 );
553}
554
555#[gpui::test]
556fn test_edits_preceding_and_intersecting_injection(cx: &mut App) {
557 test_edit_sequence(
558 "Rust",
559 &[
560 //
561 "const aaaaaaaaaaaa: B = c!(d(e.f));",
562 "const aˇa: B = c!(d(eˇ));",
563 ],
564 cx,
565 );
566}
567
568#[gpui::test]
569fn test_non_local_changes_create_injections(cx: &mut App) {
570 test_edit_sequence(
571 "Rust",
572 &[
573 "
574 // a! {
575 static B: C = d;
576 // }
577 ",
578 "
579 ˇa! {
580 static B: C = d;
581 ˇ}
582 ",
583 ],
584 cx,
585 );
586}
587
588#[gpui::test]
589fn test_creating_many_injections_in_one_edit(cx: &mut App) {
590 test_edit_sequence(
591 "Rust",
592 &[
593 "
594 fn a() {
595 one(Two::three(3));
596 four(Five::six(6));
597 seven(Eight::nine(9));
598 }
599 ",
600 "
601 fn a() {
602 one«!»(Two::three(3));
603 four«!»(Five::six(6));
604 seven«!»(Eight::nine(9));
605 }
606 ",
607 "
608 fn a() {
609 one!(Two::three«!»(3));
610 four!(Five::six«!»(6));
611 seven!(Eight::nine«!»(9));
612 }
613 ",
614 ],
615 cx,
616 );
617}
618
619#[gpui::test]
620fn test_editing_across_injection_boundary(cx: &mut App) {
621 test_edit_sequence(
622 "Rust",
623 &[
624 "
625 fn one() {
626 two();
627 three!(
628 three.four,
629 five.six,
630 );
631 }
632 ",
633 "
634 fn one() {
635 two();
636 th«irty_five![»
637 three.four,
638 five.six,
639 « seven.eight,
640 ];»
641 }
642 ",
643 ],
644 cx,
645 );
646}
647
648#[gpui::test]
649fn test_removing_injection_by_replacing_across_boundary(cx: &mut App) {
650 test_edit_sequence(
651 "Rust",
652 &[
653 "
654 fn one() {
655 two!(
656 three.four,
657 );
658 }
659 ",
660 "
661 fn one() {
662 t«en
663 .eleven(
664 twelve,
665 »
666 three.four,
667 );
668 }
669 ",
670 ],
671 cx,
672 );
673}
674
675#[gpui::test]
676fn test_combined_injections_simple(cx: &mut App) {
677 let (buffer, syntax_map) = test_edit_sequence(
678 "ERB",
679 &[
680 "
681 <body>
682 <% if @one %>
683 <div class=one>
684 <% else %>
685 <div class=two>
686 <% end %>
687 </div>
688 </body>
689 ",
690 "
691 <body>
692 <% if @one %>
693 <div class=one>
694 ˇ else ˇ
695 <div class=two>
696 <% end %>
697 </div>
698 </body>
699 ",
700 "
701 <body>
702 <% if @one «;» end %>
703 </div>
704 </body>
705 ",
706 ],
707 cx,
708 );
709
710 assert_capture_ranges(
711 &syntax_map,
712 &buffer,
713 &["tag", "ivar"],
714 "
715 <«body»>
716 <% if «@one» ; end %>
717 </«div»>
718 </«body»>
719 ",
720 );
721}
722
723#[gpui::test]
724fn test_combined_injections_empty_ranges(cx: &mut App) {
725 test_edit_sequence(
726 "ERB",
727 &[
728 "
729 <% if @one %>
730 <% else %>
731 <% end %>
732 ",
733 "
734 <% if @one %>
735 ˇ<% end %>
736 ",
737 ],
738 cx,
739 );
740}
741
742#[gpui::test]
743fn test_combined_injections_edit_edges_of_ranges(cx: &mut App) {
744 let (buffer, syntax_map) = test_edit_sequence(
745 "ERB",
746 &[
747 "
748 <%= one @two %>
749 <%= three @four %>
750 ",
751 "
752 <%= one @two %ˇ
753 <%= three @four %>
754 ",
755 "
756 <%= one @two %«>»
757 <%= three @four %>
758 ",
759 ],
760 cx,
761 );
762
763 assert_capture_ranges(
764 &syntax_map,
765 &buffer,
766 &["tag", "ivar"],
767 "
768 <%= one «@two» %>
769 <%= three «@four» %>
770 ",
771 );
772}
773
774#[gpui::test]
775fn test_combined_injections_splitting_some_injections(cx: &mut App) {
776 let (_buffer, _syntax_map) = test_edit_sequence(
777 "ERB",
778 &[
779 r#"
780 <%A if b(:c) %>
781 d
782 <% end %>
783 eee
784 <% f %>
785 "#,
786 r#"
787 <%« AAAAAAA %>
788 hhhhhhh
789 <%=» if b(:c) %>
790 d
791 <% end %>
792 eee
793 <% f %>
794 "#,
795 ],
796 cx,
797 );
798}
799
800#[gpui::test]
801fn test_combined_injections_editing_after_last_injection(cx: &mut App) {
802 test_edit_sequence(
803 "ERB",
804 &[
805 r#"
806 <% foo %>
807 <div></div>
808 <% bar %>
809 "#,
810 r#"
811 <% foo %>
812 <div></div>
813 <% bar %>«
814 more text»
815 "#,
816 ],
817 cx,
818 );
819}
820
821#[gpui::test]
822fn test_combined_injections_inside_injections(cx: &mut App) {
823 let (buffer, syntax_map) = test_edit_sequence(
824 "Markdown",
825 &[
826 r#"
827 here is
828 some
829 ERB code:
830
831 ```erb
832 <ul>
833 <% people.each do |person| %>
834 <li><%= person.name %></li>
835 <li><%= person.age %></li>
836 <% end %>
837 </ul>
838 ```
839 "#,
840 r#"
841 here is
842 some
843 ERB code:
844
845 ```erb
846 <ul>
847 <% people«2».each do |person| %>
848 <li><%= person.name %></li>
849 <li><%= person.age %></li>
850 <% end %>
851 </ul>
852 ```
853 "#,
854 // Inserting a comment character inside one code directive
855 // does not cause the other code directive to become a comment,
856 // because newlines are included in between each injection range.
857 r#"
858 here is
859 some
860 ERB code:
861
862 ```erb
863 <ul>
864 <% people2.each do |person| %>
865 <li><%= «# »person.name %></li>
866 <li><%= person.age %></li>
867 <% end %>
868 </ul>
869 ```
870 "#,
871 ],
872 cx,
873 );
874
875 // Check that the code directive below the ruby comment is
876 // not parsed as a comment.
877 assert_capture_ranges(
878 &syntax_map,
879 &buffer,
880 &["method"],
881 "
882 here is
883 some
884 ERB code:
885
886 ```erb
887 <ul>
888 <% people2.«each» do |person| %>
889 <li><%= # person.name %></li>
890 <li><%= person.«age» %></li>
891 <% end %>
892 </ul>
893 ```
894 ",
895 );
896}
897
898#[gpui::test]
899fn test_empty_combined_injections_inside_injections(cx: &mut App) {
900 let (buffer, syntax_map) = test_edit_sequence(
901 "Markdown",
902 &[r#"
903 ```erb
904 hello
905 ```
906
907 goodbye
908 "#],
909 cx,
910 );
911
912 assert_layers_for_range(
913 &syntax_map,
914 &buffer,
915 Point::new(0, 0)..Point::new(5, 0),
916 &[
917 // Markdown document
918 "(document (section (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter)) (paragraph (inline))))",
919 // ERB template in the code block
920 "(template...",
921 // Markdown inline content
922 "(inline)",
923 // The ruby syntax tree should be empty, since there are
924 // no interpolations in the ERB template.
925 "(program)",
926 // HTML within the ERB
927 "(document (text))",
928 ],
929 );
930}
931
932#[gpui::test]
933fn test_comment_triggered_injection_toggle(cx: &mut App) {
934 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
935
936 let python = Arc::new(python_lang());
937 let comment = Arc::new(comment_lang());
938 registry.add(python.clone());
939 registry.add(comment);
940 // Note: SQL is an extension language (not built-in as of v0.222.0), so we can use
941 // contains_unknown_injections() to detect when the injection is triggered.
942 // We register a mock "comment" language because Python injects all comments as
943 // language "comment", and we only want SQL to trigger unknown injections.
944
945 // Start with Python code with incomplete #sq comment (not enough to trigger injection)
946 let mut buffer = Buffer::new(
947 ReplicaId::LOCAL,
948 BufferId::new(1).unwrap(),
949 "#sq\ncmd = \"SELECT col1, col2 FROM tbl\"".to_string(),
950 );
951
952 let mut syntax_map = SyntaxMap::new(&buffer);
953 syntax_map.set_language_registry(registry);
954 syntax_map.reparse(python.clone(), &buffer);
955
956 // Should have no unknown injections (#sq doesn't match the injection pattern)
957 assert!(
958 !syntax_map.contains_unknown_injections(),
959 "Expected no unknown injections with incomplete #sq comment"
960 );
961
962 // Complete the comment by adding 'l' to make #sql
963 let sq_end = buffer.as_rope().to_string().find('\n').unwrap();
964 buffer.edit([(sq_end..sq_end, "l")]);
965 syntax_map.interpolate(&buffer);
966 syntax_map.reparse(python.clone(), &buffer);
967
968 // Should now have unknown injections (SQL injection triggered but SQL not registered)
969 assert!(
970 syntax_map.contains_unknown_injections(),
971 "Expected unknown injections after completing #sql comment"
972 );
973
974 // Remove the 'l' to go back to #sq
975 let l_position = buffer.as_rope().to_string().find("l\n").unwrap();
976 buffer.edit([(l_position..l_position + 1, "")]);
977 syntax_map.interpolate(&buffer);
978 syntax_map.reparse(python, &buffer);
979
980 // Should have no unknown injections again - SQL injection should be invalidated
981 assert!(
982 !syntax_map.contains_unknown_injections(),
983 "Expected no unknown injections after removing 'l' from #sql comment"
984 );
985}
986
987#[gpui::test]
988fn test_syntax_map_languages_loading_with_erb(cx: &mut App) {
989 let text = r#"
990 <body>
991 <% if @one %>
992 <div class=one>
993 <% else %>
994 <div class=two>
995 <% end %>
996 </div>
997 </body>
998 "#
999 .unindent();
1000
1001 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1002 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
1003
1004 let mut syntax_map = SyntaxMap::new(&buffer);
1005 syntax_map.set_language_registry(registry.clone());
1006
1007 let language = Arc::new(erb_lang());
1008
1009 log::info!("parsing");
1010 registry.add(language.clone());
1011 syntax_map.reparse(language.clone(), &buffer);
1012
1013 log::info!("loading html");
1014 registry.add(Arc::new(html_lang()));
1015 syntax_map.reparse(language.clone(), &buffer);
1016
1017 log::info!("loading ruby");
1018 registry.add(Arc::new(ruby_lang()));
1019 syntax_map.reparse(language.clone(), &buffer);
1020
1021 assert_capture_ranges(
1022 &syntax_map,
1023 &buffer,
1024 &["tag", "ivar"],
1025 "
1026 <«body»>
1027 <% if «@one» %>
1028 <«div» class=one>
1029 <% else %>
1030 <«div» class=two>
1031 <% end %>
1032 </«div»>
1033 </«body»>
1034 ",
1035 );
1036
1037 let text = r#"
1038 <body>
1039 <% if @one«_hundred» %>
1040 <div class=one>
1041 <% else %>
1042 <div class=two>
1043 <% end %>
1044 </div>
1045 </body>
1046 "#
1047 .unindent();
1048
1049 log::info!("editing");
1050 buffer.edit_via_marked_text(&text);
1051 syntax_map.interpolate(&buffer);
1052 syntax_map.reparse(language, &buffer);
1053
1054 assert_capture_ranges(
1055 &syntax_map,
1056 &buffer,
1057 &["tag", "ivar"],
1058 "
1059 <«body»>
1060 <% if «@one_hundred» %>
1061 <«div» class=one>
1062 <% else %>
1063 <«div» class=two>
1064 <% end %>
1065 </«div»>
1066 </«body»>
1067 ",
1068 );
1069}
1070
1071#[gpui::test(iterations = 50)]
1072fn test_random_syntax_map_edits_rust_macros(rng: StdRng, cx: &mut App) {
1073 let text = r#"
1074 fn test_something() {
1075 let vec = vec![5, 1, 3, 8];
1076 assert_eq!(
1077 vec
1078 .into_iter()
1079 .map(|i| i * 2)
1080 .collect::<Vec<usize>>(),
1081 vec![
1082 5 * 2, 1 * 2, 3 * 2, 8 * 2
1083 ],
1084 );
1085 }
1086 "#
1087 .unindent()
1088 .repeat(2);
1089
1090 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1091 let language = rust_lang();
1092 registry.add(language.clone());
1093
1094 test_random_edits(text, registry, language, rng);
1095}
1096
1097#[gpui::test(iterations = 50)]
1098fn test_random_syntax_map_edits_with_erb(rng: StdRng, cx: &mut App) {
1099 let text = r#"
1100 <div id="main">
1101 <% if one?(:two) %>
1102 <p class="three" four>
1103 <%= yield :five %>
1104 </p>
1105 <% elsif Six.seven(8) %>
1106 <p id="three" four>
1107 <%= yield :five %>
1108 </p>
1109 <% else %>
1110 <span>Ok</span>
1111 <% end %>
1112 </div>
1113 "#
1114 .unindent()
1115 .repeat(5);
1116
1117 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1118 let language = Arc::new(erb_lang());
1119 registry.add(language.clone());
1120 registry.add(Arc::new(ruby_lang()));
1121 registry.add(Arc::new(html_lang()));
1122
1123 test_random_edits(text, registry, language, rng);
1124}
1125
1126#[gpui::test(iterations = 50)]
1127fn test_random_syntax_map_edits_with_heex(rng: StdRng, cx: &mut App) {
1128 let text = r#"
1129 defmodule TheModule do
1130 def the_method(assigns) do
1131 ~H"""
1132 <%= if @empty do %>
1133 <div class="h-4"></div>
1134 <% else %>
1135 <div class="max-w-2xl w-full animate-pulse">
1136 <div class="flex-1 space-y-4">
1137 <div class={[@bg_class, "h-4 rounded-lg w-3/4"]}></div>
1138 <div class={[@bg_class, "h-4 rounded-lg"]}></div>
1139 <div class={[@bg_class, "h-4 rounded-lg w-5/6"]}></div>
1140 </div>
1141 </div>
1142 <% end %>
1143 """
1144 end
1145 end
1146 "#
1147 .unindent()
1148 .repeat(3);
1149
1150 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1151 let language = Arc::new(elixir_lang());
1152 registry.add(language.clone());
1153 registry.add(Arc::new(heex_lang()));
1154 registry.add(Arc::new(html_lang()));
1155
1156 test_random_edits(text, registry, language, rng);
1157}
1158
1159fn test_random_edits(
1160 text: String,
1161 registry: Arc<LanguageRegistry>,
1162 language: Arc<Language>,
1163 mut rng: StdRng,
1164) {
1165 let operations = env::var("OPERATIONS")
1166 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1167 .unwrap_or(10);
1168
1169 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
1170
1171 let mut syntax_map = SyntaxMap::new(&buffer);
1172 syntax_map.set_language_registry(registry.clone());
1173 syntax_map.reparse(language.clone(), &buffer);
1174
1175 let mut reference_syntax_map = SyntaxMap::new(&buffer);
1176 reference_syntax_map.set_language_registry(registry);
1177
1178 log::info!("initial text:\n{}", buffer.text());
1179
1180 for _ in 0..operations {
1181 let prev_buffer = buffer.snapshot().clone();
1182 let prev_syntax_map = syntax_map.snapshot();
1183
1184 buffer.randomly_edit(&mut rng, 3);
1185 log::info!("text:\n{}", buffer.text());
1186
1187 syntax_map.interpolate(&buffer);
1188 check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer);
1189
1190 syntax_map.reparse(language.clone(), &buffer);
1191
1192 reference_syntax_map.clear(&buffer);
1193 reference_syntax_map.reparse(language.clone(), &buffer);
1194 }
1195
1196 for i in 0..operations {
1197 let i = operations - i - 1;
1198 buffer.undo();
1199 log::info!("undoing operation {}", i);
1200 log::info!("text:\n{}", buffer.text());
1201
1202 syntax_map.interpolate(&buffer);
1203 syntax_map.reparse(language.clone(), &buffer);
1204
1205 reference_syntax_map.clear(&buffer);
1206 reference_syntax_map.reparse(language.clone(), &buffer);
1207 assert_eq!(
1208 syntax_map.layers(&buffer).len(),
1209 reference_syntax_map.layers(&buffer).len(),
1210 "wrong number of layers after undoing edit {i}"
1211 );
1212 }
1213
1214 let layers = syntax_map.layers(&buffer);
1215 let reference_layers = reference_syntax_map.layers(&buffer);
1216 for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers) {
1217 assert_eq!(
1218 edited_layer.node().to_sexp(),
1219 reference_layer.node().to_sexp()
1220 );
1221 assert_eq!(edited_layer.node().range(), reference_layer.node().range());
1222 }
1223}
1224
1225fn check_interpolation(
1226 old_syntax_map: &SyntaxSnapshot,
1227 new_syntax_map: &SyntaxSnapshot,
1228 old_buffer: &BufferSnapshot,
1229 new_buffer: &BufferSnapshot,
1230) {
1231 let edits = new_buffer
1232 .edits_since::<usize>(old_buffer.version())
1233 .collect::<Vec<_>>();
1234
1235 for (old_layer, new_layer) in old_syntax_map
1236 .layers
1237 .iter()
1238 .zip(new_syntax_map.layers.iter())
1239 {
1240 assert_eq!(old_layer.range, new_layer.range);
1241 let Some(old_tree) = old_layer.content.tree() else {
1242 continue;
1243 };
1244 let Some(new_tree) = new_layer.content.tree() else {
1245 continue;
1246 };
1247 let old_start_byte = old_layer.range.start.to_offset(old_buffer);
1248 let new_start_byte = new_layer.range.start.to_offset(new_buffer);
1249 let old_start_point = old_layer.range.start.to_point(old_buffer).to_ts_point();
1250 let new_start_point = new_layer.range.start.to_point(new_buffer).to_ts_point();
1251 let old_node = old_tree.root_node_with_offset(old_start_byte, old_start_point);
1252 let new_node = new_tree.root_node_with_offset(new_start_byte, new_start_point);
1253 check_node_edits(
1254 old_layer.depth,
1255 &old_layer.range,
1256 old_node,
1257 new_node,
1258 old_buffer,
1259 new_buffer,
1260 &edits,
1261 );
1262 }
1263
1264 fn check_node_edits(
1265 depth: usize,
1266 range: &Range<Anchor>,
1267 old_node: Node,
1268 new_node: Node,
1269 old_buffer: &BufferSnapshot,
1270 new_buffer: &BufferSnapshot,
1271 edits: &[text::Edit<usize>],
1272 ) {
1273 assert_eq!(old_node.kind(), new_node.kind());
1274
1275 let old_range = old_node.byte_range();
1276 let new_range = new_node.byte_range();
1277
1278 let is_edited = edits
1279 .iter()
1280 .any(|edit| edit.new.start < new_range.end && edit.new.end > new_range.start);
1281 if is_edited {
1282 assert!(
1283 new_node.has_changes(),
1284 concat!(
1285 "failed to mark node as edited.\n",
1286 "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n",
1287 "node kind: {}, old node range: {:?}, new node range: {:?}",
1288 ),
1289 depth,
1290 range.to_offset(old_buffer),
1291 range.to_offset(new_buffer),
1292 new_node.kind(),
1293 old_range,
1294 new_range,
1295 );
1296 }
1297
1298 if !new_node.has_changes() {
1299 assert_eq!(
1300 old_buffer
1301 .text_for_range(old_range.clone())
1302 .collect::<String>(),
1303 new_buffer
1304 .text_for_range(new_range.clone())
1305 .collect::<String>(),
1306 concat!(
1307 "mismatched text for node\n",
1308 "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n",
1309 "node kind: {}, old node range:{:?}, new node range:{:?}",
1310 ),
1311 depth,
1312 range.to_offset(old_buffer),
1313 range.to_offset(new_buffer),
1314 new_node.kind(),
1315 old_range,
1316 new_range,
1317 );
1318 }
1319
1320 for i in 0..new_node.child_count() {
1321 check_node_edits(
1322 depth,
1323 range,
1324 old_node.child(i as u32).unwrap(),
1325 new_node.child(i as u32).unwrap(),
1326 old_buffer,
1327 new_buffer,
1328 edits,
1329 )
1330 }
1331 }
1332}
1333
1334fn test_edit_sequence(language_name: &str, steps: &[&str], cx: &mut App) -> (Buffer, SyntaxMap) {
1335 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1336 registry.add(Arc::new(elixir_lang()));
1337 registry.add(Arc::new(heex_lang()));
1338 registry.add(rust_lang());
1339 registry.add(Arc::new(ruby_lang()));
1340 registry.add(Arc::new(html_lang()));
1341 registry.add(Arc::new(erb_lang()));
1342 registry.add(markdown_lang());
1343 registry.add(Arc::new(markdown_inline_lang()));
1344
1345 let language = registry
1346 .language_for_name(language_name)
1347 .now_or_never()
1348 .unwrap()
1349 .unwrap();
1350 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
1351
1352 let mut mutated_syntax_map = SyntaxMap::new(&buffer);
1353 mutated_syntax_map.set_language_registry(registry.clone());
1354 mutated_syntax_map.reparse(language.clone(), &buffer);
1355
1356 for (i, marked_string) in steps.iter().enumerate() {
1357 let marked_string = marked_string.unindent();
1358 log::info!("incremental parse {i}: {marked_string:?}");
1359 buffer.edit_via_marked_text(&marked_string);
1360
1361 // Reparse the syntax map
1362 mutated_syntax_map.interpolate(&buffer);
1363 mutated_syntax_map.reparse(language.clone(), &buffer);
1364
1365 // Create a second syntax map from scratch
1366 log::info!("fresh parse {i}: {marked_string:?}");
1367 let mut reference_syntax_map = SyntaxMap::new(&buffer);
1368 reference_syntax_map.set_language_registry(registry.clone());
1369 reference_syntax_map.reparse(language.clone(), &buffer);
1370
1371 // Compare the mutated syntax map to the new syntax map
1372 let mutated_layers = mutated_syntax_map.layers(&buffer);
1373 let reference_layers = reference_syntax_map.layers(&buffer);
1374 assert_eq!(
1375 mutated_layers.len(),
1376 reference_layers.len(),
1377 "wrong number of layers at step {i}"
1378 );
1379 for (edited_layer, reference_layer) in mutated_layers.into_iter().zip(reference_layers) {
1380 assert_eq!(
1381 edited_layer.node().to_sexp(),
1382 reference_layer.node().to_sexp(),
1383 "different layer at step {i}"
1384 );
1385 assert_eq!(
1386 edited_layer.node().range(),
1387 reference_layer.node().range(),
1388 "different layer at step {i}"
1389 );
1390 }
1391 }
1392
1393 (buffer, mutated_syntax_map)
1394}
1395
1396fn html_lang() -> Language {
1397 Language::new(
1398 LanguageConfig {
1399 name: "HTML".into(),
1400 matcher: (LanguageMatcher {
1401 path_suffixes: vec!["html".to_string()],
1402 ..Default::default()
1403 })
1404 .into(),
1405 ..Default::default()
1406 },
1407 Some(tree_sitter_html::LANGUAGE.into()),
1408 )
1409 .with_highlights_query(
1410 r#"
1411 (tag_name) @tag
1412 (erroneous_end_tag_name) @tag
1413 (attribute_name) @property
1414 "#,
1415 )
1416 .unwrap()
1417}
1418
1419fn ruby_lang() -> Language {
1420 Language::new(
1421 LanguageConfig {
1422 name: "Ruby".into(),
1423 matcher: (LanguageMatcher {
1424 path_suffixes: vec!["rb".to_string()],
1425 ..Default::default()
1426 })
1427 .into(),
1428 ..Default::default()
1429 },
1430 Some(tree_sitter_ruby::LANGUAGE.into()),
1431 )
1432 .with_highlights_query(
1433 r#"
1434 ["if" "do" "else" "end"] @keyword
1435 (instance_variable) @ivar
1436 (call method: (identifier) @method)
1437 "#,
1438 )
1439 .unwrap()
1440}
1441
1442fn erb_lang() -> Language {
1443 Language::new(
1444 LanguageConfig {
1445 name: "ERB".into(),
1446 matcher: (LanguageMatcher {
1447 path_suffixes: vec!["erb".to_string()],
1448 ..Default::default()
1449 })
1450 .into(),
1451 ..Default::default()
1452 },
1453 Some(tree_sitter_embedded_template::LANGUAGE.into()),
1454 )
1455 .with_highlights_query(
1456 r#"
1457 ["<%" "%>"] @keyword
1458 "#,
1459 )
1460 .unwrap()
1461 .with_injection_query(
1462 r#"
1463 (
1464 (code) @injection.content
1465 (#set! injection.language "ruby")
1466 (#set! injection.combined)
1467 )
1468
1469 (
1470 (content) @injection.content
1471 (#set! injection.language "html")
1472 (#set! injection.combined)
1473 )
1474 "#,
1475 )
1476 .unwrap()
1477}
1478
1479fn elixir_lang() -> Language {
1480 Language::new(
1481 LanguageConfig {
1482 name: "Elixir".into(),
1483 matcher: (LanguageMatcher {
1484 path_suffixes: vec!["ex".into()],
1485 ..Default::default()
1486 })
1487 .into(),
1488 ..Default::default()
1489 },
1490 Some(tree_sitter_elixir::LANGUAGE.into()),
1491 )
1492 .with_highlights_query(
1493 r#"
1494
1495 "#,
1496 )
1497 .unwrap()
1498}
1499
1500fn heex_lang() -> Language {
1501 Language::new(
1502 LanguageConfig {
1503 name: "HEEx".into(),
1504 matcher: (LanguageMatcher {
1505 path_suffixes: vec!["heex".into()],
1506 ..Default::default()
1507 })
1508 .into(),
1509 ..Default::default()
1510 },
1511 Some(tree_sitter_heex::LANGUAGE.into()),
1512 )
1513 .with_injection_query(
1514 r#"
1515 (
1516 (directive
1517 [
1518 (partial_expression_value)
1519 (expression_value)
1520 (ending_expression_value)
1521 ] @injection.content)
1522 (#set! injection.language "elixir")
1523 (#set! injection.combined)
1524 )
1525
1526 ((expression (expression_value) @injection.content)
1527 (#set! injection.language "elixir"))
1528 "#,
1529 )
1530 .unwrap()
1531}
1532
1533fn python_lang() -> Language {
1534 Language::new(
1535 LanguageConfig {
1536 name: "Python".into(),
1537 matcher: (LanguageMatcher {
1538 path_suffixes: vec!["py".to_string()],
1539 ..Default::default()
1540 })
1541 .into(),
1542 line_comments: vec!["# ".into()],
1543 ..Default::default()
1544 },
1545 Some(tree_sitter_python::LANGUAGE.into()),
1546 )
1547 .with_queries(LanguageQueries {
1548 injections: Some(Cow::from(include_str!(
1549 "../../../grammars/src/python/injections.scm"
1550 ))),
1551 ..Default::default()
1552 })
1553 .expect("Could not parse Python queries")
1554}
1555
1556fn comment_lang() -> Language {
1557 // Mock "comment" language to satisfy Python's comment injection.
1558 // Uses JSON grammar as a stand-in since we just need it to be registered.
1559 Language::new(
1560 LanguageConfig {
1561 name: "comment".into(),
1562 ..Default::default()
1563 },
1564 Some(tree_sitter_json::LANGUAGE.into()),
1565 )
1566}
1567
1568fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
1569 let start = buffer.as_rope().to_string().find(text).unwrap();
1570 start..start + text.len()
1571}
1572
1573#[track_caller]
1574fn assert_layers_for_range(
1575 syntax_map: &SyntaxMap,
1576 buffer: &BufferSnapshot,
1577 range: Range<Point>,
1578 expected_layers: &[&str],
1579) {
1580 let layers = syntax_map
1581 .layers_for_range(range, buffer, true)
1582 .collect::<Vec<_>>();
1583 assert_eq!(
1584 layers.len(),
1585 expected_layers.len(),
1586 "wrong number of layers"
1587 );
1588 for (i, (layer, expected_s_exp)) in layers.iter().zip(expected_layers.iter()).enumerate() {
1589 let actual_s_exp = layer.node().to_sexp();
1590 assert!(
1591 string_contains_sequence(
1592 &actual_s_exp,
1593 &expected_s_exp.split("...").collect::<Vec<_>>()
1594 ),
1595 "layer {i}:\n\nexpected: {expected_s_exp}\nactual: {actual_s_exp}",
1596 );
1597 }
1598}
1599
1600#[track_caller]
1601fn assert_capture_ranges(
1602 syntax_map: &SyntaxMap,
1603 buffer: &BufferSnapshot,
1604 highlight_query_capture_names: &[&str],
1605 marked_string: &str,
1606) {
1607 let mut actual_ranges = Vec::<Range<usize>>::new();
1608 let captures = syntax_map.captures(0..buffer.len(), buffer, |grammar| {
1609 grammar
1610 .highlights_config
1611 .as_ref()
1612 .map(|config| &config.query)
1613 });
1614 let queries = captures
1615 .grammars()
1616 .iter()
1617 .map(|grammar| &grammar.highlights_config.as_ref().unwrap().query)
1618 .collect::<Vec<_>>();
1619 for capture in captures {
1620 let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];
1621 if highlight_query_capture_names.contains(name) {
1622 actual_ranges.push(capture.node.byte_range());
1623 }
1624 }
1625 actual_ranges.dedup();
1626
1627 let (text, expected_ranges) = marked_text_ranges(&marked_string.unindent(), false);
1628 assert_eq!(text, buffer.text());
1629 assert_eq!(actual_ranges, expected_ranges);
1630}
1631
1632pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool {
1633 let mut last_part_end = 0;
1634 for part in parts {
1635 if let Some(start_ix) = text[last_part_end..].find(part) {
1636 last_part_end = start_ix + part.len();
1637 } else {
1638 return false;
1639 }
1640 }
1641 true
1642}
1643