Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:52:33.767Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

syntax_map_tests.rs

1643 lines · 47.4 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.