patch.rs

  1use anyhow::{Context as _, Result, anyhow};
  2use collections::HashMap;
  3use editor::ProposedChangesEditor;
  4use futures::{TryFutureExt as _, future};
  5use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString};
  6use language::{AutoindentMode, Buffer, BufferSnapshot};
  7use project::{Project, ProjectPath};
  8use std::{cmp, ops::Range, path::Path, sync::Arc};
  9use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point};
 10
 11#[derive(Clone, Debug)]
 12pub struct AssistantPatch {
 13    pub range: Range<language::Anchor>,
 14    pub title: SharedString,
 15    pub edits: Arc<[Result<AssistantEdit>]>,
 16    pub status: AssistantPatchStatus,
 17}
 18
 19#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 20pub enum AssistantPatchStatus {
 21    Pending,
 22    Ready,
 23}
 24
 25#[derive(Clone, Debug, PartialEq, Eq)]
 26pub struct AssistantEdit {
 27    pub path: String,
 28    pub kind: AssistantEditKind,
 29}
 30
 31#[derive(Clone, Debug, PartialEq, Eq)]
 32pub enum AssistantEditKind {
 33    Update {
 34        old_text: String,
 35        new_text: String,
 36        description: Option<String>,
 37    },
 38    Create {
 39        new_text: String,
 40        description: Option<String>,
 41    },
 42    InsertBefore {
 43        old_text: String,
 44        new_text: String,
 45        description: Option<String>,
 46    },
 47    InsertAfter {
 48        old_text: String,
 49        new_text: String,
 50        description: Option<String>,
 51    },
 52    Delete {
 53        old_text: String,
 54    },
 55}
 56
 57#[derive(Clone, Debug, Eq, PartialEq)]
 58pub struct ResolvedPatch {
 59    pub edit_groups: HashMap<Entity<Buffer>, Vec<ResolvedEditGroup>>,
 60    pub errors: Vec<AssistantPatchResolutionError>,
 61}
 62
 63#[derive(Clone, Debug, Eq, PartialEq)]
 64pub struct ResolvedEditGroup {
 65    pub context_range: Range<language::Anchor>,
 66    pub edits: Vec<ResolvedEdit>,
 67}
 68
 69#[derive(Clone, Debug, Eq, PartialEq)]
 70pub struct ResolvedEdit {
 71    range: Range<language::Anchor>,
 72    new_text: String,
 73    description: Option<String>,
 74}
 75
 76#[derive(Clone, Debug, Eq, PartialEq)]
 77pub struct AssistantPatchResolutionError {
 78    pub edit_ix: usize,
 79    pub message: String,
 80}
 81
 82#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 83enum SearchDirection {
 84    Up,
 85    Left,
 86    Diagonal,
 87}
 88
 89#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
 90struct SearchState {
 91    cost: u32,
 92    direction: SearchDirection,
 93}
 94
 95impl SearchState {
 96    fn new(cost: u32, direction: SearchDirection) -> Self {
 97        Self { cost, direction }
 98    }
 99}
