inline_assistant.rs

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