inline_assistant.rs

   1use crate::{
   2    assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
   3    AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
   4    CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff,
   5};
   6use anyhow::{anyhow, Context as _, Result};
   7use client::{telemetry::Telemetry, ErrorExt};
   8use collections::{hash_map, HashMap, HashSet, VecDeque};
   9use editor::{
  10    actions::{MoveDown, MoveUp, SelectAll},
  11    display_map::{
  12        BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
  13        ToDisplayPoint,
  14    },
  15    Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
  16    EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
  17    ToOffset as _, ToPoint,
  18};
  19use feature_flags::{FeatureFlagAppExt as _, ZedPro};
  20use fs::Fs;
  21use futures::{
  22    channel::mpsc,
  23    future::{BoxFuture, LocalBoxFuture},
  24    join, SinkExt, Stream, StreamExt,
  25};
  26use gpui::{
  27    anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
  28    FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext,
  29    Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
  30};
  31use language::{Buffer, IndentKind, Point, Selection, TransactionId};
  32use language_model::{
  33    LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
  34    LanguageModelTextStream, Role,
  35};
  36use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
  37use language_models::report_assistant_event;
  38use multi_buffer::MultiBufferRow;
  39use parking_lot::Mutex;
  40use project::{CodeAction, ProjectTransaction};
  41use rope::Rope;
  42use settings::{update_settings_file, Settings, SettingsStore};
  43use smol::future::FutureExt;
  44use std::{
  45    cmp,
  46    future::{self, Future},
  47    iter, mem,
  48    ops::{Range, RangeInclusive},
  49    pin::Pin,
  50    rc::Rc,
  51    sync::Arc,
  52    task::{self, Poll},
  53    time::{Duration, Instant},
  54};
  55use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
  56use terminal_view::terminal_panel::TerminalPanel;
  57use text::{OffsetRangeExt, ToPoint as _};
  58use theme::ThemeSettings;
  59use ui::{
  60    prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
  61};
  62use util::{RangeExt, ResultExt};
  63use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
  64
  65pub fn init(
  66    fs: Arc<dyn Fs>,
  67    prompt_builder: Arc<PromptBuilder>,
  68    telemetry: Arc<Telemetry>,
  69    cx: &mut AppContext,
  70) {
  71    cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));
  72    cx.observe_new_views(|_, cx| {
  73        let workspace = cx.view().clone();
  74        InlineAssistant::update_global(cx, |inline_assistant, cx| {
  75            inline_assistant.register_workspace(&workspace, cx)
  76        })
  77    })
  78    .detach();
  79}
  80
  81const PROMPT_HISTORY_MAX_LEN: usize = 20;
  82
  83pub struct InlineAssistant {
  84    next_assist_id: InlineAssistId,
  85    next_assist_group_id: InlineAssistGroupId,
  86    assists: HashMap<InlineAssistId, InlineAssist>,
  87    assists_by_editor: HashMap<WeakView<Editor>, EditorInlineAssists>,
  88    assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,
  89    confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
  90    prompt_history: VecDeque<String>,
  91    prompt_builder: Arc<PromptBuilder>,
  92    telemetry: Arc<Telemetry>,
  93    fs: Arc<dyn Fs>,
  94}
  95
  96impl Global for InlineAssistant {}
  97
  98impl InlineAssistant {
  99    pub fn new(
 100        fs: Arc<dyn Fs>,
 101        prompt_builder: Arc<PromptBuilder>,
 102        telemetry: Arc<Telemetry>,
 103    ) -> Self {
 104        Self {
 105            next_assist_id: InlineAssistId::default(),
 106            next_assist_group_id: InlineAssistGroupId::default(),
 107            assists: HashMap::default(),
 108            assists_by_editor: HashMap::default(),
 109            assist_groups: HashMap::default(),
 110            confirmed_assists: HashMap::default(),
 111            prompt_history: VecDeque::default(),
 112            prompt_builder,
 113            telemetry,
 114            fs,
 115        }
 116    }
 117
 118    pub fn register_workspace(&mut self, workspace: &View<Workspace>, cx: &mut WindowContext) {
 119        cx.subscribe(workspace, |workspace, event, cx| {
 120            Self::update_global(cx, |this, cx| {
 121                this.handle_workspace_event(workspace, event, cx)
 122            });
 123        })
 124        .detach();
 125
 126        let workspace = workspace.downgrade();
 127        cx.observe_global::<SettingsStore>(move |cx| {
 128            let Some(workspace) = workspace.upgrade() else {
 129                return;
 130            };
 131            let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
 132                return;
 133            };
 134            let enabled = AssistantSettings::get_global(cx).enabled;
 135            terminal_panel.update(cx, |terminal_panel, cx| {
 136                terminal_panel.set_assistant_enabled(enabled, cx)
 137            });
 138        })
 139        .detach();
 140    }
 141
 142    fn handle_workspace_event(
 143        &mut self,
 144        workspace: View<Workspace>,
 145        event: &workspace::Event,
 146        cx: &mut WindowContext,
 147    ) {
 148        match event {
 149            workspace::Event::UserSavedItem { item, .. } => {
 150                // When the user manually saves an editor, automatically accepts all finished transformations.
 151                if let Some(editor) = item.upgrade().and_then(|item| item.act_as::<Editor>(cx)) {
 152                    if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) {
 153                        for assist_id in editor_assists.assist_ids.clone() {
 154                            let assist = &self.assists[&assist_id];
 155                            if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) {
 156                                self.finish_assist(assist_id, false, cx)
 157                            }
 158                        }
 159                    }
 160                }
 161            }
 162            workspace::Event::ItemAdded { item } => {
 163                self.register_workspace_item(&workspace, item.as_ref(), cx);
 164            }
 165            _ => (),
 166        }
 167    }
 168
 169    fn register_workspace_item(
 170        &mut self,
 171        workspace: &View<Workspace>,
 172        item: &dyn ItemHandle,
 173        cx: &mut WindowContext,
 174    ) {
 175        if let Some(editor) = item.act_as::<Editor>(cx) {
 176            editor.update(cx, |editor, cx| {
 177                editor.push_code_action_provider(
 178                    Rc::new(AssistantCodeActionProvider {
 179                        editor: cx.view().downgrade(),
 180                        workspace: workspace.downgrade(),
 181                    }),
 182                    cx,
 183                );
 184            });
 185        }
 186    }
 187
 188    pub fn assist(
 189        &mut self,
 190        editor: &View<Editor>,
 191        workspace: Option<WeakView<Workspace>>,
 192        assistant_panel: Option<&View<AssistantPanel>>,
 193        initial_prompt: Option<String>,
 194        cx: &mut WindowContext,
 195    ) {
 196        let (snapshot, initial_selections) = editor.update(cx, |editor, cx| {
 197            (
 198                editor.buffer().read(cx).snapshot(cx),
 199                editor.selections.all::<Point>(cx),
 200            )
 201        });
 202
 203        let mut selections = Vec::<Selection<Point>>::new();
 204        let mut newest_selection = None;
 205        for mut selection in initial_selections {
 206            if selection.end > selection.start {
 207                selection.start.column = 0;
 208                // If the selection ends at the start of the line, we don't want to include it.
 209                if selection.end.column == 0 {
 210                    selection.end.row -= 1;
 211                }
 212                selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row));
 213            }
 214
 215            if let Some(prev_selection) = selections.last_mut() {
 216                if selection.start <= prev_selection.end {
 217                    prev_selection.end = selection.end;
 218                    continue;
 219                }
 220            }
 221
 222            let latest_selection = newest_selection.get_or_insert_with(|| selection.clone());
 223            if selection.id > latest_selection.id {
 224                *latest_selection = selection.clone();
 225            }
 226            selections.push(selection);
 227        }
 228        let newest_selection = newest_selection.unwrap();
 229
 230        let mut codegen_ranges = Vec::new();
 231        for (excerpt_id, buffer, buffer_range) in
 232            snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
 233                snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
 234            }))
 235        {
 236            let start = Anchor {
 237                buffer_id: Some(buffer.remote_id()),
 238                excerpt_id,
 239                text_anchor: buffer.anchor_before(buffer_range.start),
 240            };
 241            let end = Anchor {
 242                buffer_id: Some(buffer.remote_id()),
 243                excerpt_id,
 244                text_anchor: buffer.anchor_after(buffer_range.end),
 245            };
 246            codegen_ranges.push(start..end);
 247
 248            if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
 249                self.telemetry.report_assistant_event(AssistantEvent {
 250                    conversation_id: None,
 251                    kind: AssistantKind::Inline,
 252                    phase: AssistantPhase::Invoked,
 253                    message_id: None,
 254                    model: model.telemetry_id(),
 255                    model_provider: model.provider_id().to_string(),
 256                    response_latency: None,
 257                    error_message: None,
 258                    language_name: buffer.language().map(|language| language.name().to_proto()),
 259                });
 260            }
 261        }
 262
 263        let assist_group_id = self.next_assist_group_id.post_inc();
 264        let prompt_buffer =
 265            cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx));
 266        let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
 267
 268        let mut assists = Vec::new();
 269        let mut assist_to_focus = None;
 270        for range in codegen_ranges {
 271            let assist_id = self.next_assist_id.post_inc();
 272            let codegen = cx.new_model(|cx| {
 273                Codegen::new(
 274                    editor.read(cx).buffer().clone(),
 275                    range.clone(),
 276                    None,
 277                    self.telemetry.clone(),
 278                    self.prompt_builder.clone(),
 279                    cx,
 280                )
 281            });
 282
 283            let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
 284            let prompt_editor = cx.new_view(|cx| {
 285                PromptEditor::new(
 286                    assist_id,
 287                    gutter_dimensions.clone(),
 288                    self.prompt_history.clone(),
 289                    prompt_buffer.clone(),
 290                    codegen.clone(),
 291                    editor,
 292                    assistant_panel,
 293                    workspace.clone(),
 294                    self.fs.clone(),
 295                    cx,
 296                )
 297            });
 298
 299            if assist_to_focus.is_none() {
 300                let focus_assist = if newest_selection.reversed {
 301                    range.start.to_point(&snapshot) == newest_selection.start
 302                } else {
 303                    range.end.to_point(&snapshot) == newest_selection.end
 304                };
 305                if focus_assist {
 306                    assist_to_focus = Some(assist_id);
 307                }
 308            }
 309
 310            let [prompt_block_id, end_block_id] =
 311                self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
 312
 313            assists.push((
 314                assist_id,
 315                range,
 316                prompt_editor,
 317                prompt_block_id,
 318                end_block_id,
 319            ));
 320        }
 321
 322        let editor_assists = self
 323            .assists_by_editor
 324            .entry(editor.downgrade())
 325            .or_insert_with(|| EditorInlineAssists::new(&editor, cx));
 326        let mut assist_group = InlineAssistGroup::new();
 327        for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists {
 328            self.assists.insert(
 329                assist_id,
 330                InlineAssist::new(
 331                    assist_id,
 332                    assist_group_id,
 333                    assistant_panel.is_some(),
 334                    editor,
 335                    &prompt_editor,
 336                    prompt_block_id,
 337                    end_block_id,
 338                    range,
 339                    prompt_editor.read(cx).codegen.clone(),
 340                    workspace.clone(),
 341                    cx,
 342                ),
 343            );
 344            assist_group.assist_ids.push(assist_id);
 345            editor_assists.assist_ids.push(assist_id);
 346        }
 347        self.assist_groups.insert(assist_group_id, assist_group);
 348
 349        if let Some(assist_id) = assist_to_focus {
 350            self.focus_assist(assist_id, cx);
 351        }
 352    }
 353
 354    #[allow(clippy::too_many_arguments)]
 355    pub fn suggest_assist(
 356        &mut self,
 357        editor: &View<Editor>,
 358        mut range: Range<Anchor>,
 359        initial_prompt: String,
 360        initial_transaction_id: Option<TransactionId>,
 361        focus: bool,
 362        workspace: Option<WeakView<Workspace>>,
 363        assistant_panel: Option<&View<AssistantPanel>>,
 364        cx: &mut WindowContext,
 365    ) -> InlineAssistId {
 366        let assist_group_id = self.next_assist_group_id.post_inc();
 367        let prompt_buffer = cx.new_model(|cx| Buffer::local(&initial_prompt, cx));
 368        let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx));
 369
 370        let assist_id = self.next_assist_id.post_inc();
 371
 372        let buffer = editor.read(cx).buffer().clone();
 373        {
 374            let snapshot = buffer.read(cx).read(cx);
 375            range.start = range.start.bias_left(&snapshot);
 376            range.end = range.end.bias_right(&snapshot);
 377        }
 378
 379        let codegen = cx.new_model(|cx| {
 380            Codegen::new(
 381                editor.read(cx).buffer().clone(),
 382                range.clone(),
 383                initial_transaction_id,
 384                self.telemetry.clone(),
 385                self.prompt_builder.clone(),
 386                cx,
 387            )
 388        });
 389
 390        let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
 391        let prompt_editor = cx.new_view(|cx| {
 392            PromptEditor::new(
 393                assist_id,
 394                gutter_dimensions.clone(),
 395                self.prompt_history.clone(),
 396                prompt_buffer.clone(),
 397                codegen.clone(),
 398                editor,
 399                assistant_panel,
 400                workspace.clone(),
 401                self.fs.clone(),
 402                cx,
 403            )
 404        });
 405
 406        let [prompt_block_id, end_block_id] =
 407            self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
 408
 409        let editor_assists = self
 410            .assists_by_editor
 411            .entry(editor.downgrade())
 412            .or_insert_with(|| EditorInlineAssists::new(&editor, cx));
 413
 414        let mut assist_group = InlineAssistGroup::new();
 415        self.assists.insert(
 416            assist_id,
 417            InlineAssist::new(
 418                assist_id,
 419                assist_group_id,
 420                assistant_panel.is_some(),
 421                editor,
 422                &prompt_editor,
 423                prompt_block_id,
 424                end_block_id,
 425                range,
 426                prompt_editor.read(cx).codegen.clone(),
 427                workspace.clone(),
 428                cx,
 429            ),
 430        );
 431        assist_group.assist_ids.push(assist_id);
 432        editor_assists.assist_ids.push(assist_id);
 433        self.assist_groups.insert(assist_group_id, assist_group);
 434
 435        if focus {
 436            self.focus_assist(assist_id, cx);
 437        }
 438
 439        assist_id
 440    }
 441
 442    fn insert_assist_blocks(
 443        &self,
 444        editor: &View<Editor>,
 445        range: &Range<Anchor>,
 446        prompt_editor: &View<PromptEditor>,
 447        cx: &mut WindowContext,
 448    ) -> [CustomBlockId; 2] {
 449        let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
 450            prompt_editor
 451                .editor
 452                .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1 + 2)
 453        });
 454        let assist_blocks = vec![
 455            BlockProperties {
 456                style: BlockStyle::Sticky,
 457                placement: BlockPlacement::Above(range.start),
 458                height: prompt_editor_height,
 459                render: build_assist_editor_renderer(prompt_editor),
 460                priority: 0,
 461            },
 462            BlockProperties {
 463                style: BlockStyle::Sticky,
 464                placement: BlockPlacement::Below(range.end),
 465                height: 0,
 466                render: Arc::new(|cx| {
 467                    v_flex()
 468                        .h_full()
 469                        .w_full()
 470                        .border_t_1()
 471                        .border_color(cx.theme().status().info_border)
 472                        .into_any_element()
 473                }),
 474                priority: 0,
 475            },
 476        ];
 477
 478        editor.update(cx, |editor, cx| {
 479            let block_ids = editor.insert_blocks(assist_blocks, None, cx);
 480            [block_ids[0], block_ids[1]]
 481        })
 482    }
 483
 484    fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
 485        let assist = &self.assists[&assist_id];
 486        let Some(decorations) = assist.decorations.as_ref() else {
 487            return;
 488        };
 489        let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap();
 490        let editor_assists = self.assists_by_editor.get_mut(&assist.editor).unwrap();
 491
 492        assist_group.active_assist_id = Some(assist_id);
 493        if assist_group.linked {
 494            for assist_id in &assist_group.assist_ids {
 495                if let Some(decorations) = self.assists[assist_id].decorations.as_ref() {
 496                    decorations.prompt_editor.update(cx, |prompt_editor, cx| {
 497                        prompt_editor.set_show_cursor_when_unfocused(true, cx)
 498                    });
 499                }
 500            }
 501        }
 502
 503        assist
 504            .editor
 505            .update(cx, |editor, cx| {
 506                let scroll_top = editor.scroll_position(cx).y;
 507                let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.);
 508                let prompt_row = editor
 509                    .row_for_block(decorations.prompt_block_id, cx)
 510                    .unwrap()
 511                    .0 as f32;
 512
 513                if (scroll_top..scroll_bottom).contains(&prompt_row) {
 514                    editor_assists.scroll_lock = Some(InlineAssistScrollLock {
 515                        assist_id,
 516                        distance_from_top: prompt_row - scroll_top,
 517                    });
 518                } else {
 519                    editor_assists.scroll_lock = None;
 520                }
 521            })
 522            .ok();
 523    }
 524
 525    fn handle_prompt_editor_focus_out(
 526        &mut self,
 527        assist_id: InlineAssistId,
 528        cx: &mut WindowContext,
 529    ) {
 530        let assist = &self.assists[&assist_id];
 531        let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap();
 532        if assist_group.active_assist_id == Some(assist_id) {
 533            assist_group.active_assist_id = None;
 534            if assist_group.linked {
 535                for assist_id in &assist_group.assist_ids {
 536                    if let Some(decorations) = self.assists[assist_id].decorations.as_ref() {
 537                        decorations.prompt_editor.update(cx, |prompt_editor, cx| {
 538                            prompt_editor.set_show_cursor_when_unfocused(false, cx)
 539                        });
 540                    }
 541                }
 542            }
 543        }
 544    }
 545
 546    fn handle_prompt_editor_event(
 547        &mut self,
 548        prompt_editor: View<PromptEditor>,
 549        event: &PromptEditorEvent,
 550        cx: &mut WindowContext,
 551    ) {
 552        let assist_id = prompt_editor.read(cx).id;
 553        match event {
 554            PromptEditorEvent::StartRequested => {
 555                self.start_assist(assist_id, cx);
 556            }
 557            PromptEditorEvent::StopRequested => {
 558                self.stop_assist(assist_id, cx);
 559            }
 560            PromptEditorEvent::ConfirmRequested => {
 561                self.finish_assist(assist_id, false, cx);
 562            }
 563            PromptEditorEvent::CancelRequested => {
 564                self.finish_assist(assist_id, true, cx);
 565            }
 566            PromptEditorEvent::DismissRequested => {
 567                self.dismiss_assist(assist_id, cx);
 568            }
 569        }
 570    }
 571
 572    fn handle_editor_newline(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
 573        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
 574            return;
 575        };
 576
 577        if editor.read(cx).selections.count() == 1 {
 578            let (selection, buffer) = editor.update(cx, |editor, cx| {
 579                (
 580                    editor.selections.newest::<usize>(cx),
 581                    editor.buffer().read(cx).snapshot(cx),
 582                )
 583            });
 584            for assist_id in &editor_assists.assist_ids {
 585                let assist = &self.assists[assist_id];
 586                let assist_range = assist.range.to_offset(&buffer);
 587                if assist_range.contains(&selection.start) && assist_range.contains(&selection.end)
 588                {
 589                    if matches!(assist.codegen.read(cx).status(cx), CodegenStatus::Pending) {
 590                        self.dismiss_assist(*assist_id, cx);
 591                    } else {
 592                        self.finish_assist(*assist_id, false, cx);
 593                    }
 594
 595                    return;
 596                }
 597            }
 598        }
 599
 600        cx.propagate();
 601    }
 602
 603    fn handle_editor_cancel(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
 604        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
 605            return;
 606        };
 607
 608        if editor.read(cx).selections.count() == 1 {
 609            let (selection, buffer) = editor.update(cx, |editor, cx| {
 610                (
 611                    editor.selections.newest::<usize>(cx),
 612                    editor.buffer().read(cx).snapshot(cx),
 613                )
 614            });
 615            let mut closest_assist_fallback = None;
 616            for assist_id in &editor_assists.assist_ids {
 617                let assist = &self.assists[assist_id];
 618                let assist_range = assist.range.to_offset(&buffer);
 619                if assist.decorations.is_some() {
 620                    if assist_range.contains(&selection.start)
 621                        && assist_range.contains(&selection.end)
 622                    {
 623                        self.focus_assist(*assist_id, cx);
 624                        return;
 625                    } else {
 626                        let distance_from_selection = assist_range
 627                            .start
 628                            .abs_diff(selection.start)
 629                            .min(assist_range.start.abs_diff(selection.end))
 630                            + assist_range
 631                                .end
 632                                .abs_diff(selection.start)
 633                                .min(assist_range.end.abs_diff(selection.end));
 634                        match closest_assist_fallback {
 635                            Some((_, old_distance)) => {
 636                                if distance_from_selection < old_distance {
 637                                    closest_assist_fallback =
 638                                        Some((assist_id, distance_from_selection));
 639                                }
 640                            }
 641                            None => {
 642                                closest_assist_fallback = Some((assist_id, distance_from_selection))
 643                            }
 644                        }
 645                    }
 646                }
 647            }
 648
 649            if let Some((&assist_id, _)) = closest_assist_fallback {
 650                self.focus_assist(assist_id, cx);
 651            }
 652        }
 653
 654        cx.propagate();
 655    }
 656
 657    fn handle_editor_release(&mut self, editor: WeakView<Editor>, cx: &mut WindowContext) {
 658        if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor) {
 659            for assist_id in editor_assists.assist_ids.clone() {
 660                self.finish_assist(assist_id, true, cx);
 661            }
 662        }
 663    }
 664
 665    fn handle_editor_change(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
 666        let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else {
 667            return;
 668        };
 669        let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() else {
 670            return;
 671        };
 672        let assist = &self.assists[&scroll_lock.assist_id];
 673        let Some(decorations) = assist.decorations.as_ref() else {
 674            return;
 675        };
 676
 677        editor.update(cx, |editor, cx| {
 678            let scroll_position = editor.scroll_position(cx);
 679            let target_scroll_top = editor
 680                .row_for_block(decorations.prompt_block_id, cx)
 681                .unwrap()
 682                .0 as f32
 683                - scroll_lock.distance_from_top;
 684            if target_scroll_top != scroll_position.y {
 685                editor.set_scroll_position(point(scroll_position.x, target_scroll_top), cx);
 686            }
 687        });
 688    }
 689
 690    fn handle_editor_event(
 691        &mut self,
 692        editor: View<Editor>,
 693        event: &EditorEvent,
 694        cx: &mut WindowContext,
 695    ) {
 696        let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) else {
 697            return;
 698        };
 699
 700        match event {
 701            EditorEvent::Edited { transaction_id } => {
 702                let buffer = editor.read(cx).buffer().read(cx);
 703                let edited_ranges =
 704                    buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx);
 705                let snapshot = buffer.snapshot(cx);
 706
 707                for assist_id in editor_assists.assist_ids.clone() {
 708                    let assist = &self.assists[&assist_id];
 709                    if matches!(
 710                        assist.codegen.read(cx).status(cx),
 711                        CodegenStatus::Error(_) | CodegenStatus::Done
 712                    ) {
 713                        let assist_range = assist.range.to_offset(&snapshot);
 714                        if edited_ranges
 715                            .iter()
 716                            .any(|range| range.overlaps(&assist_range))
 717                        {
 718                            self.finish_assist(assist_id, false, cx);
 719                        }
 720                    }
 721                }
 722            }
 723            EditorEvent::ScrollPositionChanged { .. } => {
 724                if let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() {
 725                    let assist = &self.assists[&scroll_lock.assist_id];
 726                    if let Some(decorations) = assist.decorations.as_ref() {
 727                        let distance_from_top = editor.update(cx, |editor, cx| {
 728                            let scroll_top = editor.scroll_position(cx).y;
 729                            let prompt_row = editor
 730                                .row_for_block(decorations.prompt_block_id, cx)
 731                                .unwrap()
 732                                .0 as f32;
 733                            prompt_row - scroll_top
 734                        });
 735
 736                        if distance_from_top != scroll_lock.distance_from_top {
 737                            editor_assists.scroll_lock = None;
 738                        }
 739                    }
 740                }
 741            }
 742            EditorEvent::SelectionsChanged { .. } => {
 743                for assist_id in editor_assists.assist_ids.clone() {
 744                    let assist = &self.assists[&assist_id];
 745                    if let Some(decorations) = assist.decorations.as_ref() {
 746                        if decorations.prompt_editor.focus_handle(cx).is_focused(cx) {
 747                            return;
 748                        }
 749                    }
 750                }
 751
 752                editor_assists.scroll_lock = None;
 753            }
 754            _ => {}
 755        }
 756    }
 757
 758    pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
 759        if let Some(assist) = self.assists.get(&assist_id) {
 760            let assist_group_id = assist.group_id;
 761            if self.assist_groups[&assist_group_id].linked {
 762                for assist_id in self.unlink_assist_group(assist_group_id, cx) {
 763                    self.finish_assist(assist_id, undo, cx);
 764                }
 765                return;
 766            }
 767        }
 768
 769        self.dismiss_assist(assist_id, cx);
 770
 771        if let Some(assist) = self.assists.remove(&assist_id) {
 772            if let hash_map::Entry::Occupied(mut entry) = self.assist_groups.entry(assist.group_id)
 773            {
 774                entry.get_mut().assist_ids.retain(|id| *id != assist_id);
 775                if entry.get().assist_ids.is_empty() {
 776                    entry.remove();
 777                }
 778            }
 779
 780            if let hash_map::Entry::Occupied(mut entry) =
 781                self.assists_by_editor.entry(assist.editor.clone())
 782            {
 783                entry.get_mut().assist_ids.retain(|id| *id != assist_id);
 784                if entry.get().assist_ids.is_empty() {
 785                    entry.remove();
 786                    if let Some(editor) = assist.editor.upgrade() {
 787                        self.update_editor_highlights(&editor, cx);
 788                    }
 789                } else {
 790                    entry.get().highlight_updates.send(()).ok();
 791                }
 792            }
 793
 794            let active_alternative = assist.codegen.read(cx).active_alternative().clone();
 795            let message_id = active_alternative.read(cx).message_id.clone();
 796
 797            if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
 798                let language_name = assist.editor.upgrade().and_then(|editor| {
 799                    let multibuffer = editor.read(cx).buffer().read(cx);
 800                    let multibuffer_snapshot = multibuffer.snapshot(cx);
 801                    let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
 802                    ranges
 803                        .first()
 804                        .and_then(|(excerpt, _)| excerpt.buffer().language())
 805                        .map(|language| language.name())
 806                });
 807                report_assistant_event(
 808                    AssistantEvent {
 809                        conversation_id: None,
 810                        kind: AssistantKind::Inline,
 811                        message_id,
 812                        phase: if undo {
 813                            AssistantPhase::Rejected
 814                        } else {
 815                            AssistantPhase::Accepted
 816                        },
 817                        model: model.telemetry_id(),
 818                        model_provider: model.provider_id().to_string(),
 819                        response_latency: None,
 820                        error_message: None,
 821                        language_name: language_name.map(|name| name.to_proto()),
 822                    },
 823                    Some(self.telemetry.clone()),
 824                    cx.http_client(),
 825                    model.api_key(cx),
 826                    cx.background_executor(),
 827                );
 828            }
 829
 830            if undo {
 831                assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
 832            } else {
 833                self.confirmed_assists.insert(assist_id, active_alternative);
 834            }
 835        }
 836    }
 837
 838    fn dismiss_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
 839        let Some(assist) = self.assists.get_mut(&assist_id) else {
 840            return false;
 841        };
 842        let Some(editor) = assist.editor.upgrade() else {
 843            return false;
 844        };
 845        let Some(decorations) = assist.decorations.take() else {
 846            return false;
 847        };
 848
 849        editor.update(cx, |editor, cx| {
 850            let mut to_remove = decorations.removed_line_block_ids;
 851            to_remove.insert(decorations.prompt_block_id);
 852            to_remove.insert(decorations.end_block_id);
 853            editor.remove_blocks(to_remove, None, cx);
 854        });
 855
 856        if decorations
 857            .prompt_editor
 858            .focus_handle(cx)
 859            .contains_focused(cx)
 860        {
 861            self.focus_next_assist(assist_id, cx);
 862        }
 863
 864        if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) {
 865            if editor_assists
 866                .scroll_lock
 867                .as_ref()
 868                .map_or(false, |lock| lock.assist_id == assist_id)
 869            {
 870                editor_assists.scroll_lock = None;
 871            }
 872            editor_assists.highlight_updates.send(()).ok();
 873        }
 874
 875        true
 876    }
 877
 878    fn focus_next_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
 879        let Some(assist) = self.assists.get(&assist_id) else {
 880            return;
 881        };
 882
 883        let assist_group = &self.assist_groups[&assist.group_id];
 884        let assist_ix = assist_group
 885            .assist_ids
 886            .iter()
 887            .position(|id| *id == assist_id)
 888            .unwrap();
 889        let assist_ids = assist_group
 890            .assist_ids
 891            .iter()
 892            .skip(assist_ix + 1)
 893            .chain(assist_group.assist_ids.iter().take(assist_ix));
 894
 895        for assist_id in assist_ids {
 896            let assist = &self.assists[assist_id];
 897            if assist.decorations.is_some() {
 898                self.focus_assist(*assist_id, cx);
 899                return;
 900            }
 901        }
 902
 903        assist.editor.update(cx, |editor, cx| editor.focus(cx)).ok();
 904    }
 905
 906    fn focus_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
 907        let Some(assist) = self.assists.get(&assist_id) else {
 908            return;
 909        };
 910
 911        if let Some(decorations) = assist.decorations.as_ref() {
 912            decorations.prompt_editor.update(cx, |prompt_editor, cx| {
 913                prompt_editor.editor.update(cx, |editor, cx| {
 914                    editor.focus(cx);
 915                    editor.select_all(&SelectAll, cx);
 916                })
 917            });
 918        }
 919
 920        self.scroll_to_assist(assist_id, cx);
 921    }
 922
 923    pub fn scroll_to_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
 924        let Some(assist) = self.assists.get(&assist_id) else {
 925            return;
 926        };
 927        let Some(editor) = assist.editor.upgrade() else {
 928            return;
 929        };
 930
 931        let position = assist.range.start;
 932        editor.update(cx, |editor, cx| {
 933            editor.change_selections(None, cx, |selections| {
 934                selections.select_anchor_ranges([position..position])
 935            });
 936
 937            let mut scroll_target_top;
 938            let mut scroll_target_bottom;
 939            if let Some(decorations) = assist.decorations.as_ref() {
 940                scroll_target_top = editor
 941                    .row_for_block(decorations.prompt_block_id, cx)
 942                    .unwrap()
 943                    .0 as f32;
 944                scroll_target_bottom = editor
 945                    .row_for_block(decorations.end_block_id, cx)
 946                    .unwrap()
 947                    .0 as f32;
 948            } else {
 949                let snapshot = editor.snapshot(cx);
 950                let start_row = assist
 951                    .range
 952                    .start
 953                    .to_display_point(&snapshot.display_snapshot)
 954                    .row();
 955                scroll_target_top = start_row.0 as f32;
 956                scroll_target_bottom = scroll_target_top + 1.;
 957            }
 958            scroll_target_top -= editor.vertical_scroll_margin() as f32;
 959            scroll_target_bottom += editor.vertical_scroll_margin() as f32;
 960
 961            let height_in_lines = editor.visible_line_count().unwrap_or(0.);
 962            let scroll_top = editor.scroll_position(cx).y;
 963            let scroll_bottom = scroll_top + height_in_lines;
 964
 965            if scroll_target_top < scroll_top {
 966                editor.set_scroll_position(point(0., scroll_target_top), cx);
 967            } else if scroll_target_bottom > scroll_bottom {
 968                if (scroll_target_bottom - scroll_target_top) <= height_in_lines {
 969                    editor
 970                        .set_scroll_position(point(0., scroll_target_bottom - height_in_lines), cx);
 971                } else {
 972                    editor.set_scroll_position(point(0., scroll_target_top), cx);
 973                }
 974            }
 975        });
 976    }
 977
 978    fn unlink_assist_group(
 979        &mut self,
 980        assist_group_id: InlineAssistGroupId,
 981        cx: &mut WindowContext,
 982    ) -> Vec<InlineAssistId> {
 983        let assist_group = self.assist_groups.get_mut(&assist_group_id).unwrap();
 984        assist_group.linked = false;
 985        for assist_id in &assist_group.assist_ids {
 986            let assist = self.assists.get_mut(assist_id).unwrap();
 987            if let Some(editor_decorations) = assist.decorations.as_ref() {
 988                editor_decorations
 989                    .prompt_editor
 990                    .update(cx, |prompt_editor, cx| prompt_editor.unlink(cx));
 991            }
 992        }
 993        assist_group.assist_ids.clone()
 994    }
 995
 996    pub fn start_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
 997        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
 998            assist
 999        } else {
1000            return;
1001        };
1002
1003        let assist_group_id = assist.group_id;
1004        if self.assist_groups[&assist_group_id].linked {
1005            for assist_id in self.unlink_assist_group(assist_group_id, cx) {
1006                self.start_assist(assist_id, cx);
1007            }
1008            return;
1009        }
1010
1011        let Some(user_prompt) = assist.user_prompt(cx) else {
1012            return;
1013        };
1014
1015        self.prompt_history.retain(|prompt| *prompt != user_prompt);
1016        self.prompt_history.push_back(user_prompt.clone());
1017        if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN {
1018            self.prompt_history.pop_front();
1019        }
1020
1021        let assistant_panel_context = assist.assistant_panel_context(cx);
1022
1023        assist
1024            .codegen
1025            .update(cx, |codegen, cx| {
1026                codegen.start(user_prompt, assistant_panel_context, cx)
1027            })
1028            .log_err();
1029    }
1030
1031    pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
1032        let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
1033            assist
1034        } else {
1035            return;
1036        };
1037
1038        assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
1039    }
1040
1041    fn update_editor_highlights(&self, editor: &View<Editor>, cx: &mut WindowContext) {
1042        let mut gutter_pending_ranges = Vec::new();
1043        let mut gutter_transformed_ranges = Vec::new();
1044        let mut foreground_ranges = Vec::new();
1045        let mut inserted_row_ranges = Vec::new();
1046        let empty_assist_ids = Vec::new();
1047        let assist_ids = self
1048            .assists_by_editor
1049            .get(&editor.downgrade())
1050            .map_or(&empty_assist_ids, |editor_assists| {
1051                &editor_assists.assist_ids
1052            });
1053
1054        for assist_id in assist_ids {
1055            if let Some(assist) = self.assists.get(assist_id) {
1056                let codegen = assist.codegen.read(cx);
1057                let buffer = codegen.buffer(cx).read(cx).read(cx);
1058                foreground_ranges.extend(codegen.last_equal_ranges(cx).iter().cloned());
1059
1060                let pending_range =
1061                    codegen.edit_position(cx).unwrap_or(assist.range.start)..assist.range.end;
1062                if pending_range.end.to_offset(&buffer) > pending_range.start.to_offset(&buffer) {
1063                    gutter_pending_ranges.push(pending_range);
1064                }
1065
1066                if let Some(edit_position) = codegen.edit_position(cx) {
1067                    let edited_range = assist.range.start..edit_position;
1068                    if edited_range.end.to_offset(&buffer) > edited_range.start.to_offset(&buffer) {
1069                        gutter_transformed_ranges.push(edited_range);
1070                    }
1071                }
1072
1073                if assist.decorations.is_some() {
1074                    inserted_row_ranges
1075                        .extend(codegen.diff(cx).inserted_row_ranges.iter().cloned());
1076                }
1077            }
1078        }
1079
1080        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
1081        merge_ranges(&mut foreground_ranges, &snapshot);
1082        merge_ranges(&mut gutter_pending_ranges, &snapshot);
1083        merge_ranges(&mut gutter_transformed_ranges, &snapshot);
1084        editor.update(cx, |editor, cx| {
1085            enum GutterPendingRange {}
1086            if gutter_pending_ranges.is_empty() {
1087                editor.clear_gutter_highlights::<GutterPendingRange>(cx);
1088            } else {
1089                editor.highlight_gutter::<GutterPendingRange>(
1090                    &gutter_pending_ranges,
1091                    |cx| cx.theme().status().info_background,
1092                    cx,
1093                )
1094            }
1095
1096            enum GutterTransformedRange {}
1097            if gutter_transformed_ranges.is_empty() {
1098                editor.clear_gutter_highlights::<GutterTransformedRange>(cx);
1099            } else {
1100                editor.highlight_gutter::<GutterTransformedRange>(
1101                    &gutter_transformed_ranges,
1102                    |cx| cx.theme().status().info,
1103                    cx,
1104                )
1105            }
1106
1107            if foreground_ranges.is_empty() {
1108                editor.clear_highlights::<InlineAssist>(cx);
1109            } else {
1110                editor.highlight_text::<InlineAssist>(
1111                    foreground_ranges,
1112                    HighlightStyle {
1113                        fade_out: Some(0.6),
1114                        ..Default::default()
1115                    },
1116                    cx,
1117                );
1118            }
1119
1120            editor.clear_row_highlights::<InlineAssist>();
1121            for row_range in inserted_row_ranges {
1122                editor.highlight_rows::<InlineAssist>(
1123                    row_range,
1124                    cx.theme().status().info_background,
1125                    false,
1126                    cx,
1127                );
1128            }
1129        });
1130    }
1131
1132    fn update_editor_blocks(
1133        &mut self,
1134        editor: &View<Editor>,
1135        assist_id: InlineAssistId,
1136        cx: &mut WindowContext,
1137    ) {
1138        let Some(assist) = self.assists.get_mut(&assist_id) else {
1139            return;
1140        };
1141        let Some(decorations) = assist.decorations.as_mut() else {
1142            return;
1143        };
1144
1145        let codegen = assist.codegen.read(cx);
1146        let old_snapshot = codegen.snapshot(cx);
1147        let old_buffer = codegen.old_buffer(cx);
1148        let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
1149
1150        editor.update(cx, |editor, cx| {
1151            let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
1152            editor.remove_blocks(old_blocks, None, cx);
1153
1154            let mut new_blocks = Vec::new();
1155            for (new_row, old_row_range) in deleted_row_ranges {
1156                let (_, buffer_start) = old_snapshot
1157                    .point_to_buffer_offset(Point::new(*old_row_range.start(), 0))
1158                    .unwrap();
1159                let (_, buffer_end) = old_snapshot
1160                    .point_to_buffer_offset(Point::new(
1161                        *old_row_range.end(),
1162                        old_snapshot.line_len(MultiBufferRow(*old_row_range.end())),
1163                    ))
1164                    .unwrap();
1165
1166                let deleted_lines_editor = cx.new_view(|cx| {
1167                    let multi_buffer = cx.new_model(|_| {
1168                        MultiBuffer::without_headers(language::Capability::ReadOnly)
1169                    });
1170                    multi_buffer.update(cx, |multi_buffer, cx| {
1171                        multi_buffer.push_excerpts(
1172                            old_buffer.clone(),
1173                            Some(ExcerptRange {
1174                                context: buffer_start..buffer_end,
1175                                primary: None,
1176                            }),
1177                            cx,
1178                        );
1179                    });
1180
1181                    enum DeletedLines {}
1182                    let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
1183                    editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
1184                    editor.set_show_wrap_guides(false, cx);
1185                    editor.set_show_gutter(false, cx);
1186                    editor.scroll_manager.set_forbid_vertical_scroll(true);
1187                    editor.set_read_only(true);
1188                    editor.set_show_inline_completions(Some(false), cx);
1189                    editor.highlight_rows::<DeletedLines>(
1190                        Anchor::min()..Anchor::max(),
1191                        cx.theme().status().deleted_background,
1192                        false,
1193                        cx,
1194                    );
1195                    editor
1196                });
1197
1198                let height =
1199                    deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
1200                new_blocks.push(BlockProperties {
1201                    placement: BlockPlacement::Above(new_row),
1202                    height,
1203                    style: BlockStyle::Flex,
1204                    render: Arc::new(move |cx| {
1205                        div()
1206                            .block_mouse_down()
1207                            .bg(cx.theme().status().deleted_background)
1208                            .size_full()
1209                            .h(height as f32 * cx.line_height())
1210                            .pl(cx.gutter_dimensions.full_width())
1211                            .child(deleted_lines_editor.clone())
1212                            .into_any_element()
1213                    }),
1214                    priority: 0,
1215                });
1216            }
1217
1218            decorations.removed_line_block_ids = editor
1219                .insert_blocks(new_blocks, None, cx)
1220                .into_iter()
1221                .collect();
1222        })
1223    }
1224}
1225
1226struct EditorInlineAssists {
1227    assist_ids: Vec<InlineAssistId>,
1228    scroll_lock: Option<InlineAssistScrollLock>,
1229    highlight_updates: async_watch::Sender<()>,
1230    _update_highlights: Task<Result<()>>,
1231    _subscriptions: Vec<gpui::Subscription>,
1232}
1233
1234struct InlineAssistScrollLock {
1235    assist_id: InlineAssistId,
1236    distance_from_top: f32,
1237}
1238
1239impl EditorInlineAssists {
1240    #[allow(clippy::too_many_arguments)]
1241    fn new(editor: &View<Editor>, cx: &mut WindowContext) -> Self {
1242        let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
1243        Self {
1244            assist_ids: Vec::new(),
1245            scroll_lock: None,
1246            highlight_updates: highlight_updates_tx,
1247            _update_highlights: cx.spawn(|mut cx| {
1248                let editor = editor.downgrade();
1249                async move {
1250                    while let Ok(()) = highlight_updates_rx.changed().await {
1251                        let editor = editor.upgrade().context("editor was dropped")?;
1252                        cx.update_global(|assistant: &mut InlineAssistant, cx| {
1253                            assistant.update_editor_highlights(&editor, cx);
1254                        })?;
1255                    }
1256                    Ok(())
1257                }
1258            }),
1259            _subscriptions: vec![
1260                cx.observe_release(editor, {
1261                    let editor = editor.downgrade();
1262                    |_, cx| {
1263                        InlineAssistant::update_global(cx, |this, cx| {
1264                            this.handle_editor_release(editor, cx);
1265                        })
1266                    }
1267                }),
1268                cx.observe(editor, move |editor, cx| {
1269                    InlineAssistant::update_global(cx, |this, cx| {
1270                        this.handle_editor_change(editor, cx)
1271                    })
1272                }),
1273                cx.subscribe(editor, move |editor, event, cx| {
1274                    InlineAssistant::update_global(cx, |this, cx| {
1275                        this.handle_editor_event(editor, event, cx)
1276                    })
1277                }),
1278                editor.update(cx, |editor, cx| {
1279                    let editor_handle = cx.view().downgrade();
1280                    editor.register_action(
1281                        move |_: &editor::actions::Newline, cx: &mut WindowContext| {
1282                            InlineAssistant::update_global(cx, |this, cx| {
1283                                if let Some(editor) = editor_handle.upgrade() {
1284                                    this.handle_editor_newline(editor, cx)
1285                                }
1286                            })
1287                        },
1288                    )
1289                }),
1290                editor.update(cx, |editor, cx| {
1291                    let editor_handle = cx.view().downgrade();
1292                    editor.register_action(
1293                        move |_: &editor::actions::Cancel, cx: &mut WindowContext| {
1294                            InlineAssistant::update_global(cx, |this, cx| {
1295                                if let Some(editor) = editor_handle.upgrade() {
1296                                    this.handle_editor_cancel(editor, cx)
1297                                }
1298                            })
1299                        },
1300                    )
1301                }),
1302            ],
1303        }
1304    }
1305}
1306
1307struct InlineAssistGroup {
1308    assist_ids: Vec<InlineAssistId>,
1309    linked: bool,
1310    active_assist_id: Option<InlineAssistId>,
1311}
1312
1313impl InlineAssistGroup {
1314    fn new() -> Self {
1315        Self {
1316            assist_ids: Vec::new(),
1317            linked: true,
1318            active_assist_id: None,
1319        }
1320    }
1321}
1322
1323fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
1324    let editor = editor.clone();
1325    Arc::new(move |cx: &mut BlockContext| {
1326        *editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
1327        editor.clone().into_any_element()
1328    })
1329}
1330
1331#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
1332pub struct InlineAssistId(usize);
1333
1334impl InlineAssistId {
1335    fn post_inc(&mut self) -> InlineAssistId {
1336        let id = *self;
1337        self.0 += 1;
1338        id
1339    }
1340}
1341
1342#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
1343struct InlineAssistGroupId(usize);
1344
1345impl InlineAssistGroupId {
1346    fn post_inc(&mut self) -> InlineAssistGroupId {
1347        let id = *self;
1348        self.0 += 1;
1349        id
1350    }
1351}
1352
1353enum PromptEditorEvent {
1354    StartRequested,
1355    StopRequested,
1356    ConfirmRequested,
1357    CancelRequested,
1358    DismissRequested,
1359}
1360
1361struct PromptEditor {
1362    id: InlineAssistId,
1363    editor: View<Editor>,
1364    language_model_selector: View<LanguageModelSelector>,
1365    edited_since_done: bool,
1366    gutter_dimensions: Arc<Mutex<GutterDimensions>>,
1367    prompt_history: VecDeque<String>,
1368    prompt_history_ix: Option<usize>,
1369    pending_prompt: String,
1370    codegen: Model<Codegen>,
1371    _codegen_subscription: Subscription,
1372    editor_subscriptions: Vec<Subscription>,
1373    pending_token_count: Task<Result<()>>,
1374    token_counts: Option<TokenCounts>,
1375    _token_count_subscriptions: Vec<Subscription>,
1376    workspace: Option<WeakView<Workspace>>,
1377    show_rate_limit_notice: bool,
1378}
1379
1380#[derive(Copy, Clone)]
1381pub struct TokenCounts {
1382    total: usize,
1383    assistant_panel: usize,
1384}
1385
1386impl EventEmitter<PromptEditorEvent> for PromptEditor {}
1387
1388impl Render for PromptEditor {
1389    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1390        let gutter_dimensions = *self.gutter_dimensions.lock();
1391        let codegen = self.codegen.read(cx);
1392
1393        let mut buttons = Vec::new();
1394        if codegen.alternative_count(cx) > 1 {
1395            buttons.push(self.render_cycle_controls(cx));
1396        }
1397
1398        let status = codegen.status(cx);
1399        buttons.extend(match status {
1400            CodegenStatus::Idle => {
1401                vec![
1402                    IconButton::new("cancel", IconName::Close)
1403                        .icon_color(Color::Muted)
1404                        .shape(IconButtonShape::Square)
1405                        .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
1406                        .on_click(
1407                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1408                        )
1409                        .into_any_element(),
1410                    IconButton::new("start", IconName::SparkleAlt)
1411                        .icon_color(Color::Muted)
1412                        .shape(IconButtonShape::Square)
1413                        .tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
1414                        .on_click(
1415                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
1416                        )
1417                        .into_any_element(),
1418                ]
1419            }
1420            CodegenStatus::Pending => {
1421                vec![
1422                    IconButton::new("cancel", IconName::Close)
1423                        .icon_color(Color::Muted)
1424                        .shape(IconButtonShape::Square)
1425                        .tooltip(|cx| Tooltip::text("Cancel Assist", cx))
1426                        .on_click(
1427                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1428                        )
1429                        .into_any_element(),
1430                    IconButton::new("stop", IconName::Stop)
1431                        .icon_color(Color::Error)
1432                        .shape(IconButtonShape::Square)
1433                        .tooltip(|cx| {
1434                            Tooltip::with_meta(
1435                                "Interrupt Transformation",
1436                                Some(&menu::Cancel),
1437                                "Changes won't be discarded",
1438                                cx,
1439                            )
1440                        })
1441                        .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
1442                        .into_any_element(),
1443                ]
1444            }
1445            CodegenStatus::Error(_) | CodegenStatus::Done => {
1446                let must_rerun =
1447                    self.edited_since_done || matches!(status, CodegenStatus::Error(_));
1448                // when accept button isn't visible, then restart maps to confirm
1449                // when accept button is visible, then restart must be mapped to an alternate keyboard shortcut
1450                let restart_key: &dyn gpui::Action = if must_rerun {
1451                    &menu::Confirm
1452                } else {
1453                    &menu::Restart
1454                };
1455                vec![
1456                    IconButton::new("cancel", IconName::Close)
1457                        .icon_color(Color::Muted)
1458                        .shape(IconButtonShape::Square)
1459                        .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
1460                        .on_click(
1461                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1462                        )
1463                        .into_any_element(),
1464                    IconButton::new("restart", IconName::RotateCw)
1465                        .icon_color(Color::Muted)
1466                        .shape(IconButtonShape::Square)
1467                        .tooltip(|cx| {
1468                            Tooltip::with_meta(
1469                                "Regenerate Transformation",
1470                                Some(restart_key),
1471                                "Current change will be discarded",
1472                                cx,
1473                            )
1474                        })
1475                        .on_click(cx.listener(|_, _, cx| {
1476                            cx.emit(PromptEditorEvent::StartRequested);
1477                        }))
1478                        .into_any_element(),
1479                    if !must_rerun {
1480                        IconButton::new("confirm", IconName::Check)
1481                            .icon_color(Color::Info)
1482                            .shape(IconButtonShape::Square)
1483                            .tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
1484                            .on_click(cx.listener(|_, _, cx| {
1485                                cx.emit(PromptEditorEvent::ConfirmRequested);
1486                            }))
1487                            .into_any_element()
1488                    } else {
1489                        div().into_any_element()
1490                    },
1491                ]
1492            }
1493        });
1494
1495        h_flex()
1496            .key_context("PromptEditor")
1497            .bg(cx.theme().colors().editor_background)
1498            .block_mouse_down()
1499            .cursor(CursorStyle::Arrow)
1500            .border_y_1()
1501            .border_color(cx.theme().status().info_border)
1502            .size_full()
1503            .py(cx.line_height() / 2.5)
1504            .on_action(cx.listener(Self::confirm))
1505            .on_action(cx.listener(Self::cancel))
1506            .on_action(cx.listener(Self::restart))
1507            .on_action(cx.listener(Self::move_up))
1508            .on_action(cx.listener(Self::move_down))
1509            .capture_action(cx.listener(Self::cycle_prev))
1510            .capture_action(cx.listener(Self::cycle_next))
1511            .child(
1512                h_flex()
1513                    .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
1514                    .justify_center()
1515                    .gap_2()
1516                    .child(LanguageModelSelectorPopoverMenu::new(
1517                        self.language_model_selector.clone(),
1518                        IconButton::new("context", IconName::SettingsAlt)
1519                            .shape(IconButtonShape::Square)
1520                            .icon_size(IconSize::Small)
1521                            .icon_color(Color::Muted)
1522                            .tooltip(move |cx| {
1523                                Tooltip::with_meta(
1524                                    format!(
1525                                        "Using {}",
1526                                        LanguageModelRegistry::read_global(cx)
1527                                            .active_model()
1528                                            .map(|model| model.name().0)
1529                                            .unwrap_or_else(|| "No model selected".into()),
1530                                    ),
1531                                    None,
1532                                    "Change Model",
1533                                    cx,
1534                                )
1535                            }),
1536                    ))
1537                    .map(|el| {
1538                        let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
1539                            return el;
1540                        };
1541
1542                        let error_message = SharedString::from(error.to_string());
1543                        if error.error_code() == proto::ErrorCode::RateLimitExceeded
1544                            && cx.has_flag::<ZedPro>()
1545                        {
1546                            el.child(
1547                                v_flex()
1548                                    .child(
1549                                        IconButton::new("rate-limit-error", IconName::XCircle)
1550                                            .toggle_state(self.show_rate_limit_notice)
1551                                            .shape(IconButtonShape::Square)
1552                                            .icon_size(IconSize::Small)
1553                                            .on_click(cx.listener(Self::toggle_rate_limit_notice)),
1554                                    )
1555                                    .children(self.show_rate_limit_notice.then(|| {
1556                                        deferred(
1557                                            anchored()
1558                                                .position_mode(gpui::AnchoredPositionMode::Local)
1559                                                .position(point(px(0.), px(24.)))
1560                                                .anchor(gpui::Corner::TopLeft)
1561                                                .child(self.render_rate_limit_notice(cx)),
1562                                        )
1563                                    })),
1564                            )
1565                        } else {
1566                            el.child(
1567                                div()
1568                                    .id("error")
1569                                    .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
1570                                    .child(
1571                                        Icon::new(IconName::XCircle)
1572                                            .size(IconSize::Small)
1573                                            .color(Color::Error),
1574                                    ),
1575                            )
1576                        }
1577                    }),
1578            )
1579            .child(div().flex_1().child(self.render_prompt_editor(cx)))
1580            .child(
1581                h_flex()
1582                    .gap_2()
1583                    .pr_6()
1584                    .children(self.render_token_count(cx))
1585                    .children(buttons),
1586            )
1587    }
1588}
1589
1590impl FocusableView for PromptEditor {
1591    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1592        self.editor.focus_handle(cx)
1593    }
1594}
1595
1596impl PromptEditor {
1597    const MAX_LINES: u8 = 8;
1598
1599    #[allow(clippy::too_many_arguments)]
1600    fn new(
1601        id: InlineAssistId,
1602        gutter_dimensions: Arc<Mutex<GutterDimensions>>,
1603        prompt_history: VecDeque<String>,
1604        prompt_buffer: Model<MultiBuffer>,
1605        codegen: Model<Codegen>,
1606        parent_editor: &View<Editor>,
1607        assistant_panel: Option<&View<AssistantPanel>>,
1608        workspace: Option<WeakView<Workspace>>,
1609        fs: Arc<dyn Fs>,
1610        cx: &mut ViewContext<Self>,
1611    ) -> Self {
1612        let prompt_editor = cx.new_view(|cx| {
1613            let mut editor = Editor::new(
1614                EditorMode::AutoHeight {
1615                    max_lines: Self::MAX_LINES as usize,
1616                },
1617                prompt_buffer,
1618                None,
1619                false,
1620                cx,
1621            );
1622            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
1623            // Since the prompt editors for all inline assistants are linked,
1624            // always show the cursor (even when it isn't focused) because
1625            // typing in one will make what you typed appear in all of them.
1626            editor.set_show_cursor_when_unfocused(true, cx);
1627            editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
1628            editor
1629        });
1630
1631        let mut token_count_subscriptions = Vec::new();
1632        token_count_subscriptions
1633            .push(cx.subscribe(parent_editor, Self::handle_parent_editor_event));
1634        if let Some(assistant_panel) = assistant_panel {
1635            token_count_subscriptions
1636                .push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
1637        }
1638
1639        let mut this = Self {
1640            id,
1641            editor: prompt_editor,
1642            language_model_selector: cx.new_view(|cx| {
1643                let fs = fs.clone();
1644                LanguageModelSelector::new(
1645                    move |model, cx| {
1646                        update_settings_file::<AssistantSettings>(
1647                            fs.clone(),
1648                            cx,
1649                            move |settings, _| settings.set_model(model.clone()),
1650                        );
1651                    },
1652                    cx,
1653                )
1654            }),
1655            edited_since_done: false,
1656            gutter_dimensions,
1657            prompt_history,
1658            prompt_history_ix: None,
1659            pending_prompt: String::new(),
1660            _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
1661            editor_subscriptions: Vec::new(),
1662            codegen,
1663            pending_token_count: Task::ready(Ok(())),
1664            token_counts: None,
1665            _token_count_subscriptions: token_count_subscriptions,
1666            workspace,
1667            show_rate_limit_notice: false,
1668        };
1669        this.count_tokens(cx);
1670        this.subscribe_to_editor(cx);
1671        this
1672    }
1673
1674    fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
1675        self.editor_subscriptions.clear();
1676        self.editor_subscriptions
1677            .push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
1678    }
1679
1680    fn set_show_cursor_when_unfocused(
1681        &mut self,
1682        show_cursor_when_unfocused: bool,
1683        cx: &mut ViewContext<Self>,
1684    ) {
1685        self.editor.update(cx, |editor, cx| {
1686            editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
1687        });
1688    }
1689
1690    fn unlink(&mut self, cx: &mut ViewContext<Self>) {
1691        let prompt = self.prompt(cx);
1692        let focus = self.editor.focus_handle(cx).contains_focused(cx);
1693        self.editor = cx.new_view(|cx| {
1694            let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
1695            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
1696            editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
1697            editor.set_placeholder_text("Add a prompt…", cx);
1698            editor.set_text(prompt, cx);
1699            if focus {
1700                editor.focus(cx);
1701            }
1702            editor
1703        });
1704        self.subscribe_to_editor(cx);
1705    }
1706
1707    fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
1708        let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
1709            .map(|keybinding| format!("{keybinding} for context"))
1710            .unwrap_or_default();
1711
1712        let action = if codegen.is_insertion {
1713            "Generate"
1714        } else {
1715            "Transform"
1716        };
1717
1718        format!("{action}{context_keybinding} • ↓↑ for history")
1719    }
1720
1721    fn prompt(&self, cx: &AppContext) -> String {
1722        self.editor.read(cx).text(cx)
1723    }
1724
1725    fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
1726        self.show_rate_limit_notice = !self.show_rate_limit_notice;
1727        if self.show_rate_limit_notice {
1728            cx.focus_view(&self.editor);
1729        }
1730        cx.notify();
1731    }
1732
1733    fn handle_parent_editor_event(
1734        &mut self,
1735        _: View<Editor>,
1736        event: &EditorEvent,
1737        cx: &mut ViewContext<Self>,
1738    ) {
1739        if let EditorEvent::BufferEdited { .. } = event {
1740            self.count_tokens(cx);
1741        }
1742    }
1743
1744    fn handle_assistant_panel_event(
1745        &mut self,
1746        _: View<AssistantPanel>,
1747        event: &AssistantPanelEvent,
1748        cx: &mut ViewContext<Self>,
1749    ) {
1750        let AssistantPanelEvent::ContextEdited { .. } = event;
1751        self.count_tokens(cx);
1752    }
1753
1754    fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
1755        let assist_id = self.id;
1756        self.pending_token_count = cx.spawn(|this, mut cx| async move {
1757            cx.background_executor().timer(Duration::from_secs(1)).await;
1758            let token_count = cx
1759                .update_global(|inline_assistant: &mut InlineAssistant, cx| {
1760                    let assist = inline_assistant
1761                        .assists
1762                        .get(&assist_id)
1763                        .context("assist not found")?;
1764                    anyhow::Ok(assist.count_tokens(cx))
1765                })??
1766                .await?;
1767
1768            this.update(&mut cx, |this, cx| {
1769                this.token_counts = Some(token_count);
1770                cx.notify();
1771            })
1772        })
1773    }
1774
1775    fn handle_prompt_editor_events(
1776        &mut self,
1777        _: View<Editor>,
1778        event: &EditorEvent,
1779        cx: &mut ViewContext<Self>,
1780    ) {
1781        match event {
1782            EditorEvent::Edited { .. } => {
1783                if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
1784                    workspace
1785                        .update(cx, |workspace, cx| {
1786                            let is_via_ssh = workspace
1787                                .project()
1788                                .update(cx, |project, _| project.is_via_ssh());
1789
1790                            workspace
1791                                .client()
1792                                .telemetry()
1793                                .log_edit_event("inline assist", is_via_ssh);
1794                        })
1795                        .log_err();
1796                }
1797                let prompt = self.editor.read(cx).text(cx);
1798                if self
1799                    .prompt_history_ix
1800                    .map_or(true, |ix| self.prompt_history[ix] != prompt)
1801                {
1802                    self.prompt_history_ix.take();
1803                    self.pending_prompt = prompt;
1804                }
1805
1806                self.edited_since_done = true;
1807                cx.notify();
1808            }
1809            EditorEvent::BufferEdited => {
1810                self.count_tokens(cx);
1811            }
1812            EditorEvent::Blurred => {
1813                if self.show_rate_limit_notice {
1814                    self.show_rate_limit_notice = false;
1815                    cx.notify();
1816                }
1817            }
1818            _ => {}
1819        }
1820    }
1821
1822    fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
1823        match self.codegen.read(cx).status(cx) {
1824            CodegenStatus::Idle => {
1825                self.editor
1826                    .update(cx, |editor, _| editor.set_read_only(false));
1827            }
1828            CodegenStatus::Pending => {
1829                self.editor
1830                    .update(cx, |editor, _| editor.set_read_only(true));
1831            }
1832            CodegenStatus::Done => {
1833                self.edited_since_done = false;
1834                self.editor
1835                    .update(cx, |editor, _| editor.set_read_only(false));
1836            }
1837            CodegenStatus::Error(error) => {
1838                if cx.has_flag::<ZedPro>()
1839                    && error.error_code() == proto::ErrorCode::RateLimitExceeded
1840                    && !dismissed_rate_limit_notice()
1841                {
1842                    self.show_rate_limit_notice = true;
1843                    cx.notify();
1844                }
1845
1846                self.edited_since_done = false;
1847                self.editor
1848                    .update(cx, |editor, _| editor.set_read_only(false));
1849            }
1850        }
1851    }
1852
1853    fn restart(&mut self, _: &menu::Restart, cx: &mut ViewContext<Self>) {
1854        cx.emit(PromptEditorEvent::StartRequested);
1855    }
1856
1857    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1858        match self.codegen.read(cx).status(cx) {
1859            CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
1860                cx.emit(PromptEditorEvent::CancelRequested);
1861            }
1862            CodegenStatus::Pending => {
1863                cx.emit(PromptEditorEvent::StopRequested);
1864            }
1865        }
1866    }
1867
1868    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
1869        match self.codegen.read(cx).status(cx) {
1870            CodegenStatus::Idle => {
1871                cx.emit(PromptEditorEvent::StartRequested);
1872            }
1873            CodegenStatus::Pending => {
1874                cx.emit(PromptEditorEvent::DismissRequested);
1875            }
1876            CodegenStatus::Done => {
1877                if self.edited_since_done {
1878                    cx.emit(PromptEditorEvent::StartRequested);
1879                } else {
1880                    cx.emit(PromptEditorEvent::ConfirmRequested);
1881                }
1882            }
1883            CodegenStatus::Error(_) => {
1884                cx.emit(PromptEditorEvent::StartRequested);
1885            }
1886        }
1887    }
1888
1889    fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
1890        if let Some(ix) = self.prompt_history_ix {
1891            if ix > 0 {
1892                self.prompt_history_ix = Some(ix - 1);
1893                let prompt = self.prompt_history[ix - 1].as_str();
1894                self.editor.update(cx, |editor, cx| {
1895                    editor.set_text(prompt, cx);
1896                    editor.move_to_beginning(&Default::default(), cx);
1897                });
1898            }
1899        } else if !self.prompt_history.is_empty() {
1900            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
1901            let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
1902            self.editor.update(cx, |editor, cx| {
1903                editor.set_text(prompt, cx);
1904                editor.move_to_beginning(&Default::default(), cx);
1905            });
1906        }
1907    }
1908
1909    fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
1910        if let Some(ix) = self.prompt_history_ix {
1911            if ix < self.prompt_history.len() - 1 {
1912                self.prompt_history_ix = Some(ix + 1);
1913                let prompt = self.prompt_history[ix + 1].as_str();
1914                self.editor.update(cx, |editor, cx| {
1915                    editor.set_text(prompt, cx);
1916                    editor.move_to_end(&Default::default(), cx)
1917                });
1918            } else {
1919                self.prompt_history_ix = None;
1920                let prompt = self.pending_prompt.as_str();
1921                self.editor.update(cx, |editor, cx| {
1922                    editor.set_text(prompt, cx);
1923                    editor.move_to_end(&Default::default(), cx)
1924                });
1925            }
1926        }
1927    }
1928
1929    fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
1930        self.codegen
1931            .update(cx, |codegen, cx| codegen.cycle_prev(cx));
1932    }
1933
1934    fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
1935        self.codegen
1936            .update(cx, |codegen, cx| codegen.cycle_next(cx));
1937    }
1938
1939    fn render_cycle_controls(&self, cx: &ViewContext<Self>) -> AnyElement {
1940        let codegen = self.codegen.read(cx);
1941        let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
1942
1943        let model_registry = LanguageModelRegistry::read_global(cx);
1944        let default_model = model_registry.active_model();
1945        let alternative_models = model_registry.inline_alternative_models();
1946
1947        let get_model_name = |index: usize| -> String {
1948            let name = |model: &Arc<dyn LanguageModel>| model.name().0.to_string();
1949
1950            match index {
1951                0 => default_model.as_ref().map_or_else(String::new, name),
1952                index if index <= alternative_models.len() => alternative_models
1953                    .get(index - 1)
1954                    .map_or_else(String::new, name),
1955                _ => String::new(),
1956            }
1957        };
1958
1959        let total_models = alternative_models.len() + 1;
1960
1961        if total_models <= 1 {
1962            return div().into_any_element();
1963        }
1964
1965        let current_index = codegen.active_alternative;
1966        let prev_index = (current_index + total_models - 1) % total_models;
1967        let next_index = (current_index + 1) % total_models;
1968
1969        let prev_model_name = get_model_name(prev_index);
1970        let next_model_name = get_model_name(next_index);
1971
1972        h_flex()
1973            .child(
1974                IconButton::new("previous", IconName::ChevronLeft)
1975                    .icon_color(Color::Muted)
1976                    .disabled(disabled || current_index == 0)
1977                    .shape(IconButtonShape::Square)
1978                    .tooltip({
1979                        let focus_handle = self.editor.focus_handle(cx);
1980                        move |cx| {
1981                            cx.new_view(|cx| {
1982                                let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
1983                                    KeyBinding::for_action_in(
1984                                        &CyclePreviousInlineAssist,
1985                                        &focus_handle,
1986                                        cx,
1987                                    ),
1988                                );
1989                                if !disabled && current_index != 0 {
1990                                    tooltip = tooltip.meta(prev_model_name.clone());
1991                                }
1992                                tooltip
1993                            })
1994                            .into()
1995                        }
1996                    })
1997                    .on_click(cx.listener(|this, _, cx| {
1998                        this.codegen
1999                            .update(cx, |codegen, cx| codegen.cycle_prev(cx))
2000                    })),
2001            )
2002            .child(
2003                Label::new(format!(
2004                    "{}/{}",
2005                    codegen.active_alternative + 1,
2006                    codegen.alternative_count(cx)
2007                ))
2008                .size(LabelSize::Small)
2009                .color(if disabled {
2010                    Color::Disabled
2011                } else {
2012                    Color::Muted
2013                }),
2014            )
2015            .child(
2016                IconButton::new("next", IconName::ChevronRight)
2017                    .icon_color(Color::Muted)
2018                    .disabled(disabled || current_index == total_models - 1)
2019                    .shape(IconButtonShape::Square)
2020                    .tooltip({
2021                        let focus_handle = self.editor.focus_handle(cx);
2022                        move |cx| {
2023                            cx.new_view(|cx| {
2024                                let mut tooltip = Tooltip::new("Next Alternative").key_binding(
2025                                    KeyBinding::for_action_in(
2026                                        &CycleNextInlineAssist,
2027                                        &focus_handle,
2028                                        cx,
2029                                    ),
2030                                );
2031                                if !disabled && current_index != total_models - 1 {
2032                                    tooltip = tooltip.meta(next_model_name.clone());
2033                                }
2034                                tooltip
2035                            })
2036                            .into()
2037                        }
2038                    })
2039                    .on_click(cx.listener(|this, _, cx| {
2040                        this.codegen
2041                            .update(cx, |codegen, cx| codegen.cycle_next(cx))
2042                    })),
2043            )
2044            .into_any_element()
2045    }
2046
2047    fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
2048        let model = LanguageModelRegistry::read_global(cx).active_model()?;
2049        let token_counts = self.token_counts?;
2050        let max_token_count = model.max_token_count();
2051
2052        let remaining_tokens = max_token_count as isize - token_counts.total as isize;
2053        let token_count_color = if remaining_tokens <= 0 {
2054            Color::Error
2055        } else if token_counts.total as f32 / max_token_count as f32 >= 0.8 {
2056            Color::Warning
2057        } else {
2058            Color::Muted
2059        };
2060
2061        let mut token_count = h_flex()
2062            .id("token_count")
2063            .gap_0p5()
2064            .child(
2065                Label::new(humanize_token_count(token_counts.total))
2066                    .size(LabelSize::Small)
2067                    .color(token_count_color),
2068            )
2069            .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2070            .child(
2071                Label::new(humanize_token_count(max_token_count))
2072                    .size(LabelSize::Small)
2073                    .color(Color::Muted),
2074            );
2075        if let Some(workspace) = self.workspace.clone() {
2076            token_count = token_count
2077                .tooltip(move |cx| {
2078                    Tooltip::with_meta(
2079                        format!(
2080                            "Tokens Used ({} from the Assistant Panel)",
2081                            humanize_token_count(token_counts.assistant_panel)
2082                        ),
2083                        None,
2084                        "Click to open the Assistant Panel",
2085                        cx,
2086                    )
2087                })
2088                .cursor_pointer()
2089                .on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
2090                .on_click(move |_, cx| {
2091                    cx.stop_propagation();
2092                    workspace
2093                        .update(cx, |workspace, cx| {
2094                            workspace.focus_panel::<AssistantPanel>(cx)
2095                        })
2096                        .ok();
2097                });
2098        } else {
2099            token_count = token_count
2100                .cursor_default()
2101                .tooltip(|cx| Tooltip::text("Tokens used", cx));
2102        }
2103
2104        Some(token_count)
2105    }
2106
2107    fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2108        let settings = ThemeSettings::get_global(cx);
2109        let text_style = TextStyle {
2110            color: if self.editor.read(cx).read_only(cx) {
2111                cx.theme().colors().text_disabled
2112            } else {
2113                cx.theme().colors().text
2114            },
2115            font_family: settings.buffer_font.family.clone(),
2116            font_fallbacks: settings.buffer_font.fallbacks.clone(),
2117            font_size: settings.buffer_font_size.into(),
2118            font_weight: settings.buffer_font.weight,
2119            line_height: relative(settings.buffer_line_height.value()),
2120            ..Default::default()
2121        };
2122        EditorElement::new(
2123            &self.editor,
2124            EditorStyle {
2125                background: cx.theme().colors().editor_background,
2126                local_player: cx.theme().players().local(),
2127                text: text_style,
2128                ..Default::default()
2129            },
2130        )
2131    }
2132
2133    fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2134        Popover::new().child(
2135            v_flex()
2136                .occlude()
2137                .p_2()
2138                .child(
2139                    Label::new("Out of Tokens")
2140                        .size(LabelSize::Small)
2141                        .weight(FontWeight::BOLD),
2142                )
2143                .child(Label::new(
2144                    "Try Zed Pro for higher limits, a wider range of models, and more.",
2145                ))
2146                .child(
2147                    h_flex()
2148                        .justify_between()
2149                        .child(CheckboxWithLabel::new(
2150                            "dont-show-again",
2151                            Label::new("Don't show again"),
2152                            if dismissed_rate_limit_notice() {
2153                                ui::ToggleState::Selected
2154                            } else {
2155                                ui::ToggleState::Unselected
2156                            },
2157                            |selection, cx| {
2158                                let is_dismissed = match selection {
2159                                    ui::ToggleState::Unselected => false,
2160                                    ui::ToggleState::Indeterminate => return,
2161                                    ui::ToggleState::Selected => true,
2162                                };
2163
2164                                set_rate_limit_notice_dismissed(is_dismissed, cx)
2165                            },
2166                        ))
2167                        .child(
2168                            h_flex()
2169                                .gap_2()
2170                                .child(
2171                                    Button::new("dismiss", "Dismiss")
2172                                        .style(ButtonStyle::Transparent)
2173                                        .on_click(cx.listener(Self::toggle_rate_limit_notice)),
2174                                )
2175                                .child(Button::new("more-info", "More Info").on_click(
2176                                    |_event, cx| {
2177                                        cx.dispatch_action(Box::new(
2178                                            zed_actions::OpenAccountSettings,
2179                                        ))
2180                                    },
2181                                )),
2182                        ),
2183                ),
2184        )
2185    }
2186}
2187
2188const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice";
2189
2190fn dismissed_rate_limit_notice() -> bool {
2191    db::kvp::KEY_VALUE_STORE
2192        .read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY)
2193        .log_err()
2194        .map_or(false, |s| s.is_some())
2195}
2196
2197fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut AppContext) {
2198    db::write_and_log(cx, move || async move {
2199        if is_dismissed {
2200            db::kvp::KEY_VALUE_STORE
2201                .write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into())
2202                .await
2203        } else {
2204            db::kvp::KEY_VALUE_STORE
2205                .delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into())
2206                .await
2207        }
2208    })
2209}
2210
2211struct InlineAssist {
2212    group_id: InlineAssistGroupId,
2213    range: Range<Anchor>,
2214    editor: WeakView<Editor>,
2215    decorations: Option<InlineAssistDecorations>,
2216    codegen: Model<Codegen>,
2217    _subscriptions: Vec<Subscription>,
2218    workspace: Option<WeakView<Workspace>>,
2219    include_context: bool,
2220}
2221
2222impl InlineAssist {
2223    #[allow(clippy::too_many_arguments)]
2224    fn new(
2225        assist_id: InlineAssistId,
2226        group_id: InlineAssistGroupId,
2227        include_context: bool,
2228        editor: &View<Editor>,
2229        prompt_editor: &View<PromptEditor>,
2230        prompt_block_id: CustomBlockId,
2231        end_block_id: CustomBlockId,
2232        range: Range<Anchor>,
2233        codegen: Model<Codegen>,
2234        workspace: Option<WeakView<Workspace>>,
2235        cx: &mut WindowContext,
2236    ) -> Self {
2237        let prompt_editor_focus_handle = prompt_editor.focus_handle(cx);
2238        InlineAssist {
2239            group_id,
2240            include_context,
2241            editor: editor.downgrade(),
2242            decorations: Some(InlineAssistDecorations {
2243                prompt_block_id,
2244                prompt_editor: prompt_editor.clone(),
2245                removed_line_block_ids: HashSet::default(),
2246                end_block_id,
2247            }),
2248            range,
2249            codegen: codegen.clone(),
2250            workspace: workspace.clone(),
2251            _subscriptions: vec![
2252                cx.on_focus_in(&prompt_editor_focus_handle, move |cx| {
2253                    InlineAssistant::update_global(cx, |this, cx| {
2254                        this.handle_prompt_editor_focus_in(assist_id, cx)
2255                    })
2256                }),
2257                cx.on_focus_out(&prompt_editor_focus_handle, move |_, cx| {
2258                    InlineAssistant::update_global(cx, |this, cx| {
2259                        this.handle_prompt_editor_focus_out(assist_id, cx)
2260                    })
2261                }),
2262                cx.subscribe(prompt_editor, |prompt_editor, event, cx| {
2263                    InlineAssistant::update_global(cx, |this, cx| {
2264                        this.handle_prompt_editor_event(prompt_editor, event, cx)
2265                    })
2266                }),
2267                cx.observe(&codegen, {
2268                    let editor = editor.downgrade();
2269                    move |_, cx| {
2270                        if let Some(editor) = editor.upgrade() {
2271                            InlineAssistant::update_global(cx, |this, cx| {
2272                                if let Some(editor_assists) =
2273                                    this.assists_by_editor.get(&editor.downgrade())
2274                                {
2275                                    editor_assists.highlight_updates.send(()).ok();
2276                                }
2277
2278                                this.update_editor_blocks(&editor, assist_id, cx);
2279                            })
2280                        }
2281                    }
2282                }),
2283                cx.subscribe(&codegen, move |codegen, event, cx| {
2284                    InlineAssistant::update_global(cx, |this, cx| match event {
2285                        CodegenEvent::Undone => this.finish_assist(assist_id, false, cx),
2286                        CodegenEvent::Finished => {
2287                            let assist = if let Some(assist) = this.assists.get(&assist_id) {
2288                                assist
2289                            } else {
2290                                return;
2291                            };
2292
2293                            if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
2294                                if assist.decorations.is_none() {
2295                                    if let Some(workspace) = assist
2296                                        .workspace
2297                                        .as_ref()
2298                                        .and_then(|workspace| workspace.upgrade())
2299                                    {
2300                                        let error = format!("Inline assistant error: {}", error);
2301                                        workspace.update(cx, |workspace, cx| {
2302                                            struct InlineAssistantError;
2303
2304                                            let id =
2305                                                NotificationId::composite::<InlineAssistantError>(
2306                                                    assist_id.0,
2307                                                );
2308
2309                                            workspace.show_toast(Toast::new(id, error), cx);
2310                                        })
2311                                    }
2312                                }
2313                            }
2314
2315                            if assist.decorations.is_none() {
2316                                this.finish_assist(assist_id, false, cx);
2317                            }
2318                        }
2319                    })
2320                }),
2321            ],
2322        }
2323    }
2324
2325    fn user_prompt(&self, cx: &AppContext) -> Option<String> {
2326        let decorations = self.decorations.as_ref()?;
2327        Some(decorations.prompt_editor.read(cx).prompt(cx))
2328    }
2329
2330    fn assistant_panel_context(&self, cx: &WindowContext) -> Option<LanguageModelRequest> {
2331        if self.include_context {
2332            let workspace = self.workspace.as_ref()?;
2333            let workspace = workspace.upgrade()?.read(cx);
2334            let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
2335            Some(
2336                assistant_panel
2337                    .read(cx)
2338                    .active_context(cx)?
2339                    .read(cx)
2340                    .to_completion_request(RequestType::Chat, cx),
2341            )
2342        } else {
2343            None
2344        }
2345    }
2346
2347    pub fn count_tokens(&self, cx: &WindowContext) -> BoxFuture<'static, Result<TokenCounts>> {
2348        let Some(user_prompt) = self.user_prompt(cx) else {
2349            return future::ready(Err(anyhow!("no user prompt"))).boxed();
2350        };
2351        let assistant_panel_context = self.assistant_panel_context(cx);
2352        self.codegen
2353            .read(cx)
2354            .count_tokens(user_prompt, assistant_panel_context, cx)
2355    }
2356}
2357
2358struct InlineAssistDecorations {
2359    prompt_block_id: CustomBlockId,
2360    prompt_editor: View<PromptEditor>,
2361    removed_line_block_ids: HashSet<CustomBlockId>,
2362    end_block_id: CustomBlockId,
2363}
2364
2365#[derive(Copy, Clone, Debug)]
2366pub enum CodegenEvent {
2367    Finished,
2368    Undone,
2369}
2370
2371pub struct Codegen {
2372    alternatives: Vec<Model<CodegenAlternative>>,
2373    active_alternative: usize,
2374    seen_alternatives: HashSet<usize>,
2375    subscriptions: Vec<Subscription>,
2376    buffer: Model<MultiBuffer>,
2377    range: Range<Anchor>,
2378    initial_transaction_id: Option<TransactionId>,
2379    telemetry: Arc<Telemetry>,
2380    builder: Arc<PromptBuilder>,
2381    is_insertion: bool,
2382}
2383
2384impl Codegen {
2385    pub fn new(
2386        buffer: Model<MultiBuffer>,
2387        range: Range<Anchor>,
2388        initial_transaction_id: Option<TransactionId>,
2389        telemetry: Arc<Telemetry>,
2390        builder: Arc<PromptBuilder>,
2391        cx: &mut ModelContext<Self>,
2392    ) -> Self {
2393        let codegen = cx.new_model(|cx| {
2394            CodegenAlternative::new(
2395                buffer.clone(),
2396                range.clone(),
2397                false,
2398                Some(telemetry.clone()),
2399                builder.clone(),
2400                cx,
2401            )
2402        });
2403        let mut this = Self {
2404            is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
2405            alternatives: vec![codegen],
2406            active_alternative: 0,
2407            seen_alternatives: HashSet::default(),
2408            subscriptions: Vec::new(),
2409            buffer,
2410            range,
2411            initial_transaction_id,
2412            telemetry,
2413            builder,
2414        };
2415        this.activate(0, cx);
2416        this
2417    }
2418
2419    fn subscribe_to_alternative(&mut self, cx: &mut ModelContext<Self>) {
2420        let codegen = self.active_alternative().clone();
2421        self.subscriptions.clear();
2422        self.subscriptions
2423            .push(cx.observe(&codegen, |_, _, cx| cx.notify()));
2424        self.subscriptions
2425            .push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
2426    }
2427
2428    fn active_alternative(&self) -> &Model<CodegenAlternative> {
2429        &self.alternatives[self.active_alternative]
2430    }
2431
2432    fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus {
2433        &self.active_alternative().read(cx).status
2434    }
2435
2436    fn alternative_count(&self, cx: &AppContext) -> usize {
2437        LanguageModelRegistry::read_global(cx)
2438            .inline_alternative_models()
2439            .len()
2440            + 1
2441    }
2442
2443    pub fn cycle_prev(&mut self, cx: &mut ModelContext<Self>) {
2444        let next_active_ix = if self.active_alternative == 0 {
2445            self.alternatives.len() - 1
2446        } else {
2447            self.active_alternative - 1
2448        };
2449        self.activate(next_active_ix, cx);
2450    }
2451
2452    pub fn cycle_next(&mut self, cx: &mut ModelContext<Self>) {
2453        let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
2454        self.activate(next_active_ix, cx);
2455    }
2456
2457    fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
2458        self.active_alternative()
2459            .update(cx, |codegen, cx| codegen.set_active(false, cx));
2460        self.seen_alternatives.insert(index);
2461        self.active_alternative = index;
2462        self.active_alternative()
2463            .update(cx, |codegen, cx| codegen.set_active(true, cx));
2464        self.subscribe_to_alternative(cx);
2465        cx.notify();
2466    }
2467
2468    pub fn start(
2469        &mut self,
2470        user_prompt: String,
2471        assistant_panel_context: Option<LanguageModelRequest>,
2472        cx: &mut ModelContext<Self>,
2473    ) -> Result<()> {
2474        let alternative_models = LanguageModelRegistry::read_global(cx)
2475            .inline_alternative_models()
2476            .to_vec();
2477
2478        self.active_alternative()
2479            .update(cx, |alternative, cx| alternative.undo(cx));
2480        self.activate(0, cx);
2481        self.alternatives.truncate(1);
2482
2483        for _ in 0..alternative_models.len() {
2484            self.alternatives.push(cx.new_model(|cx| {
2485                CodegenAlternative::new(
2486                    self.buffer.clone(),
2487                    self.range.clone(),
2488                    false,
2489                    Some(self.telemetry.clone()),
2490                    self.builder.clone(),
2491                    cx,
2492                )
2493            }));
2494        }
2495
2496        let primary_model = LanguageModelRegistry::read_global(cx)
2497            .active_model()
2498            .context("no active model")?;
2499
2500        for (model, alternative) in iter::once(primary_model)
2501            .chain(alternative_models)
2502            .zip(&self.alternatives)
2503        {
2504            alternative.update(cx, |alternative, cx| {
2505                alternative.start(
2506                    user_prompt.clone(),
2507                    assistant_panel_context.clone(),
2508                    model.clone(),
2509                    cx,
2510                )
2511            })?;
2512        }
2513
2514        Ok(())
2515    }
2516
2517    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
2518        for codegen in &self.alternatives {
2519            codegen.update(cx, |codegen, cx| codegen.stop(cx));
2520        }
2521    }
2522
2523    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
2524        self.active_alternative()
2525            .update(cx, |codegen, cx| codegen.undo(cx));
2526
2527        self.buffer.update(cx, |buffer, cx| {
2528            if let Some(transaction_id) = self.initial_transaction_id.take() {
2529                buffer.undo_transaction(transaction_id, cx);
2530                buffer.refresh_preview(cx);
2531            }
2532        });
2533    }
2534
2535    pub fn count_tokens(
2536        &self,
2537        user_prompt: String,
2538        assistant_panel_context: Option<LanguageModelRequest>,
2539        cx: &AppContext,
2540    ) -> BoxFuture<'static, Result<TokenCounts>> {
2541        self.active_alternative()
2542            .read(cx)
2543            .count_tokens(user_prompt, assistant_panel_context, cx)
2544    }
2545
2546    pub fn buffer(&self, cx: &AppContext) -> Model<MultiBuffer> {
2547        self.active_alternative().read(cx).buffer.clone()
2548    }
2549
2550    pub fn old_buffer(&self, cx: &AppContext) -> Model<Buffer> {
2551        self.active_alternative().read(cx).old_buffer.clone()
2552    }
2553
2554    pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
2555        self.active_alternative().read(cx).snapshot.clone()
2556    }
2557
2558    pub fn edit_position(&self, cx: &AppContext) -> Option<Anchor> {
2559        self.active_alternative().read(cx).edit_position
2560    }
2561
2562    fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff {
2563        &self.active_alternative().read(cx).diff
2564    }
2565
2566    pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range<Anchor>] {
2567        self.active_alternative().read(cx).last_equal_ranges()
2568    }
2569}
2570
2571impl EventEmitter<CodegenEvent> for Codegen {}
2572
2573pub struct CodegenAlternative {
2574    buffer: Model<MultiBuffer>,
2575    old_buffer: Model<Buffer>,
2576    snapshot: MultiBufferSnapshot,
2577    edit_position: Option<Anchor>,
2578    range: Range<Anchor>,
2579    last_equal_ranges: Vec<Range<Anchor>>,
2580    transformation_transaction_id: Option<TransactionId>,
2581    status: CodegenStatus,
2582    generation: Task<()>,
2583    diff: Diff,
2584    telemetry: Option<Arc<Telemetry>>,
2585    _subscription: gpui::Subscription,
2586    builder: Arc<PromptBuilder>,
2587    active: bool,
2588    edits: Vec<(Range<Anchor>, String)>,
2589    line_operations: Vec<LineOperation>,
2590    request: Option<LanguageModelRequest>,
2591    elapsed_time: Option<f64>,
2592    completion: Option<String>,
2593    message_id: Option<String>,
2594}
2595
2596enum CodegenStatus {
2597    Idle,
2598    Pending,
2599    Done,
2600    Error(anyhow::Error),
2601}
2602
2603#[derive(Default)]
2604struct Diff {
2605    deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
2606    inserted_row_ranges: Vec<Range<Anchor>>,
2607}
2608
2609impl Diff {
2610    fn is_empty(&self) -> bool {
2611        self.deleted_row_ranges.is_empty() && self.inserted_row_ranges.is_empty()
2612    }
2613}
2614
2615impl EventEmitter<CodegenEvent> for CodegenAlternative {}
2616
2617impl CodegenAlternative {
2618    pub fn new(
2619        multi_buffer: Model<MultiBuffer>,
2620        range: Range<Anchor>,
2621        active: bool,
2622        telemetry: Option<Arc<Telemetry>>,
2623        builder: Arc<PromptBuilder>,
2624        cx: &mut ModelContext<Self>,
2625    ) -> Self {
2626        let snapshot = multi_buffer.read(cx).snapshot(cx);
2627
2628        let (old_excerpt, _) = snapshot
2629            .range_to_buffer_ranges(range.clone())
2630            .pop()
2631            .unwrap();
2632        let old_buffer = cx.new_model(|cx| {
2633            let text = old_excerpt.buffer().as_rope().clone();
2634            let line_ending = old_excerpt.buffer().line_ending();
2635            let language = old_excerpt.buffer().language().cloned();
2636            let language_registry = multi_buffer
2637                .read(cx)
2638                .buffer(old_excerpt.buffer_id())
2639                .unwrap()
2640                .read(cx)
2641                .language_registry();
2642
2643            let mut buffer = Buffer::local_normalized(text, line_ending, cx);
2644            buffer.set_language(language, cx);
2645            if let Some(language_registry) = language_registry {
2646                buffer.set_language_registry(language_registry)
2647            }
2648            buffer
2649        });
2650
2651        Self {
2652            buffer: multi_buffer.clone(),
2653            old_buffer,
2654            edit_position: None,
2655            message_id: None,
2656            snapshot,
2657            last_equal_ranges: Default::default(),
2658            transformation_transaction_id: None,
2659            status: CodegenStatus::Idle,
2660            generation: Task::ready(()),
2661            diff: Diff::default(),
2662            telemetry,
2663            _subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
2664            builder,
2665            active,
2666            edits: Vec::new(),
2667            line_operations: Vec::new(),
2668            range,
2669            request: None,
2670            elapsed_time: None,
2671            completion: None,
2672        }
2673    }
2674
2675    fn set_active(&mut self, active: bool, cx: &mut ModelContext<Self>) {
2676        if active != self.active {
2677            self.active = active;
2678
2679            if self.active {
2680                let edits = self.edits.clone();
2681                self.apply_edits(edits, cx);
2682                if matches!(self.status, CodegenStatus::Pending) {
2683                    let line_operations = self.line_operations.clone();
2684                    self.reapply_line_based_diff(line_operations, cx);
2685                } else {
2686                    self.reapply_batch_diff(cx).detach();
2687                }
2688            } else if let Some(transaction_id) = self.transformation_transaction_id.take() {
2689                self.buffer.update(cx, |buffer, cx| {
2690                    buffer.undo_transaction(transaction_id, cx);
2691                    buffer.forget_transaction(transaction_id, cx);
2692                });
2693            }
2694        }
2695    }
2696
2697    fn handle_buffer_event(
2698        &mut self,
2699        _buffer: Model<MultiBuffer>,
2700        event: &multi_buffer::Event,
2701        cx: &mut ModelContext<Self>,
2702    ) {
2703        if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
2704            if self.transformation_transaction_id == Some(*transaction_id) {
2705                self.transformation_transaction_id = None;
2706                self.generation = Task::ready(());
2707                cx.emit(CodegenEvent::Undone);
2708            }
2709        }
2710    }
2711
2712    pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
2713        &self.last_equal_ranges
2714    }
2715
2716    pub fn count_tokens(
2717        &self,
2718        user_prompt: String,
2719        assistant_panel_context: Option<LanguageModelRequest>,
2720        cx: &AppContext,
2721    ) -> BoxFuture<'static, Result<TokenCounts>> {
2722        if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
2723            let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
2724            match request {
2725                Ok(request) => {
2726                    let total_count = model.count_tokens(request.clone(), cx);
2727                    let assistant_panel_count = assistant_panel_context
2728                        .map(|context| model.count_tokens(context, cx))
2729                        .unwrap_or_else(|| future::ready(Ok(0)).boxed());
2730
2731                    async move {
2732                        Ok(TokenCounts {
2733                            total: total_count.await?,
2734                            assistant_panel: assistant_panel_count.await?,
2735                        })
2736                    }
2737                    .boxed()
2738                }
2739                Err(error) => futures::future::ready(Err(error)).boxed(),
2740            }
2741        } else {
2742            future::ready(Err(anyhow!("no active model"))).boxed()
2743        }
2744    }
2745
2746    pub fn start(
2747        &mut self,
2748        user_prompt: String,
2749        assistant_panel_context: Option<LanguageModelRequest>,
2750        model: Arc<dyn LanguageModel>,
2751        cx: &mut ModelContext<Self>,
2752    ) -> Result<()> {
2753        if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
2754            self.buffer.update(cx, |buffer, cx| {
2755                buffer.undo_transaction(transformation_transaction_id, cx);
2756            });
2757        }
2758
2759        self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
2760
2761        let api_key = model.api_key(cx);
2762        let telemetry_id = model.telemetry_id();
2763        let provider_id = model.provider_id();
2764        let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
2765            if user_prompt.trim().to_lowercase() == "delete" {
2766                async { Ok(LanguageModelTextStream::default()) }.boxed_local()
2767            } else {
2768                let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
2769                self.request = Some(request.clone());
2770
2771                cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
2772                    .boxed_local()
2773            };
2774        self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
2775        Ok(())
2776    }
2777
2778    fn build_request(
2779        &self,
2780        user_prompt: String,
2781        assistant_panel_context: Option<LanguageModelRequest>,
2782        cx: &AppContext,
2783    ) -> Result<LanguageModelRequest> {
2784        let buffer = self.buffer.read(cx).snapshot(cx);
2785        let language = buffer.language_at(self.range.start);
2786        let language_name = if let Some(language) = language.as_ref() {
2787            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
2788                None
2789            } else {
2790                Some(language.name())
2791            }
2792        } else {
2793            None
2794        };
2795
2796        let language_name = language_name.as_ref();
2797        let start = buffer.point_to_buffer_offset(self.range.start);
2798        let end = buffer.point_to_buffer_offset(self.range.end);
2799        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
2800            let (start_buffer, start_buffer_offset) = start;
2801            let (end_buffer, end_buffer_offset) = end;
2802            if start_buffer.remote_id() == end_buffer.remote_id() {
2803                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
2804            } else {
2805                return Err(anyhow::anyhow!("invalid transformation range"));
2806            }
2807        } else {
2808            return Err(anyhow::anyhow!("invalid transformation range"));
2809        };
2810
2811        let prompt = self
2812            .builder
2813            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
2814            .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
2815
2816        let mut messages = Vec::new();
2817        if let Some(context_request) = assistant_panel_context {
2818            messages = context_request.messages;
2819        }
2820
2821        messages.push(LanguageModelRequestMessage {
2822            role: Role::User,
2823            content: vec![prompt.into()],
2824            cache: false,
2825        });
2826
2827        Ok(LanguageModelRequest {
2828            messages,
2829            tools: Vec::new(),
2830            stop: Vec::new(),
2831            temperature: None,
2832        })
2833    }
2834
2835    pub fn handle_stream(
2836        &mut self,
2837        model_telemetry_id: String,
2838        model_provider_id: String,
2839        model_api_key: Option<String>,
2840        stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
2841        cx: &mut ModelContext<Self>,
2842    ) {
2843        let start_time = Instant::now();
2844        let snapshot = self.snapshot.clone();
2845        let selected_text = snapshot
2846            .text_for_range(self.range.start..self.range.end)
2847            .collect::<Rope>();
2848
2849        let selection_start = self.range.start.to_point(&snapshot);
2850
2851        // Start with the indentation of the first line in the selection
2852        let mut suggested_line_indent = snapshot
2853            .suggested_indents(selection_start.row..=selection_start.row, cx)
2854            .into_values()
2855            .next()
2856            .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
2857
2858        // If the first line in the selection does not have indentation, check the following lines
2859        if suggested_line_indent.len == 0 && suggested_line_indent.kind == IndentKind::Space {
2860            for row in selection_start.row..=self.range.end.to_point(&snapshot).row {
2861                let line_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
2862                // Prefer tabs if a line in the selection uses tabs as indentation
2863                if line_indent.kind == IndentKind::Tab {
2864                    suggested_line_indent.kind = IndentKind::Tab;
2865                    break;
2866                }
2867            }
2868        }
2869
2870        let http_client = cx.http_client().clone();
2871        let telemetry = self.telemetry.clone();
2872        let language_name = {
2873            let multibuffer = self.buffer.read(cx);
2874            let snapshot = multibuffer.snapshot(cx);
2875            let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
2876            ranges
2877                .first()
2878                .and_then(|(excerpt, _)| excerpt.buffer().language())
2879                .map(|language| language.name())
2880        };
2881
2882        self.diff = Diff::default();
2883        self.status = CodegenStatus::Pending;
2884        let mut edit_start = self.range.start.to_offset(&snapshot);
2885        let completion = Arc::new(Mutex::new(String::new()));
2886        let completion_clone = completion.clone();
2887
2888        self.generation = cx.spawn(|codegen, mut cx| {
2889            async move {
2890                let stream = stream.await;
2891                let message_id = stream
2892                    .as_ref()
2893                    .ok()
2894                    .and_then(|stream| stream.message_id.clone());
2895                let generate = async {
2896                    let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
2897                    let executor = cx.background_executor().clone();
2898                    let message_id = message_id.clone();
2899                    let line_based_stream_diff: Task<anyhow::Result<()>> =
2900                        cx.background_executor().spawn(async move {
2901                            let mut response_latency = None;
2902                            let request_start = Instant::now();
2903                            let diff = async {
2904                                let chunks = StripInvalidSpans::new(stream?.stream);
2905                                futures::pin_mut!(chunks);
2906                                let mut diff = StreamingDiff::new(selected_text.to_string());
2907                                let mut line_diff = LineDiff::default();
2908
2909                                let mut new_text = String::new();
2910                                let mut base_indent = None;
2911                                let mut line_indent = None;
2912                                let mut first_line = true;
2913
2914                                while let Some(chunk) = chunks.next().await {
2915                                    if response_latency.is_none() {
2916                                        response_latency = Some(request_start.elapsed());
2917                                    }
2918                                    let chunk = chunk?;
2919                                    completion_clone.lock().push_str(&chunk);
2920
2921                                    let mut lines = chunk.split('\n').peekable();
2922                                    while let Some(line) = lines.next() {
2923                                        new_text.push_str(line);
2924                                        if line_indent.is_none() {
2925                                            if let Some(non_whitespace_ch_ix) =
2926                                                new_text.find(|ch: char| !ch.is_whitespace())
2927                                            {
2928                                                line_indent = Some(non_whitespace_ch_ix);
2929                                                base_indent = base_indent.or(line_indent);
2930
2931                                                let line_indent = line_indent.unwrap();
2932                                                let base_indent = base_indent.unwrap();
2933                                                let indent_delta =
2934                                                    line_indent as i32 - base_indent as i32;
2935                                                let mut corrected_indent_len = cmp::max(
2936                                                    0,
2937                                                    suggested_line_indent.len as i32 + indent_delta,
2938                                                )
2939                                                    as usize;
2940                                                if first_line {
2941                                                    corrected_indent_len = corrected_indent_len
2942                                                        .saturating_sub(
2943                                                            selection_start.column as usize,
2944                                                        );
2945                                                }
2946
2947                                                let indent_char = suggested_line_indent.char();
2948                                                let mut indent_buffer = [0; 4];
2949                                                let indent_str =
2950                                                    indent_char.encode_utf8(&mut indent_buffer);
2951                                                new_text.replace_range(
2952                                                    ..line_indent,
2953                                                    &indent_str.repeat(corrected_indent_len),
2954                                                );
2955                                            }
2956                                        }
2957
2958                                        if line_indent.is_some() {
2959                                            let char_ops = diff.push_new(&new_text);
2960                                            line_diff
2961                                                .push_char_operations(&char_ops, &selected_text);
2962                                            diff_tx
2963                                                .send((char_ops, line_diff.line_operations()))
2964                                                .await?;
2965                                            new_text.clear();
2966                                        }
2967
2968                                        if lines.peek().is_some() {
2969                                            let char_ops = diff.push_new("\n");
2970                                            line_diff
2971                                                .push_char_operations(&char_ops, &selected_text);
2972                                            diff_tx
2973                                                .send((char_ops, line_diff.line_operations()))
2974                                                .await?;
2975                                            if line_indent.is_none() {
2976                                                // Don't write out the leading indentation in empty lines on the next line
2977                                                // This is the case where the above if statement didn't clear the buffer
2978                                                new_text.clear();
2979                                            }
2980                                            line_indent = None;
2981                                            first_line = false;
2982                                        }
2983                                    }
2984                                }
2985
2986                                let mut char_ops = diff.push_new(&new_text);
2987                                char_ops.extend(diff.finish());
2988                                line_diff.push_char_operations(&char_ops, &selected_text);
2989                                line_diff.finish(&selected_text);
2990                                diff_tx
2991                                    .send((char_ops, line_diff.line_operations()))
2992                                    .await?;
2993
2994                                anyhow::Ok(())
2995                            };
2996
2997                            let result = diff.await;
2998
2999                            let error_message =
3000                                result.as_ref().err().map(|error| error.to_string());
3001                            report_assistant_event(
3002                                AssistantEvent {
3003                                    conversation_id: None,
3004                                    message_id,
3005                                    kind: AssistantKind::Inline,
3006                                    phase: AssistantPhase::Response,
3007                                    model: model_telemetry_id,
3008                                    model_provider: model_provider_id.to_string(),
3009                                    response_latency,
3010                                    error_message,
3011                                    language_name: language_name.map(|name| name.to_proto()),
3012                                },
3013                                telemetry,
3014                                http_client,
3015                                model_api_key,
3016                                &executor,
3017                            );
3018
3019                            result?;
3020                            Ok(())
3021                        });
3022
3023                    while let Some((char_ops, line_ops)) = diff_rx.next().await {
3024                        codegen.update(&mut cx, |codegen, cx| {
3025                            codegen.last_equal_ranges.clear();
3026
3027                            let edits = char_ops
3028                                .into_iter()
3029                                .filter_map(|operation| match operation {
3030                                    CharOperation::Insert { text } => {
3031                                        let edit_start = snapshot.anchor_after(edit_start);
3032                                        Some((edit_start..edit_start, text))
3033                                    }
3034                                    CharOperation::Delete { bytes } => {
3035                                        let edit_end = edit_start + bytes;
3036                                        let edit_range = snapshot.anchor_after(edit_start)
3037                                            ..snapshot.anchor_before(edit_end);
3038                                        edit_start = edit_end;
3039                                        Some((edit_range, String::new()))
3040                                    }
3041                                    CharOperation::Keep { bytes } => {
3042                                        let edit_end = edit_start + bytes;
3043                                        let edit_range = snapshot.anchor_after(edit_start)
3044                                            ..snapshot.anchor_before(edit_end);
3045                                        edit_start = edit_end;
3046                                        codegen.last_equal_ranges.push(edit_range);
3047                                        None
3048                                    }
3049                                })
3050                                .collect::<Vec<_>>();
3051
3052                            if codegen.active {
3053                                codegen.apply_edits(edits.iter().cloned(), cx);
3054                                codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
3055                            }
3056                            codegen.edits.extend(edits);
3057                            codegen.line_operations = line_ops;
3058                            codegen.edit_position = Some(snapshot.anchor_after(edit_start));
3059
3060                            cx.notify();
3061                        })?;
3062                    }
3063
3064                    // Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
3065                    // That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
3066                    // It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
3067                    let batch_diff_task =
3068                        codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
3069                    let (line_based_stream_diff, ()) =
3070                        join!(line_based_stream_diff, batch_diff_task);
3071                    line_based_stream_diff?;
3072
3073                    anyhow::Ok(())
3074                };
3075
3076                let result = generate.await;
3077                let elapsed_time = start_time.elapsed().as_secs_f64();
3078
3079                codegen
3080                    .update(&mut cx, |this, cx| {
3081                        this.message_id = message_id;
3082                        this.last_equal_ranges.clear();
3083                        if let Err(error) = result {
3084                            this.status = CodegenStatus::Error(error);
3085                        } else {
3086                            this.status = CodegenStatus::Done;
3087                        }
3088                        this.elapsed_time = Some(elapsed_time);
3089                        this.completion = Some(completion.lock().clone());
3090                        cx.emit(CodegenEvent::Finished);
3091                        cx.notify();
3092                    })
3093                    .ok();
3094            }
3095        });
3096        cx.notify();
3097    }
3098
3099    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
3100        self.last_equal_ranges.clear();
3101        if self.diff.is_empty() {
3102            self.status = CodegenStatus::Idle;
3103        } else {
3104            self.status = CodegenStatus::Done;
3105        }
3106        self.generation = Task::ready(());
3107        cx.emit(CodegenEvent::Finished);
3108        cx.notify();
3109    }
3110
3111    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
3112        self.buffer.update(cx, |buffer, cx| {
3113            if let Some(transaction_id) = self.transformation_transaction_id.take() {
3114                buffer.undo_transaction(transaction_id, cx);
3115                buffer.refresh_preview(cx);
3116            }
3117        });
3118    }
3119
3120    fn apply_edits(
3121        &mut self,
3122        edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
3123        cx: &mut ModelContext<CodegenAlternative>,
3124    ) {
3125        let transaction = self.buffer.update(cx, |buffer, cx| {
3126            // Avoid grouping assistant edits with user edits.
3127            buffer.finalize_last_transaction(cx);
3128            buffer.start_transaction(cx);
3129            buffer.edit(edits, None, cx);
3130            buffer.end_transaction(cx)
3131        });
3132
3133        if let Some(transaction) = transaction {
3134            if let Some(first_transaction) = self.transformation_transaction_id {
3135                // Group all assistant edits into the first transaction.
3136                self.buffer.update(cx, |buffer, cx| {
3137                    buffer.merge_transactions(transaction, first_transaction, cx)
3138                });
3139            } else {
3140                self.transformation_transaction_id = Some(transaction);
3141                self.buffer
3142                    .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
3143            }
3144        }
3145    }
3146
3147    fn reapply_line_based_diff(
3148        &mut self,
3149        line_operations: impl IntoIterator<Item = LineOperation>,
3150        cx: &mut ModelContext<Self>,
3151    ) {
3152        let old_snapshot = self.snapshot.clone();
3153        let old_range = self.range.to_point(&old_snapshot);
3154        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3155        let new_range = self.range.to_point(&new_snapshot);
3156
3157        let mut old_row = old_range.start.row;
3158        let mut new_row = new_range.start.row;
3159
3160        self.diff.deleted_row_ranges.clear();
3161        self.diff.inserted_row_ranges.clear();
3162        for operation in line_operations {
3163            match operation {
3164                LineOperation::Keep { lines } => {
3165                    old_row += lines;
3166                    new_row += lines;
3167                }
3168                LineOperation::Delete { lines } => {
3169                    let old_end_row = old_row + lines - 1;
3170                    let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3171
3172                    if let Some((_, last_deleted_row_range)) =
3173                        self.diff.deleted_row_ranges.last_mut()
3174                    {
3175                        if *last_deleted_row_range.end() + 1 == old_row {
3176                            *last_deleted_row_range = *last_deleted_row_range.start()..=old_end_row;
3177                        } else {
3178                            self.diff
3179                                .deleted_row_ranges
3180                                .push((new_row, old_row..=old_end_row));
3181                        }
3182                    } else {
3183                        self.diff
3184                            .deleted_row_ranges
3185                            .push((new_row, old_row..=old_end_row));
3186                    }
3187
3188                    old_row += lines;
3189                }
3190                LineOperation::Insert { lines } => {
3191                    let new_end_row = new_row + lines - 1;
3192                    let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3193                    let end = new_snapshot.anchor_before(Point::new(
3194                        new_end_row,
3195                        new_snapshot.line_len(MultiBufferRow(new_end_row)),
3196                    ));
3197                    self.diff.inserted_row_ranges.push(start..end);
3198                    new_row += lines;
3199                }
3200            }
3201
3202            cx.notify();
3203        }
3204    }
3205
3206    fn reapply_batch_diff(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
3207        let old_snapshot = self.snapshot.clone();
3208        let old_range = self.range.to_point(&old_snapshot);
3209        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3210        let new_range = self.range.to_point(&new_snapshot);
3211
3212        cx.spawn(|codegen, mut cx| async move {
3213            let (deleted_row_ranges, inserted_row_ranges) = cx
3214                .background_executor()
3215                .spawn(async move {
3216                    let old_text = old_snapshot
3217                        .text_for_range(
3218                            Point::new(old_range.start.row, 0)
3219                                ..Point::new(
3220                                    old_range.end.row,
3221                                    old_snapshot.line_len(MultiBufferRow(old_range.end.row)),
3222                                ),
3223                        )
3224                        .collect::<String>();
3225                    let new_text = new_snapshot
3226                        .text_for_range(
3227                            Point::new(new_range.start.row, 0)
3228                                ..Point::new(
3229                                    new_range.end.row,
3230                                    new_snapshot.line_len(MultiBufferRow(new_range.end.row)),
3231                                ),
3232                        )
3233                        .collect::<String>();
3234
3235                    let mut old_row = old_range.start.row;
3236                    let mut new_row = new_range.start.row;
3237                    let batch_diff =
3238                        similar::TextDiff::from_lines(old_text.as_str(), new_text.as_str());
3239
3240                    let mut deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)> = Vec::new();
3241                    let mut inserted_row_ranges = Vec::new();
3242                    for change in batch_diff.iter_all_changes() {
3243                        let line_count = change.value().lines().count() as u32;
3244                        match change.tag() {
3245                            similar::ChangeTag::Equal => {
3246                                old_row += line_count;
3247                                new_row += line_count;
3248                            }
3249                            similar::ChangeTag::Delete => {
3250                                let old_end_row = old_row + line_count - 1;
3251                                let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3252
3253                                if let Some((_, last_deleted_row_range)) =
3254                                    deleted_row_ranges.last_mut()
3255                                {
3256                                    if *last_deleted_row_range.end() + 1 == old_row {
3257                                        *last_deleted_row_range =
3258                                            *last_deleted_row_range.start()..=old_end_row;
3259                                    } else {
3260                                        deleted_row_ranges.push((new_row, old_row..=old_end_row));
3261                                    }
3262                                } else {
3263                                    deleted_row_ranges.push((new_row, old_row..=old_end_row));
3264                                }
3265
3266                                old_row += line_count;
3267                            }
3268                            similar::ChangeTag::Insert => {
3269                                let new_end_row = new_row + line_count - 1;
3270                                let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3271                                let end = new_snapshot.anchor_before(Point::new(
3272                                    new_end_row,
3273                                    new_snapshot.line_len(MultiBufferRow(new_end_row)),
3274                                ));
3275                                inserted_row_ranges.push(start..end);
3276                                new_row += line_count;
3277                            }
3278                        }
3279                    }
3280
3281                    (deleted_row_ranges, inserted_row_ranges)
3282                })
3283                .await;
3284
3285            codegen
3286                .update(&mut cx, |codegen, cx| {
3287                    codegen.diff.deleted_row_ranges = deleted_row_ranges;
3288                    codegen.diff.inserted_row_ranges = inserted_row_ranges;
3289                    cx.notify();
3290                })
3291                .ok();
3292        })
3293    }
3294}
3295
3296struct StripInvalidSpans<T> {
3297    stream: T,
3298    stream_done: bool,
3299    buffer: String,
3300    first_line: bool,
3301    line_end: bool,
3302    starts_with_code_block: bool,
3303}
3304
3305impl<T> StripInvalidSpans<T>
3306where
3307    T: Stream<Item = Result<String>>,
3308{
3309    fn new(stream: T) -> Self {
3310        Self {
3311            stream,
3312            stream_done: false,
3313            buffer: String::new(),
3314            first_line: true,
3315            line_end: false,
3316            starts_with_code_block: false,
3317        }
3318    }
3319}
3320
3321impl<T> Stream for StripInvalidSpans<T>
3322where
3323    T: Stream<Item = Result<String>>,
3324{
3325    type Item = Result<String>;
3326
3327    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
3328        const CODE_BLOCK_DELIMITER: &str = "```";
3329        const CURSOR_SPAN: &str = "<|CURSOR|>";
3330
3331        let this = unsafe { self.get_unchecked_mut() };
3332        loop {
3333            if !this.stream_done {
3334                let mut stream = unsafe { Pin::new_unchecked(&mut this.stream) };
3335                match stream.as_mut().poll_next(cx) {
3336                    Poll::Ready(Some(Ok(chunk))) => {
3337                        this.buffer.push_str(&chunk);
3338                    }
3339                    Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(error))),
3340                    Poll::Ready(None) => {
3341                        this.stream_done = true;
3342                    }
3343                    Poll::Pending => return Poll::Pending,
3344                }
3345            }
3346
3347            let mut chunk = String::new();
3348            let mut consumed = 0;
3349            if !this.buffer.is_empty() {
3350                let mut lines = this.buffer.split('\n').enumerate().peekable();
3351                while let Some((line_ix, line)) = lines.next() {
3352                    if line_ix > 0 {
3353                        this.first_line = false;
3354                    }
3355
3356                    if this.first_line {
3357                        let trimmed_line = line.trim();
3358                        if lines.peek().is_some() {
3359                            if trimmed_line.starts_with(CODE_BLOCK_DELIMITER) {
3360                                consumed += line.len() + 1;
3361                                this.starts_with_code_block = true;
3362                                continue;
3363                            }
3364                        } else if trimmed_line.is_empty()
3365                            || prefixes(CODE_BLOCK_DELIMITER)
3366                                .any(|prefix| trimmed_line.starts_with(prefix))
3367                        {
3368                            break;
3369                        }
3370                    }
3371
3372                    let line_without_cursor = line.replace(CURSOR_SPAN, "");
3373                    if lines.peek().is_some() {
3374                        if this.line_end {
3375                            chunk.push('\n');
3376                        }
3377
3378                        chunk.push_str(&line_without_cursor);
3379                        this.line_end = true;
3380                        consumed += line.len() + 1;
3381                    } else if this.stream_done {
3382                        if !this.starts_with_code_block
3383                            || !line_without_cursor.trim().ends_with(CODE_BLOCK_DELIMITER)
3384                        {
3385                            if this.line_end {
3386                                chunk.push('\n');
3387                            }
3388
3389                            chunk.push_str(&line);
3390                        }
3391
3392                        consumed += line.len();
3393                    } else {
3394                        let trimmed_line = line.trim();
3395                        if trimmed_line.is_empty()
3396                            || prefixes(CURSOR_SPAN).any(|prefix| trimmed_line.ends_with(prefix))
3397                            || prefixes(CODE_BLOCK_DELIMITER)
3398                                .any(|prefix| trimmed_line.ends_with(prefix))
3399                        {
3400                            break;
3401                        } else {
3402                            if this.line_end {
3403                                chunk.push('\n');
3404                                this.line_end = false;
3405                            }
3406
3407                            chunk.push_str(&line_without_cursor);
3408                            consumed += line.len();
3409                        }
3410                    }
3411                }
3412            }
3413
3414            this.buffer = this.buffer.split_off(consumed);
3415            if !chunk.is_empty() {
3416                return Poll::Ready(Some(Ok(chunk)));
3417            } else if this.stream_done {
3418                return Poll::Ready(None);
3419            }
3420        }
3421    }
3422}
3423
3424struct AssistantCodeActionProvider {
3425    editor: WeakView<Editor>,
3426    workspace: WeakView<Workspace>,
3427}
3428
3429impl CodeActionProvider for AssistantCodeActionProvider {
3430    fn code_actions(
3431        &self,
3432        buffer: &Model<Buffer>,
3433        range: Range<text::Anchor>,
3434        cx: &mut WindowContext,
3435    ) -> Task<Result<Vec<CodeAction>>> {
3436        if !AssistantSettings::get_global(cx).enabled {
3437            return Task::ready(Ok(Vec::new()));
3438        }
3439
3440        let snapshot = buffer.read(cx).snapshot();
3441        let mut range = range.to_point(&snapshot);
3442
3443        // Expand the range to line boundaries.
3444        range.start.column = 0;
3445        range.end.column = snapshot.line_len(range.end.row);
3446
3447        let mut has_diagnostics = false;
3448        for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) {
3449            range.start = cmp::min(range.start, diagnostic.range.start);
3450            range.end = cmp::max(range.end, diagnostic.range.end);
3451            has_diagnostics = true;
3452        }
3453        if has_diagnostics {
3454            if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) {
3455                if let Some(symbol) = symbols_containing_start.last() {
3456                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3457                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3458                }
3459            }
3460
3461            if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) {
3462                if let Some(symbol) = symbols_containing_end.last() {
3463                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3464                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3465                }
3466            }
3467
3468            Task::ready(Ok(vec![CodeAction {
3469                server_id: language::LanguageServerId(0),
3470                range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
3471                lsp_action: lsp::CodeAction {
3472                    title: "Fix with Assistant".into(),
3473                    ..Default::default()
3474                },
3475            }]))
3476        } else {
3477            Task::ready(Ok(Vec::new()))
3478        }
3479    }
3480
3481    fn apply_code_action(
3482        &self,
3483        buffer: Model<Buffer>,
3484        action: CodeAction,
3485        excerpt_id: ExcerptId,
3486        _push_to_history: bool,
3487        cx: &mut WindowContext,
3488    ) -> Task<Result<ProjectTransaction>> {
3489        let editor = self.editor.clone();
3490        let workspace = self.workspace.clone();
3491        cx.spawn(|mut cx| async move {
3492            let editor = editor.upgrade().context("editor was released")?;
3493            let range = editor
3494                .update(&mut cx, |editor, cx| {
3495                    editor.buffer().update(cx, |multibuffer, cx| {
3496                        let buffer = buffer.read(cx);
3497                        let multibuffer_snapshot = multibuffer.read(cx);
3498
3499                        let old_context_range =
3500                            multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
3501                        let mut new_context_range = old_context_range.clone();
3502                        if action
3503                            .range
3504                            .start
3505                            .cmp(&old_context_range.start, buffer)
3506                            .is_lt()
3507                        {
3508                            new_context_range.start = action.range.start;
3509                        }
3510                        if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
3511                            new_context_range.end = action.range.end;
3512                        }
3513                        drop(multibuffer_snapshot);
3514
3515                        if new_context_range != old_context_range {
3516                            multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
3517                        }
3518
3519                        let multibuffer_snapshot = multibuffer.read(cx);
3520                        Some(
3521                            multibuffer_snapshot
3522                                .anchor_in_excerpt(excerpt_id, action.range.start)?
3523                                ..multibuffer_snapshot
3524                                    .anchor_in_excerpt(excerpt_id, action.range.end)?,
3525                        )
3526                    })
3527                })?
3528                .context("invalid range")?;
3529            let assistant_panel = workspace.update(&mut cx, |workspace, cx| {
3530                workspace
3531                    .panel::<AssistantPanel>(cx)
3532                    .context("assistant panel was released")
3533            })??;
3534
3535            cx.update_global(|assistant: &mut InlineAssistant, cx| {
3536                let assist_id = assistant.suggest_assist(
3537                    &editor,
3538                    range,
3539                    "Fix Diagnostics".into(),
3540                    None,
3541                    true,
3542                    Some(workspace),
3543                    Some(&assistant_panel),
3544                    cx,
3545                );
3546                assistant.start_assist(assist_id, cx);
3547            })?;
3548
3549            Ok(ProjectTransaction::default())
3550        })
3551    }
3552}
3553
3554fn prefixes(text: &str) -> impl Iterator<Item = &str> {
3555    (0..text.len() - 1).map(|ix| &text[..ix + 1])
3556}
3557
3558fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
3559    ranges.sort_unstable_by(|a, b| {
3560        a.start
3561            .cmp(&b.start, buffer)
3562            .then_with(|| b.end.cmp(&a.end, buffer))
3563    });
3564
3565    let mut ix = 0;
3566    while ix + 1 < ranges.len() {
3567        let b = ranges[ix + 1].clone();
3568        let a = &mut ranges[ix];
3569        if a.end.cmp(&b.start, buffer).is_gt() {
3570            if a.end.cmp(&b.end, buffer).is_lt() {
3571                a.end = b.end;
3572            }
3573            ranges.remove(ix + 1);
3574        } else {
3575            ix += 1;
3576        }
3577    }
3578}
3579
3580#[cfg(test)]
3581mod tests {
3582    use super::*;
3583    use futures::stream::{self};
3584    use gpui::{Context, TestAppContext};
3585    use indoc::indoc;
3586    use language::{
3587        language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
3588        Point,
3589    };
3590    use language_model::LanguageModelRegistry;
3591    use rand::prelude::*;
3592    use serde::Serialize;
3593    use settings::SettingsStore;
3594    use std::{future, sync::Arc};
3595
3596    #[derive(Serialize)]
3597    pub struct DummyCompletionRequest {
3598        pub name: String,
3599    }
3600
3601    #[gpui::test(iterations = 10)]
3602    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
3603        cx.set_global(cx.update(SettingsStore::test));
3604        cx.update(language_model::LanguageModelRegistry::test);
3605        cx.update(language_settings::init);
3606
3607        let text = indoc! {"
3608            fn main() {
3609                let x = 0;
3610                for _ in 0..10 {
3611                    x += 1;
3612                }
3613            }
3614        "};
3615        let buffer =
3616            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3617        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3618        let range = buffer.read_with(cx, |buffer, cx| {
3619            let snapshot = buffer.snapshot(cx);
3620            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
3621        });
3622        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3623        let codegen = cx.new_model(|cx| {
3624            CodegenAlternative::new(
3625                buffer.clone(),
3626                range.clone(),
3627                true,
3628                None,
3629                prompt_builder,
3630                cx,
3631            )
3632        });
3633
3634        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3635
3636        let mut new_text = concat!(
3637            "       let mut x = 0;\n",
3638            "       while x < 10 {\n",
3639            "           x += 1;\n",
3640            "       }",
3641        );
3642        while !new_text.is_empty() {
3643            let max_len = cmp::min(new_text.len(), 10);
3644            let len = rng.gen_range(1..=max_len);
3645            let (chunk, suffix) = new_text.split_at(len);
3646            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3647            new_text = suffix;
3648            cx.background_executor.run_until_parked();
3649        }
3650        drop(chunks_tx);
3651        cx.background_executor.run_until_parked();
3652
3653        assert_eq!(
3654            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3655            indoc! {"
3656                fn main() {
3657                    let mut x = 0;
3658                    while x < 10 {
3659                        x += 1;
3660                    }
3661                }
3662            "}
3663        );
3664    }
3665
3666    #[gpui::test(iterations = 10)]
3667    async fn test_autoindent_when_generating_past_indentation(
3668        cx: &mut TestAppContext,
3669        mut rng: StdRng,
3670    ) {
3671        cx.set_global(cx.update(SettingsStore::test));
3672        cx.update(language_settings::init);
3673
3674        let text = indoc! {"
3675            fn main() {
3676                le
3677            }
3678        "};
3679        let buffer =
3680            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3681        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3682        let range = buffer.read_with(cx, |buffer, cx| {
3683            let snapshot = buffer.snapshot(cx);
3684            snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
3685        });
3686        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3687        let codegen = cx.new_model(|cx| {
3688            CodegenAlternative::new(
3689                buffer.clone(),
3690                range.clone(),
3691                true,
3692                None,
3693                prompt_builder,
3694                cx,
3695            )
3696        });
3697
3698        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3699
3700        cx.background_executor.run_until_parked();
3701
3702        let mut new_text = concat!(
3703            "t mut x = 0;\n",
3704            "while x < 10 {\n",
3705            "    x += 1;\n",
3706            "}", //
3707        );
3708        while !new_text.is_empty() {
3709            let max_len = cmp::min(new_text.len(), 10);
3710            let len = rng.gen_range(1..=max_len);
3711            let (chunk, suffix) = new_text.split_at(len);
3712            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3713            new_text = suffix;
3714            cx.background_executor.run_until_parked();
3715        }
3716        drop(chunks_tx);
3717        cx.background_executor.run_until_parked();
3718
3719        assert_eq!(
3720            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3721            indoc! {"
3722                fn main() {
3723                    let mut x = 0;
3724                    while x < 10 {
3725                        x += 1;
3726                    }
3727                }
3728            "}
3729        );
3730    }
3731
3732    #[gpui::test(iterations = 10)]
3733    async fn test_autoindent_when_generating_before_indentation(
3734        cx: &mut TestAppContext,
3735        mut rng: StdRng,
3736    ) {
3737        cx.update(LanguageModelRegistry::test);
3738        cx.set_global(cx.update(SettingsStore::test));
3739        cx.update(language_settings::init);
3740
3741        let text = concat!(
3742            "fn main() {\n",
3743            "  \n",
3744            "}\n" //
3745        );
3746        let buffer =
3747            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3748        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3749        let range = buffer.read_with(cx, |buffer, cx| {
3750            let snapshot = buffer.snapshot(cx);
3751            snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
3752        });
3753        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3754        let codegen = cx.new_model(|cx| {
3755            CodegenAlternative::new(
3756                buffer.clone(),
3757                range.clone(),
3758                true,
3759                None,
3760                prompt_builder,
3761                cx,
3762            )
3763        });
3764
3765        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3766
3767        cx.background_executor.run_until_parked();
3768
3769        let mut new_text = concat!(
3770            "let mut x = 0;\n",
3771            "while x < 10 {\n",
3772            "    x += 1;\n",
3773            "}", //
3774        );
3775        while !new_text.is_empty() {
3776            let max_len = cmp::min(new_text.len(), 10);
3777            let len = rng.gen_range(1..=max_len);
3778            let (chunk, suffix) = new_text.split_at(len);
3779            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3780            new_text = suffix;
3781            cx.background_executor.run_until_parked();
3782        }
3783        drop(chunks_tx);
3784        cx.background_executor.run_until_parked();
3785
3786        assert_eq!(
3787            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3788            indoc! {"
3789                fn main() {
3790                    let mut x = 0;
3791                    while x < 10 {
3792                        x += 1;
3793                    }
3794                }
3795            "}
3796        );
3797    }
3798
3799    #[gpui::test(iterations = 10)]
3800    async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) {
3801        cx.update(LanguageModelRegistry::test);
3802        cx.set_global(cx.update(SettingsStore::test));
3803        cx.update(language_settings::init);
3804
3805        let text = indoc! {"
3806            func main() {
3807            \tx := 0
3808            \tfor i := 0; i < 10; i++ {
3809            \t\tx++
3810            \t}
3811            }
3812        "};
3813        let buffer = cx.new_model(|cx| Buffer::local(text, cx));
3814        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3815        let range = buffer.read_with(cx, |buffer, cx| {
3816            let snapshot = buffer.snapshot(cx);
3817            snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
3818        });
3819        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3820        let codegen = cx.new_model(|cx| {
3821            CodegenAlternative::new(
3822                buffer.clone(),
3823                range.clone(),
3824                true,
3825                None,
3826                prompt_builder,
3827                cx,
3828            )
3829        });
3830
3831        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3832        let new_text = concat!(
3833            "func main() {\n",
3834            "\tx := 0\n",
3835            "\tfor x < 10 {\n",
3836            "\t\tx++\n",
3837            "\t}", //
3838        );
3839        chunks_tx.unbounded_send(new_text.to_string()).unwrap();
3840        drop(chunks_tx);
3841        cx.background_executor.run_until_parked();
3842
3843        assert_eq!(
3844            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3845            indoc! {"
3846                func main() {
3847                \tx := 0
3848                \tfor x < 10 {
3849                \t\tx++
3850                \t}
3851                }
3852            "}
3853        );
3854    }
3855
3856    #[gpui::test]
3857    async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) {
3858        cx.update(LanguageModelRegistry::test);
3859        cx.set_global(cx.update(SettingsStore::test));
3860        cx.update(language_settings::init);
3861
3862        let text = indoc! {"
3863            fn main() {
3864                let x = 0;
3865            }
3866        "};
3867        let buffer =
3868            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3869        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3870        let range = buffer.read_with(cx, |buffer, cx| {
3871            let snapshot = buffer.snapshot(cx);
3872            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
3873        });
3874        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3875        let codegen = cx.new_model(|cx| {
3876            CodegenAlternative::new(
3877                buffer.clone(),
3878                range.clone(),
3879                false,
3880                None,
3881                prompt_builder,
3882                cx,
3883            )
3884        });
3885
3886        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3887        chunks_tx
3888            .unbounded_send("let mut x = 0;\nx += 1;".to_string())
3889            .unwrap();
3890        drop(chunks_tx);
3891        cx.run_until_parked();
3892
3893        // The codegen is inactive, so the buffer doesn't get modified.
3894        assert_eq!(
3895            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3896            text
3897        );
3898
3899        // Activating the codegen applies the changes.
3900        codegen.update(cx, |codegen, cx| codegen.set_active(true, cx));
3901        assert_eq!(
3902            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3903            indoc! {"
3904                fn main() {
3905                    let mut x = 0;
3906                    x += 1;
3907                }
3908            "}
3909        );
3910
3911        // Deactivating the codegen undoes the changes.
3912        codegen.update(cx, |codegen, cx| codegen.set_active(false, cx));
3913        cx.run_until_parked();
3914        assert_eq!(
3915            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3916            text
3917        );
3918    }
3919
3920    #[gpui::test]
3921    async fn test_strip_invalid_spans_from_codeblock() {
3922        assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
3923        assert_chunks("```\nLorem ipsum dolor", "Lorem ipsum dolor").await;
3924        assert_chunks("```\nLorem ipsum dolor\n```", "Lorem ipsum dolor").await;
3925        assert_chunks(
3926            "```html\n```js\nLorem ipsum dolor\n```\n```",
3927            "```js\nLorem ipsum dolor\n```",
3928        )
3929        .await;
3930        assert_chunks("``\nLorem ipsum dolor\n```", "``\nLorem ipsum dolor\n```").await;
3931        assert_chunks("Lorem<|CURSOR|> ipsum", "Lorem ipsum").await;
3932        assert_chunks("Lorem ipsum", "Lorem ipsum").await;
3933        assert_chunks("```\n<|CURSOR|>Lorem ipsum\n```", "Lorem ipsum").await;
3934
3935        async fn assert_chunks(text: &str, expected_text: &str) {
3936            for chunk_size in 1..=text.len() {
3937                let actual_text = StripInvalidSpans::new(chunks(text, chunk_size))
3938                    .map(|chunk| chunk.unwrap())
3939                    .collect::<String>()
3940                    .await;
3941                assert_eq!(
3942                    actual_text, expected_text,
3943                    "failed to strip invalid spans, chunk size: {}",
3944                    chunk_size
3945                );
3946            }
3947        }
3948
3949        fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
3950            stream::iter(
3951                text.chars()
3952                    .collect::<Vec<_>>()
3953                    .chunks(size)
3954                    .map(|chunk| Ok(chunk.iter().collect::<String>()))
3955                    .collect::<Vec<_>>(),
3956            )
3957        }
3958    }
3959
3960    fn simulate_response_stream(
3961        codegen: Model<CodegenAlternative>,
3962        cx: &mut TestAppContext,
3963    ) -> mpsc::UnboundedSender<String> {
3964        let (chunks_tx, chunks_rx) = mpsc::unbounded();
3965        codegen.update(cx, |codegen, cx| {
3966            codegen.handle_stream(
3967                String::new(),
3968                String::new(),
3969                None,
3970                future::ready(Ok(LanguageModelTextStream {
3971                    message_id: None,
3972                    stream: chunks_rx.map(Ok).boxed(),
3973                })),
3974                cx,
3975            );
3976        });
3977        chunks_tx
3978    }
3979
3980    fn rust_lang() -> Language {
3981        Language::new(
3982            LanguageConfig {
3983                name: "Rust".into(),
3984                matcher: LanguageMatcher {
3985                    path_suffixes: vec!["rs".to_string()],
3986                    ..Default::default()
3987                },
3988                ..Default::default()
3989            },
3990            Some(tree_sitter_rust::LANGUAGE.into()),
3991        )
3992        .with_indents_query(
3993            r#"
3994            (call_expression) @indent
3995            (field_expression) @indent
3996            (_ "(" ")" @end) @indent
3997            (_ "{" "}" @end) @indent
3998            "#,
3999        )
4000        .unwrap()
4001    }
4002}