100
101struct SearchMatrix {
102    cols: usize,
103    data: Vec<SearchState>,
104}
105
106impl SearchMatrix {
107    fn new(rows: usize, cols: usize) -> Self {
108        SearchMatrix {
109            cols,
110            data: vec![SearchState::new(0, SearchDirection::Diagonal); rows * cols],
111        }
112    }
113
114    fn get(&self, row: usize, col: usize) -> SearchState {
115        self.data[row * self.cols + col]
116    }
117
118    fn set(&mut self, row: usize, col: usize, cost: SearchState) {
119        self.data[row * self.cols + col] = cost;
120    }
121}
122
123impl ResolvedPatch {
124    pub fn apply(&self, editor: &ProposedChangesEditor, cx: &mut App) {
125        for (buffer, groups) in &self.edit_groups {
126            let branch = editor.branch_buffer_for_base(buffer).unwrap();
127            Self::apply_edit_groups(groups, &branch, cx);
128        }
129        editor.recalculate_all_buffer_diffs();
130    }
131
132    fn apply_edit_groups(groups: &Vec<ResolvedEditGroup>, buffer: &Entity<Buffer>, cx: &mut App) {
133        let mut edits = Vec::new();
134        for group in groups {
135            for suggestion in &group.edits {
136                edits.push((suggestion.range.clone(), suggestion.new_text.clone()));
137            }
138        }
139        buffer.update(cx, |buffer, cx| {
140            buffer.edit(
141                edits,
142                Some(AutoindentMode::Block {
143                    original_indent_columns: Vec::new(),
144                }),
145                cx,
146            );
147        });
148    }
149}
150
151impl ResolvedEdit {
152    pub fn try_merge(&mut self, other: &Self, buffer: &text::BufferSnapshot) -> bool {
153        let range = &self.range;
154        let other_range = &other.range;
155
156        // Don't merge if we don't contain the other suggestion.
157        if range.start.cmp(&other_range.start, buffer).is_gt()
158            || range.end.cmp(&other_range.end, buffer).is_lt()
159        {
160            return false;
161        }
162
163        let other_offset_range = other_range.to_offset(buffer);
164        let offset_range = range.to_offset(buffer);
165
166        // If the other range is empty at the start of this edit's range, combine the new text
167        if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
168            self.new_text = format!("{}\n{}", other.new_text, self.new_text);
169            self.range.start = other_range.start;
170
171            if let Some((description, other_description)) =
172                self.description.as_mut().zip(other.description.as_ref())
173            {
174                *description = format!("{}\n{}", other_description, description)
175            }
176        } else {
177            if let Some((description, other_description)) =
178                self.description.as_mut().zip(other.description.as_ref())
179            {
180                description.push('\n');
181                description.push_str(other_description);
182            }
183        }
184
185        true
186    }
187}
188
189impl AssistantEdit {
190    pub fn new(
191        path: Option<String>,
192        operation: Option<String>,
193        old_text: Option<String>,
194        new_text: Option<String>,
195        description: Option<String>,
196    ) -> Result<Self> {
197        let path = path.ok_or_else(|| anyhow!("missing path"))?;
198        let operation = operation.ok_or_else(|| anyhow!("missing operation"))?;
199
200        let kind = match operation.as_str() {
201            "update" => AssistantEditKind::Update {
202                old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
203                new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
204                description,
205            },
206            "insert_before" => AssistantEditKind::InsertBefore {
207                old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
208                new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
209                description,
210            },
211            "insert_after" => AssistantEditKind::InsertAfter {
212                old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
213                new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
214                description,
215            },
216            "delete" => AssistantEditKind::Delete {
217                old_text: old_text.ok_or_else(|| anyhow!("missing old_text"))?,
218            },
219            "create" => AssistantEditKind::Create {
220                description,
221                new_text: new_text.ok_or_else(|| anyhow!("missing new_text"))?,
222            },
223            _ => Err(anyhow!("unknown operation {operation:?}"))?,
224        };
225
226        Ok(Self { path, kind })
227    }
228
229    pub async fn resolve(
230        &self,
231        project: Entity<Project>,
232        mut cx: AsyncApp,
233    ) -> Result<(Entity<Buffer>, ResolvedEdit)> {
234        let path = self.path.clone();
235        let kind = self.kind.clone();
236        let buffer = project
237            .update(&mut cx, |project, cx| {
238                let project_path = project
239                    .find_project_path(Path::new(&path), cx)
240                    .or_else(|| {
241                        // If we couldn't find a project path for it, put it in the active worktree
242                        // so that when we create the buffer, it can be saved.
243                        let worktree = project
244                            .active_entry()
245                            .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
246                            .or_else(|| project.worktrees(cx).next())?;
247                        let worktree = worktree.read(cx);
248
249                        Some(ProjectPath {
250                            worktree_id: worktree.id(),
251                            path: Arc::from(Path::new(&path)),
252                        })
253                    })
254                    .with_context(|| format!("worktree not found for {:?}", path))?;
255                anyhow::Ok(project.open_buffer(project_path, cx))
256            })??
257            .await?;
258
259        let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
260        let suggestion = cx
261            .background_spawn(async move { kind.resolve(&snapshot) })
262            .await;
263
264        Ok((buffer, suggestion))
265    }
266}
267
268impl AssistantEditKind {
269    fn resolve(self, snapshot: &BufferSnapshot) -> ResolvedEdit {
270        match self {
271            Self::Update {
272                old_text,
273                new_text,
274                description,
275            } => {
276                let range = Self::resolve_location(&snapshot, &old_text);
277                ResolvedEdit {
278                    range,
279                    new_text,
280                    description,
281                }
282            }
283            Self::Create {
284                new_text,
285                description,
286            } => ResolvedEdit {
287                range: text::Anchor::MIN..text::Anchor::MAX,
288                description,
289                new_text,
290            },
291            Self::InsertBefore {
292                old_text,
293                mut new_text,
294                description,
295            } => {
296                let range = Self::resolve_location(&snapshot, &old_text);
297                new_text.push('\n');
298                ResolvedEdit {
299                    range: range.start..range.start,
300                    new_text,
301                    description,
302                }
303            }
304            Self::InsertAfter {
305                old_text,
306                mut new_text,
307                description,
308            } => {
309                let range = Self::resolve_location(&snapshot, &old_text);
310                new_text.insert(0, '\n');
311                ResolvedEdit {
312                    range: range.end..range.end,
313                    new_text,
314                    description,
315                }
316            }
317            Self::Delete { old_text } => {
318                let range = Self::resolve_location(&snapshot, &old_text);
319                ResolvedEdit {
320                    range,
321                    new_text: String::new(),
322                    description: None,
323                }
324            }
325        }
326    }
327
328    fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
329        const INSERTION_COST: u32 = 3;
330        const DELETION_COST: u32 = 10;
331        const WHITESPACE_INSERTION_COST: u32 = 1;
332        const WHITESPACE_DELETION_COST: u32 = 1;
333
334        let buffer_len = buffer.len();
335        let query_len = search_query.len();
336        let mut matrix = SearchMatrix::new(query_len + 1, buffer_len + 1);
337        let mut leading_deletion_cost = 0_u32;
338        for (row, query_byte) in search_query.bytes().enumerate() {
339            let deletion_cost = if query_byte.is_ascii_whitespace() {
340                WHITESPACE_DELETION_COST
341            } else {
342                DELETION_COST
343            };
344
345            leading_deletion_cost = leading_deletion_cost.saturating_add(deletion_cost);
346            matrix.set(
347                row + 1,
348                0,
349                SearchState::new(leading_deletion_cost, SearchDirection::Diagonal),
350            );
351
352            for (col, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
353                let insertion_cost = if buffer_byte.is_ascii_whitespace() {
354                    WHITESPACE_INSERTION_COST
355                } else {
356                    INSERTION_COST
357                };
358
359                let up = SearchState::new(
360                    matrix.get(row, col + 1).cost.saturating_add(deletion_cost),
361                    SearchDirection::Up,
362                );
363                let left = SearchState::new(
364                    matrix.get(row + 1, col).cost.saturating_add(insertion_cost),
365                    SearchDirection::Left,
366                );
367                let diagonal = SearchState::new(
368                    if query_byte == *buffer_byte {
369                        matrix.get(row, col).cost
370                    } else {
371                        matrix
372                            .get(row, col)
373                            .cost
374                            .saturating_add(deletion_cost + insertion_cost)
375                    },
376                    SearchDirection::Diagonal,
377                );
378                matrix.set(row + 1, col + 1, up.min(left).min(diagonal));
379            }
380        }
381
382        // Traceback to find the best match
383        let mut best_buffer_end = buffer_len;
384        let mut best_cost = u32::MAX;
385        for col in 1..=buffer_len {
386            let cost = matrix.get(query_len, col).cost;
387            if cost < best_cost {
388                best_cost = cost;
389                best_buffer_end = col;
390            }
391        }
392
393        let mut query_ix = query_len;
394        let mut buffer_ix = best_buffer_end;
395        while query_ix > 0 && buffer_ix > 0 {
396            let current = matrix.get(query_ix, buffer_ix);
397            match current.direction {
398                SearchDirection::Diagonal => {
399                    query_ix -= 1;
400                    buffer_ix -= 1;
401                }
402                SearchDirection::Up => {
403                    query_ix -= 1;
404                }
405                SearchDirection::Left => {
406                    buffer_ix -= 1;
407                }
408            }
409        }
410
411        let mut start = buffer.offset_to_point(buffer.clip_offset(buffer_ix, Bias::Left));
412        start.column = 0;
413        let mut end = buffer.offset_to_point(buffer.clip_offset(best_buffer_end, Bias::Right));
414        if end.column > 0 {
415            end.column = buffer.line_len(end.row);
416        }
417
418        buffer.anchor_after(start)..buffer.anchor_before(end)
419    }
420}
421
422impl AssistantPatch {
423    pub async fn resolve(&self, project: Entity<Project>, cx: &mut AsyncApp) -> ResolvedPatch {
424        let mut resolve_tasks = Vec::new();
425        for (ix, edit) in self.edits.iter().enumerate() {
426            if let Ok(edit) = edit.as_ref() {
427                resolve_tasks.push(
428                    edit.resolve(project.clone(), cx.clone())
429                        .map_err(move |error| (ix, error)),
430                );
431            }
432        }
433
434        let edits = future::join_all(resolve_tasks).await;
435        let mut errors = Vec::new();
436        let mut edits_by_buffer = HashMap::default();
437        for entry in edits {
438            match entry {
439                Ok((buffer, edit)) => {
440                    edits_by_buffer
441                        .entry(buffer)
442                        .or_insert_with(Vec::new)
443                        .push(edit);
444                }
445                Err((edit_ix, error)) => errors.push(AssistantPatchResolutionError {
446                    edit_ix,
447                    message: error.to_string(),
448                }),
449            }
450        }
451
452        // Expand the context ranges of each edit and group edits with overlapping context ranges.
453        let mut edit_groups_by_buffer = HashMap::default();
454        for (buffer, edits) in edits_by_buffer {
455            if let Ok(snapshot) = buffer.update(cx, |buffer, _| buffer.text_snapshot()) {
456                edit_groups_by_buffer.insert(buffer, Self::group_edits(edits, &snapshot));
457            }
458        }
459
460        ResolvedPatch {
461            edit_groups: edit_groups_by_buffer,
462            errors,
463        }
464    }
465
466    fn group_edits(
467        mut edits: Vec<ResolvedEdit>,
468        snapshot: &text::BufferSnapshot,
469    ) -> Vec<ResolvedEditGroup> {
470        let mut edit_groups = Vec::<ResolvedEditGroup>::new();
471        // Sort edits by their range so that earlier, larger ranges come first
472        edits.sort_by(|a, b| a.range.cmp(&b.range, &snapshot));
473
474        // Merge overlapping edits
475        edits.dedup_by(|a, b| b.try_merge(a, &snapshot));
476
477        // Create context ranges for each edit
478        for edit in edits {
479            let context_range = {
480                let edit_point_range = edit.range.to_point(&snapshot);
481                let start_row = edit_point_range.start.row.saturating_sub(5);
482                let end_row = cmp::min(edit_point_range.end.row + 5, snapshot.max_point().row);
483                let start = snapshot.anchor_before(Point::new(start_row, 0));
484                let end = snapshot.anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
485                start..end
486            };
487
488            if let Some(last_group) = edit_groups.last_mut() {
489                if last_group
490                    .context_range
491                    .end
492                    .cmp(&context_range.start, &snapshot)
493                    .is_ge()
494                {
495                    // Merge with the previous group if context ranges overlap
496                    last_group.context_range.end = context_range.end;
497                    last_group.edits.push(edit);
498                } else {
499                    // Create a new group
500                    edit_groups.push(ResolvedEditGroup {
501                        context_range,
502                        edits: vec![edit],
503                    });
504                }
505            } else {
506                // Create the first group
507                edit_groups.push(ResolvedEditGroup {
508                    context_range,
509                    edits: vec![edit],
510                });
511            }
512        }
513
514        edit_groups
515    }
516
517    pub fn path_count(&self) -> usize {
518        self.paths().count()
519    }
520
521    pub fn paths(&self) -> impl '_ + Iterator<Item = &str> {
522        let mut prev_path = None;
523        self.edits.iter().filter_map(move |edit| {
524            if let Ok(edit) = edit {
525                let path = Some(edit.path.as_str());
526                if path != prev_path {
527                    prev_path = path;
528                    return path;
529                }
530            }
531            None
532        })
533    }
534}
535
536impl PartialEq for AssistantPatch {
537    fn eq(&self, other: &Self) -> bool {
538        self.range == other.range
539            && self.title == other.title
540            && Arc::ptr_eq(&self.edits, &other.edits)
541    }
542}
543
544impl Eq for AssistantPatch {}
545
546#[cfg(test)]
547mod tests {
548    use super::*;
549    use gpui::App;
550    use language::{
551        Language, LanguageConfig, LanguageMatcher, language_settings::AllLanguageSettings,
552    };
553    use settings::SettingsStore;
554    use ui::BorrowAppContext;
555    use unindent::Unindent as _;
556    use util::test::{generate_marked_text, marked_text_ranges};
557
558    #[gpui::test]
559    fn test_resolve_location(cx: &mut App) {
560        assert_location_resolution(
561            concat!(
562                "    Lorem\n",
563                "«    ipsum\n",
564                "    dolor sit amet»\n",
565                "    consecteur",
566            ),
567            "ipsum\ndolor",
568            cx,
569        );
570
571        assert_location_resolution(
572            &"
573            «fn foo1(a: usize) -> usize {
574                40
575576
577            fn foo2(b: usize) -> usize {
578                42
579            }
580            "
581            .unindent(),
582            "fn foo1(b: usize) {\n40\n}",
583            cx,
584        );
585
586        assert_location_resolution(
587            &"
588            fn main() {
589            «    Foo
590                    .bar()
591                    .baz()
592                    .qux()»
593            }
594
595            fn foo2(b: usize) -> usize {
596                42
597            }
598            "
599            .unindent(),
600            "Foo.bar.baz.qux()",
601            cx,
602        );
603
604        assert_location_resolution(
605            &"
606            class Something {
607                one() { return 1; }
608            «    two() { return 2222; }
609                three() { return 333; }
610                four() { return 4444; }
611                five() { return 5555; }
612                six() { return 6666; }
613            »    seven() { return 7; }
614                eight() { return 8; }
615            }
616            "
617            .unindent(),
618            &"
619                two() { return 2222; }
620                four() { return 4444; }
621                five() { return 5555; }
622                six() { return 6666; }
623            "
624            .unindent(),
625            cx,
626        );
627    }
628
629    #[gpui::test]
630    fn test_resolve_edits(cx: &mut App) {
631        init_test(cx);
632
633        assert_edits(
634            "
635                /// A person
636                struct Person {
637                    name: String,
638                    age: usize,
639                }
640
641                /// A dog
642                struct Dog {
643                    weight: f32,
644                }
645
646                impl Person {
647                    fn name(&self) -> &str {
648                        &self.name
649                    }
650                }
651            "
652            .unindent(),
653            vec![
654                AssistantEditKind::Update {
655                    old_text: "
656                        name: String,
657                    "
658                    .unindent(),
659                    new_text: "
660                        first_name: String,
661                        last_name: String,
662                    "
663                    .unindent(),
664                    description: None,
665                },
666                AssistantEditKind::Update {
667                    old_text: "
668                        fn name(&self) -> &str {
669                            &self.name
670                        }
671                    "
672                    .unindent(),
673                    new_text: "
674                        fn name(&self) -> String {
675                            format!(\"{} {}\", self.first_name, self.last_name)
676                        }
677                    "
678                    .unindent(),
679                    description: None,
680                },
681            ],
682            "
683                /// A person
684                struct Person {
685                    first_name: String,
686                    last_name: String,
687                    age: usize,
688                }
689
690                /// A dog
691                struct Dog {
692                    weight: f32,
693                }
694
695                impl Person {
696                    fn name(&self) -> String {
697                        format!(\"{} {}\", self.first_name, self.last_name)
698                    }
699                }
700            "
701            .unindent(),
702            cx,
703        );
704
705        // Ensure InsertBefore merges correctly with Update of the same text
706        assert_edits(
707            "
708                fn foo() {
709
710                }
711            "
712            .unindent(),
713            vec![
714                AssistantEditKind::InsertBefore {
715                    old_text: "
716                        fn foo() {"
717                        .unindent(),
718                    new_text: "
719                        fn bar() {
720                            qux();
721                        }"
722                    .unindent(),
723                    description: Some("implement bar".into()),
724                },
725                AssistantEditKind::Update {
726                    old_text: "
727                        fn foo() {
728
729                        }"
730                    .unindent(),
731                    new_text: "
732                        fn foo() {
733                            bar();
734                        }"
735                    .unindent(),
736                    description: Some("call bar in foo".into()),
737                },
738                AssistantEditKind::InsertAfter {
739                    old_text: "
740                        fn foo() {
741
742                        }
743                    "
744                    .unindent(),
745                    new_text: "
746                        fn qux() {
747                            // todo
748                        }
749                    "
750                    .unindent(),
751                    description: Some("implement qux".into()),
752                },
753            ],
754            "
755                fn bar() {
756                    qux();
757                }
758
759                fn foo() {
760                    bar();
761                }
762
763                fn qux() {
764                    // todo
765                }
766            "
767            .unindent(),
768            cx,
769        );
770
771        // Correctly indent new text when replacing multiple adjacent indented blocks.
772        assert_edits(
773            "
774            impl Numbers {
775                fn one() {
776                    1
777                }
778
779                fn two() {
780                    2
781                }
782
783                fn three() {
784                    3
785                }
786            }
787            "
788            .unindent(),
789            vec![
790                AssistantEditKind::Update {
791                    old_text: "
792                        fn one() {
793                            1
794                        }
795                    "
796                    .unindent(),
797                    new_text: "
798                        fn one() {
799                            101
800                        }
801                    "
802                    .unindent(),
803                    description: None,
804                },
805                AssistantEditKind::Update {
806                    old_text: "
807                        fn two() {
808                            2
809                        }
810                    "
811                    .unindent(),
812                    new_text: "
813                        fn two() {
814                            102
815                        }
816                    "
817                    .unindent(),
818                    description: None,
819                },
820                AssistantEditKind::Update {
821                    old_text: "
822                        fn three() {
823                            3
824                        }
825                    "
826                    .unindent(),
827                    new_text: "
828                        fn three() {
829                            103
830                        }
831                    "
832                    .unindent(),
833                    description: None,
834                },
835            ],
836            "
837                impl Numbers {
838                    fn one() {
839                        101
840                    }
841
842                    fn two() {
843                        102
844                    }
845
846                    fn three() {
847                        103
848                    }
849                }
850            "
851            .unindent(),
852            cx,
853        );
854
855        assert_edits(
856            "
857            impl Person {
858                fn set_name(&mut self, name: String) {
859                    self.name = name;
860                }
861
862                fn name(&self) -> String {
863                    return self.name;
864                }
865            }
866            "
867            .unindent(),
868            vec![
869                AssistantEditKind::Update {
870                    old_text: "self.name = name;".unindent(),
871                    new_text: "self._name = name;".unindent(),
872                    description: None,
873                },
874                AssistantEditKind::Update {
875                    old_text: "return self.name;\n".unindent(),
876                    new_text: "return self._name;\n".unindent(),
877                    description: None,
878                },
879            ],
880            "
881                impl Person {
882                    fn set_name(&mut self, name: String) {
883                        self._name = name;
884                    }
885
886                    fn name(&self) -> String {
887                        return self._name;
888                    }
889                }
890            "
891            .unindent(),
892            cx,
893        );
894    }
895
896    fn init_test(cx: &mut App) {
897        let settings_store = SettingsStore::test(cx);
898        cx.set_global(settings_store);
899        language::init(cx);
900        cx.update_global::<SettingsStore, _>(|settings, cx| {
901            settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
902        });
903    }
904
905    #[track_caller]
906    fn assert_location_resolution(text_with_expected_range: &str, query: &str, cx: &mut App) {
907        let (text, _) = marked_text_ranges(text_with_expected_range, false);
908        let buffer = cx.new(|cx| Buffer::local(text.clone(), cx));
909        let snapshot = buffer.read(cx).snapshot();
910        let range = AssistantEditKind::resolve_location(&snapshot, query).to_offset(&snapshot);
911        let text_with_actual_range = generate_marked_text(&text, &[range], false);
912        pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
913    }
914
915    #[track_caller]
916    fn assert_edits(
917        old_text: String,
918        edits: Vec<AssistantEditKind>,
919        new_text: String,
920        cx: &mut App,
921    ) {
922        let buffer =
923            cx.new(|cx| Buffer::local(old_text, cx).with_language(Arc::new(rust_lang()), cx));
924        let snapshot = buffer.read(cx).snapshot();
925        let resolved_edits = edits
926            .into_iter()
927            .map(|kind| kind.resolve(&snapshot))
928            .collect();
929        let edit_groups = AssistantPatch::group_edits(resolved_edits, &snapshot);
930        ResolvedPatch::apply_edit_groups(&edit_groups, &buffer, cx);
931        let actual_new_text = buffer.read(cx).text();
932        pretty_assertions::assert_eq!(actual_new_text, new_text);
933    }
934
935    fn rust_lang() -> Language {
936        Language::new(
937            LanguageConfig {
938                name: "Rust".into(),
939                matcher: LanguageMatcher {
940                    path_suffixes: vec!["rs".to_string()],
941                    ..Default::default()
942                },
943                ..Default::default()
944            },
945            Some(language::tree_sitter_rust::LANGUAGE.into()),
946        )
947        .with_indents_query(
948            r#"
949            (call_expression) @indent
950            (field_expression) @indent
951            (_ "(" ")" @end) @indent
952            (_ "{" "}" @end) @indent
953            "#,
954        )
955        .unwrap()
956    }
957}