inline_assistant.rs

   1use crate::{
   2    AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist,
   3};
   4use anyhow::{anyhow, Context as _, Result};
   5use assistant_context_editor::{humanize_token_count, RequestType};
   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_show_scrollbars(false, cx);
1210                    editor.set_read_only(true);
1211                    editor.set_show_inline_completions(Some(false), cx);
1212                    editor.highlight_rows::<DeletedLines>(
1213                        Anchor::min()..Anchor::max(),
1214                        cx.theme().status().deleted_background,
1215                        false,
1216                        cx,
1217                    );
1218                    editor
1219                });
1220
1221                let height =
1222                    deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
1223                new_blocks.push(BlockProperties {
1224                    placement: BlockPlacement::Above(new_row),
1225                    height,
1226                    style: BlockStyle::Flex,
1227                    render: Arc::new(move |cx| {
1228                        div()
1229                            .block_mouse_down()
1230                            .bg(cx.theme().status().deleted_background)
1231                            .size_full()
1232                            .h(height as f32 * cx.line_height())
1233                            .pl(cx.gutter_dimensions.full_width())
1234                            .child(deleted_lines_editor.clone())
1235                            .into_any_element()
1236                    }),
1237                    priority: 0,
1238                });
1239            }
1240
1241            decorations.removed_line_block_ids = editor
1242                .insert_blocks(new_blocks, None, cx)
1243                .into_iter()
1244                .collect();
1245        })
1246    }
1247}
1248
1249struct EditorInlineAssists {
1250    assist_ids: Vec<InlineAssistId>,
1251    scroll_lock: Option<InlineAssistScrollLock>,
1252    highlight_updates: async_watch::Sender<()>,
1253    _update_highlights: Task<Result<()>>,
1254    _subscriptions: Vec<gpui::Subscription>,
1255}
1256
1257struct InlineAssistScrollLock {
1258    assist_id: InlineAssistId,
1259    distance_from_top: f32,
1260}
1261
1262impl EditorInlineAssists {
1263    #[allow(clippy::too_many_arguments)]
1264    fn new(editor: &View<Editor>, cx: &mut WindowContext) -> Self {
1265        let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
1266        Self {
1267            assist_ids: Vec::new(),
1268            scroll_lock: None,
1269            highlight_updates: highlight_updates_tx,
1270            _update_highlights: cx.spawn(|mut cx| {
1271                let editor = editor.downgrade();
1272                async move {
1273                    while let Ok(()) = highlight_updates_rx.changed().await {
1274                        let editor = editor.upgrade().context("editor was dropped")?;
1275                        cx.update_global(|assistant: &mut InlineAssistant, cx| {
1276                            assistant.update_editor_highlights(&editor, cx);
1277                        })?;
1278                    }
1279                    Ok(())
1280                }
1281            }),
1282            _subscriptions: vec![
1283                cx.observe_release(editor, {
1284                    let editor = editor.downgrade();
1285                    |_, cx| {
1286                        InlineAssistant::update_global(cx, |this, cx| {
1287                            this.handle_editor_release(editor, cx);
1288                        })
1289                    }
1290                }),
1291                cx.observe(editor, move |editor, cx| {
1292                    InlineAssistant::update_global(cx, |this, cx| {
1293                        this.handle_editor_change(editor, cx)
1294                    })
1295                }),
1296                cx.subscribe(editor, move |editor, event, cx| {
1297                    InlineAssistant::update_global(cx, |this, cx| {
1298                        this.handle_editor_event(editor, event, cx)
1299                    })
1300                }),
1301                editor.update(cx, |editor, cx| {
1302                    let editor_handle = cx.view().downgrade();
1303                    editor.register_action(
1304                        move |_: &editor::actions::Newline, cx: &mut WindowContext| {
1305                            InlineAssistant::update_global(cx, |this, cx| {
1306                                if let Some(editor) = editor_handle.upgrade() {
1307                                    this.handle_editor_newline(editor, cx)
1308                                }
1309                            })
1310                        },
1311                    )
1312                }),
1313                editor.update(cx, |editor, cx| {
1314                    let editor_handle = cx.view().downgrade();
1315                    editor.register_action(
1316                        move |_: &editor::actions::Cancel, cx: &mut WindowContext| {
1317                            InlineAssistant::update_global(cx, |this, cx| {
1318                                if let Some(editor) = editor_handle.upgrade() {
1319                                    this.handle_editor_cancel(editor, cx)
1320                                }
1321                            })
1322                        },
1323                    )
1324                }),
1325            ],
1326        }
1327    }
1328}
1329
1330struct InlineAssistGroup {
1331    assist_ids: Vec<InlineAssistId>,
1332    linked: bool,
1333    active_assist_id: Option<InlineAssistId>,
1334}
1335
1336impl InlineAssistGroup {
1337    fn new() -> Self {
1338        Self {
1339            assist_ids: Vec::new(),
1340            linked: true,
1341            active_assist_id: None,
1342        }
1343    }
1344}
1345
1346fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
1347    let editor = editor.clone();
1348    Arc::new(move |cx: &mut BlockContext| {
1349        *editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
1350        editor.clone().into_any_element()
1351    })
1352}
1353
1354#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
1355pub struct InlineAssistId(usize);
1356
1357impl InlineAssistId {
1358    fn post_inc(&mut self) -> InlineAssistId {
1359        let id = *self;
1360        self.0 += 1;
1361        id
1362    }
1363}
1364
1365#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
1366struct InlineAssistGroupId(usize);
1367
1368impl InlineAssistGroupId {
1369    fn post_inc(&mut self) -> InlineAssistGroupId {
1370        let id = *self;
1371        self.0 += 1;
1372        id
1373    }
1374}
1375
1376enum PromptEditorEvent {
1377    StartRequested,
1378    StopRequested,
1379    ConfirmRequested,
1380    CancelRequested,
1381    DismissRequested,
1382}
1383
1384struct PromptEditor {
1385    id: InlineAssistId,
1386    editor: View<Editor>,
1387    language_model_selector: View<LanguageModelSelector>,
1388    edited_since_done: bool,
1389    gutter_dimensions: Arc<Mutex<GutterDimensions>>,
1390    prompt_history: VecDeque<String>,
1391    prompt_history_ix: Option<usize>,
1392    pending_prompt: String,
1393    codegen: Model<Codegen>,
1394    _codegen_subscription: Subscription,
1395    editor_subscriptions: Vec<Subscription>,
1396    pending_token_count: Task<Result<()>>,
1397    token_counts: Option<TokenCounts>,
1398    _token_count_subscriptions: Vec<Subscription>,
1399    workspace: Option<WeakView<Workspace>>,
1400    show_rate_limit_notice: bool,
1401}
1402
1403#[derive(Copy, Clone)]
1404pub struct TokenCounts {
1405    total: usize,
1406    assistant_panel: usize,
1407}
1408
1409impl EventEmitter<PromptEditorEvent> for PromptEditor {}
1410
1411impl Render for PromptEditor {
1412    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1413        let gutter_dimensions = *self.gutter_dimensions.lock();
1414        let codegen = self.codegen.read(cx);
1415
1416        let mut buttons = Vec::new();
1417        if codegen.alternative_count(cx) > 1 {
1418            buttons.push(self.render_cycle_controls(cx));
1419        }
1420
1421        let status = codegen.status(cx);
1422        buttons.extend(match status {
1423            CodegenStatus::Idle => {
1424                vec![
1425                    IconButton::new("cancel", IconName::Close)
1426                        .icon_color(Color::Muted)
1427                        .shape(IconButtonShape::Square)
1428                        .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
1429                        .on_click(
1430                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1431                        )
1432                        .into_any_element(),
1433                    IconButton::new("start", IconName::SparkleAlt)
1434                        .icon_color(Color::Muted)
1435                        .shape(IconButtonShape::Square)
1436                        .tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
1437                        .on_click(
1438                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
1439                        )
1440                        .into_any_element(),
1441                ]
1442            }
1443            CodegenStatus::Pending => {
1444                vec![
1445                    IconButton::new("cancel", IconName::Close)
1446                        .icon_color(Color::Muted)
1447                        .shape(IconButtonShape::Square)
1448                        .tooltip(|cx| Tooltip::text("Cancel Assist", cx))
1449                        .on_click(
1450                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1451                        )
1452                        .into_any_element(),
1453                    IconButton::new("stop", IconName::Stop)
1454                        .icon_color(Color::Error)
1455                        .shape(IconButtonShape::Square)
1456                        .tooltip(|cx| {
1457                            Tooltip::with_meta(
1458                                "Interrupt Transformation",
1459                                Some(&menu::Cancel),
1460                                "Changes won't be discarded",
1461                                cx,
1462                            )
1463                        })
1464                        .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
1465                        .into_any_element(),
1466                ]
1467            }
1468            CodegenStatus::Error(_) | CodegenStatus::Done => {
1469                let must_rerun =
1470                    self.edited_since_done || matches!(status, CodegenStatus::Error(_));
1471                // when accept button isn't visible, then restart maps to confirm
1472                // when accept button is visible, then restart must be mapped to an alternate keyboard shortcut
1473                let restart_key: &dyn gpui::Action = if must_rerun {
1474                    &menu::Confirm
1475                } else {
1476                    &menu::Restart
1477                };
1478                vec![
1479                    IconButton::new("cancel", IconName::Close)
1480                        .icon_color(Color::Muted)
1481                        .shape(IconButtonShape::Square)
1482                        .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
1483                        .on_click(
1484                            cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
1485                        )
1486                        .into_any_element(),
1487                    IconButton::new("restart", IconName::RotateCw)
1488                        .icon_color(Color::Muted)
1489                        .shape(IconButtonShape::Square)
1490                        .tooltip(|cx| {
1491                            Tooltip::with_meta(
1492                                "Regenerate Transformation",
1493                                Some(restart_key),
1494                                "Current change will be discarded",
1495                                cx,
1496                            )
1497                        })
1498                        .on_click(cx.listener(|_, _, cx| {
1499                            cx.emit(PromptEditorEvent::StartRequested);
1500                        }))
1501                        .into_any_element(),
1502                    if !must_rerun {
1503                        IconButton::new("confirm", IconName::Check)
1504                            .icon_color(Color::Info)
1505                            .shape(IconButtonShape::Square)
1506                            .tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
1507                            .on_click(cx.listener(|_, _, cx| {
1508                                cx.emit(PromptEditorEvent::ConfirmRequested);
1509                            }))
1510                            .into_any_element()
1511                    } else {
1512                        div().into_any_element()
1513                    },
1514                ]
1515            }
1516        });
1517
1518        h_flex()
1519            .key_context("PromptEditor")
1520            .bg(cx.theme().colors().editor_background)
1521            .block_mouse_down()
1522            .cursor(CursorStyle::Arrow)
1523            .border_y_1()
1524            .border_color(cx.theme().status().info_border)
1525            .size_full()
1526            .py(cx.line_height() / 2.5)
1527            .on_action(cx.listener(Self::confirm))
1528            .on_action(cx.listener(Self::cancel))
1529            .on_action(cx.listener(Self::restart))
1530            .on_action(cx.listener(Self::move_up))
1531            .on_action(cx.listener(Self::move_down))
1532            .capture_action(cx.listener(Self::cycle_prev))
1533            .capture_action(cx.listener(Self::cycle_next))
1534            .child(
1535                h_flex()
1536                    .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
1537                    .justify_center()
1538                    .gap_2()
1539                    .child(LanguageModelSelectorPopoverMenu::new(
1540                        self.language_model_selector.clone(),
1541                        IconButton::new("context", IconName::SettingsAlt)
1542                            .shape(IconButtonShape::Square)
1543                            .icon_size(IconSize::Small)
1544                            .icon_color(Color::Muted)
1545                            .tooltip(move |cx| {
1546                                Tooltip::with_meta(
1547                                    format!(
1548                                        "Using {}",
1549                                        LanguageModelRegistry::read_global(cx)
1550                                            .active_model()
1551                                            .map(|model| model.name().0)
1552                                            .unwrap_or_else(|| "No model selected".into()),
1553                                    ),
1554                                    None,
1555                                    "Change Model",
1556                                    cx,
1557                                )
1558                            }),
1559                    ))
1560                    .map(|el| {
1561                        let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
1562                            return el;
1563                        };
1564
1565                        let error_message = SharedString::from(error.to_string());
1566                        if error.error_code() == proto::ErrorCode::RateLimitExceeded
1567                            && cx.has_flag::<ZedPro>()
1568                        {
1569                            el.child(
1570                                v_flex()
1571                                    .child(
1572                                        IconButton::new("rate-limit-error", IconName::XCircle)
1573                                            .toggle_state(self.show_rate_limit_notice)
1574                                            .shape(IconButtonShape::Square)
1575                                            .icon_size(IconSize::Small)
1576                                            .on_click(cx.listener(Self::toggle_rate_limit_notice)),
1577                                    )
1578                                    .children(self.show_rate_limit_notice.then(|| {
1579                                        deferred(
1580                                            anchored()
1581                                                .position_mode(gpui::AnchoredPositionMode::Local)
1582                                                .position(point(px(0.), px(24.)))
1583                                                .anchor(gpui::Corner::TopLeft)
1584                                                .child(self.render_rate_limit_notice(cx)),
1585                                        )
1586                                    })),
1587                            )
1588                        } else {
1589                            el.child(
1590                                div()
1591                                    .id("error")
1592                                    .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
1593                                    .child(
1594                                        Icon::new(IconName::XCircle)
1595                                            .size(IconSize::Small)
1596                                            .color(Color::Error),
1597                                    ),
1598                            )
1599                        }
1600                    }),
1601            )
1602            .child(div().flex_1().child(self.render_prompt_editor(cx)))
1603            .child(
1604                h_flex()
1605                    .gap_2()
1606                    .pr_6()
1607                    .children(self.render_token_count(cx))
1608                    .children(buttons),
1609            )
1610    }
1611}
1612
1613impl FocusableView for PromptEditor {
1614    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1615        self.editor.focus_handle(cx)
1616    }
1617}
1618
1619impl PromptEditor {
1620    const MAX_LINES: u8 = 8;
1621
1622    #[allow(clippy::too_many_arguments)]
1623    fn new(
1624        id: InlineAssistId,
1625        gutter_dimensions: Arc<Mutex<GutterDimensions>>,
1626        prompt_history: VecDeque<String>,
1627        prompt_buffer: Model<MultiBuffer>,
1628        codegen: Model<Codegen>,
1629        parent_editor: &View<Editor>,
1630        assistant_panel: Option<&View<AssistantPanel>>,
1631        workspace: Option<WeakView<Workspace>>,
1632        fs: Arc<dyn Fs>,
1633        cx: &mut ViewContext<Self>,
1634    ) -> Self {
1635        let prompt_editor = cx.new_view(|cx| {
1636            let mut editor = Editor::new(
1637                EditorMode::AutoHeight {
1638                    max_lines: Self::MAX_LINES as usize,
1639                },
1640                prompt_buffer,
1641                None,
1642                false,
1643                cx,
1644            );
1645            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
1646            // Since the prompt editors for all inline assistants are linked,
1647            // always show the cursor (even when it isn't focused) because
1648            // typing in one will make what you typed appear in all of them.
1649            editor.set_show_cursor_when_unfocused(true, cx);
1650            editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
1651            editor
1652        });
1653
1654        let mut token_count_subscriptions = Vec::new();
1655        token_count_subscriptions
1656            .push(cx.subscribe(parent_editor, Self::handle_parent_editor_event));
1657        if let Some(assistant_panel) = assistant_panel {
1658            token_count_subscriptions
1659                .push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
1660        }
1661
1662        let mut this = Self {
1663            id,
1664            editor: prompt_editor,
1665            language_model_selector: cx.new_view(|cx| {
1666                let fs = fs.clone();
1667                LanguageModelSelector::new(
1668                    move |model, cx| {
1669                        update_settings_file::<AssistantSettings>(
1670                            fs.clone(),
1671                            cx,
1672                            move |settings, _| settings.set_model(model.clone()),
1673                        );
1674                    },
1675                    cx,
1676                )
1677            }),
1678            edited_since_done: false,
1679            gutter_dimensions,
1680            prompt_history,
1681            prompt_history_ix: None,
1682            pending_prompt: String::new(),
1683            _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
1684            editor_subscriptions: Vec::new(),
1685            codegen,
1686            pending_token_count: Task::ready(Ok(())),
1687            token_counts: None,
1688            _token_count_subscriptions: token_count_subscriptions,
1689            workspace,
1690            show_rate_limit_notice: false,
1691        };
1692        this.count_tokens(cx);
1693        this.subscribe_to_editor(cx);
1694        this
1695    }
1696
1697    fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
1698        self.editor_subscriptions.clear();
1699        self.editor_subscriptions
1700            .push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
1701    }
1702
1703    fn set_show_cursor_when_unfocused(
1704        &mut self,
1705        show_cursor_when_unfocused: bool,
1706        cx: &mut ViewContext<Self>,
1707    ) {
1708        self.editor.update(cx, |editor, cx| {
1709            editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx)
1710        });
1711    }
1712
1713    fn unlink(&mut self, cx: &mut ViewContext<Self>) {
1714        let prompt = self.prompt(cx);
1715        let focus = self.editor.focus_handle(cx).contains_focused(cx);
1716        self.editor = cx.new_view(|cx| {
1717            let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
1718            editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
1719            editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
1720            editor.set_placeholder_text("Add a prompt…", cx);
1721            editor.set_text(prompt, cx);
1722            if focus {
1723                editor.focus(cx);
1724            }
1725            editor
1726        });
1727        self.subscribe_to_editor(cx);
1728    }
1729
1730    fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
1731        let context_keybinding = text_for_action(&zed_actions::assistant::ToggleFocus, cx)
1732            .map(|keybinding| format!("{keybinding} for context"))
1733            .unwrap_or_default();
1734
1735        let action = if codegen.is_insertion {
1736            "Generate"
1737        } else {
1738            "Transform"
1739        };
1740
1741        format!("{action}{context_keybinding} • ↓↑ for history")
1742    }
1743
1744    fn prompt(&self, cx: &AppContext) -> String {
1745        self.editor.read(cx).text(cx)
1746    }
1747
1748    fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
1749        self.show_rate_limit_notice = !self.show_rate_limit_notice;
1750        if self.show_rate_limit_notice {
1751            cx.focus_view(&self.editor);
1752        }
1753        cx.notify();
1754    }
1755
1756    fn handle_parent_editor_event(
1757        &mut self,
1758        _: View<Editor>,
1759        event: &EditorEvent,
1760        cx: &mut ViewContext<Self>,
1761    ) {
1762        if let EditorEvent::BufferEdited { .. } = event {
1763            self.count_tokens(cx);
1764        }
1765    }
1766
1767    fn handle_assistant_panel_event(
1768        &mut self,
1769        _: View<AssistantPanel>,
1770        event: &AssistantPanelEvent,
1771        cx: &mut ViewContext<Self>,
1772    ) {
1773        let AssistantPanelEvent::ContextEdited { .. } = event;
1774        self.count_tokens(cx);
1775    }
1776
1777    fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
1778        let assist_id = self.id;
1779        self.pending_token_count = cx.spawn(|this, mut cx| async move {
1780            cx.background_executor().timer(Duration::from_secs(1)).await;
1781            let token_count = cx
1782                .update_global(|inline_assistant: &mut InlineAssistant, cx| {
1783                    let assist = inline_assistant
1784                        .assists
1785                        .get(&assist_id)
1786                        .context("assist not found")?;
1787                    anyhow::Ok(assist.count_tokens(cx))
1788                })??
1789                .await?;
1790
1791            this.update(&mut cx, |this, cx| {
1792                this.token_counts = Some(token_count);
1793                cx.notify();
1794            })
1795        })
1796    }
1797
1798    fn handle_prompt_editor_events(
1799        &mut self,
1800        _: View<Editor>,
1801        event: &EditorEvent,
1802        cx: &mut ViewContext<Self>,
1803    ) {
1804        match event {
1805            EditorEvent::Edited { .. } => {
1806                if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
1807                    workspace
1808                        .update(cx, |workspace, cx| {
1809                            let is_via_ssh = workspace
1810                                .project()
1811                                .update(cx, |project, _| project.is_via_ssh());
1812
1813                            workspace
1814                                .client()
1815                                .telemetry()
1816                                .log_edit_event("inline assist", is_via_ssh);
1817                        })
1818                        .log_err();
1819                }
1820                let prompt = self.editor.read(cx).text(cx);
1821                if self
1822                    .prompt_history_ix
1823                    .map_or(true, |ix| self.prompt_history[ix] != prompt)
1824                {
1825                    self.prompt_history_ix.take();
1826                    self.pending_prompt = prompt;
1827                }
1828
1829                self.edited_since_done = true;
1830                cx.notify();
1831            }
1832            EditorEvent::BufferEdited => {
1833                self.count_tokens(cx);
1834            }
1835            EditorEvent::Blurred => {
1836                if self.show_rate_limit_notice {
1837                    self.show_rate_limit_notice = false;
1838                    cx.notify();
1839                }
1840            }
1841            _ => {}
1842        }
1843    }
1844
1845    fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
1846        match self.codegen.read(cx).status(cx) {
1847            CodegenStatus::Idle => {
1848                self.editor
1849                    .update(cx, |editor, _| editor.set_read_only(false));
1850            }
1851            CodegenStatus::Pending => {
1852                self.editor
1853                    .update(cx, |editor, _| editor.set_read_only(true));
1854            }
1855            CodegenStatus::Done => {
1856                self.edited_since_done = false;
1857                self.editor
1858                    .update(cx, |editor, _| editor.set_read_only(false));
1859            }
1860            CodegenStatus::Error(error) => {
1861                if cx.has_flag::<ZedPro>()
1862                    && error.error_code() == proto::ErrorCode::RateLimitExceeded
1863                    && !dismissed_rate_limit_notice()
1864                {
1865                    self.show_rate_limit_notice = true;
1866                    cx.notify();
1867                }
1868
1869                self.edited_since_done = false;
1870                self.editor
1871                    .update(cx, |editor, _| editor.set_read_only(false));
1872            }
1873        }
1874    }
1875
1876    fn restart(&mut self, _: &menu::Restart, cx: &mut ViewContext<Self>) {
1877        cx.emit(PromptEditorEvent::StartRequested);
1878    }
1879
1880    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1881        match self.codegen.read(cx).status(cx) {
1882            CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
1883                cx.emit(PromptEditorEvent::CancelRequested);
1884            }
1885            CodegenStatus::Pending => {
1886                cx.emit(PromptEditorEvent::StopRequested);
1887            }
1888        }
1889    }
1890
1891    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
1892        match self.codegen.read(cx).status(cx) {
1893            CodegenStatus::Idle => {
1894                cx.emit(PromptEditorEvent::StartRequested);
1895            }
1896            CodegenStatus::Pending => {
1897                cx.emit(PromptEditorEvent::DismissRequested);
1898            }
1899            CodegenStatus::Done => {
1900                if self.edited_since_done {
1901                    cx.emit(PromptEditorEvent::StartRequested);
1902                } else {
1903                    cx.emit(PromptEditorEvent::ConfirmRequested);
1904                }
1905            }
1906            CodegenStatus::Error(_) => {
1907                cx.emit(PromptEditorEvent::StartRequested);
1908            }
1909        }
1910    }
1911
1912    fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
1913        if let Some(ix) = self.prompt_history_ix {
1914            if ix > 0 {
1915                self.prompt_history_ix = Some(ix - 1);
1916                let prompt = self.prompt_history[ix - 1].as_str();
1917                self.editor.update(cx, |editor, cx| {
1918                    editor.set_text(prompt, cx);
1919                    editor.move_to_beginning(&Default::default(), cx);
1920                });
1921            }
1922        } else if !self.prompt_history.is_empty() {
1923            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
1924            let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
1925            self.editor.update(cx, |editor, cx| {
1926                editor.set_text(prompt, cx);
1927                editor.move_to_beginning(&Default::default(), cx);
1928            });
1929        }
1930    }
1931
1932    fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
1933        if let Some(ix) = self.prompt_history_ix {
1934            if ix < self.prompt_history.len() - 1 {
1935                self.prompt_history_ix = Some(ix + 1);
1936                let prompt = self.prompt_history[ix + 1].as_str();
1937                self.editor.update(cx, |editor, cx| {
1938                    editor.set_text(prompt, cx);
1939                    editor.move_to_end(&Default::default(), cx)
1940                });
1941            } else {
1942                self.prompt_history_ix = None;
1943                let prompt = self.pending_prompt.as_str();
1944                self.editor.update(cx, |editor, cx| {
1945                    editor.set_text(prompt, cx);
1946                    editor.move_to_end(&Default::default(), cx)
1947                });
1948            }
1949        }
1950    }
1951
1952    fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext<Self>) {
1953        self.codegen
1954            .update(cx, |codegen, cx| codegen.cycle_prev(cx));
1955    }
1956
1957    fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext<Self>) {
1958        self.codegen
1959            .update(cx, |codegen, cx| codegen.cycle_next(cx));
1960    }
1961
1962    fn render_cycle_controls(&self, cx: &ViewContext<Self>) -> AnyElement {
1963        let codegen = self.codegen.read(cx);
1964        let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
1965
1966        let model_registry = LanguageModelRegistry::read_global(cx);
1967        let default_model = model_registry.active_model();
1968        let alternative_models = model_registry.inline_alternative_models();
1969
1970        let get_model_name = |index: usize| -> String {
1971            let name = |model: &Arc<dyn LanguageModel>| model.name().0.to_string();
1972
1973            match index {
1974                0 => default_model.as_ref().map_or_else(String::new, name),
1975                index if index <= alternative_models.len() => alternative_models
1976                    .get(index - 1)
1977                    .map_or_else(String::new, name),
1978                _ => String::new(),
1979            }
1980        };
1981
1982        let total_models = alternative_models.len() + 1;
1983
1984        if total_models <= 1 {
1985            return div().into_any_element();
1986        }
1987
1988        let current_index = codegen.active_alternative;
1989        let prev_index = (current_index + total_models - 1) % total_models;
1990        let next_index = (current_index + 1) % total_models;
1991
1992        let prev_model_name = get_model_name(prev_index);
1993        let next_model_name = get_model_name(next_index);
1994
1995        h_flex()
1996            .child(
1997                IconButton::new("previous", IconName::ChevronLeft)
1998                    .icon_color(Color::Muted)
1999                    .disabled(disabled || current_index == 0)
2000                    .shape(IconButtonShape::Square)
2001                    .tooltip({
2002                        let focus_handle = self.editor.focus_handle(cx);
2003                        move |cx| {
2004                            cx.new_view(|cx| {
2005                                let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
2006                                    KeyBinding::for_action_in(
2007                                        &CyclePreviousInlineAssist,
2008                                        &focus_handle,
2009                                        cx,
2010                                    ),
2011                                );
2012                                if !disabled && current_index != 0 {
2013                                    tooltip = tooltip.meta(prev_model_name.clone());
2014                                }
2015                                tooltip
2016                            })
2017                            .into()
2018                        }
2019                    })
2020                    .on_click(cx.listener(|this, _, cx| {
2021                        this.codegen
2022                            .update(cx, |codegen, cx| codegen.cycle_prev(cx))
2023                    })),
2024            )
2025            .child(
2026                Label::new(format!(
2027                    "{}/{}",
2028                    codegen.active_alternative + 1,
2029                    codegen.alternative_count(cx)
2030                ))
2031                .size(LabelSize::Small)
2032                .color(if disabled {
2033                    Color::Disabled
2034                } else {
2035                    Color::Muted
2036                }),
2037            )
2038            .child(
2039                IconButton::new("next", IconName::ChevronRight)
2040                    .icon_color(Color::Muted)
2041                    .disabled(disabled || current_index == total_models - 1)
2042                    .shape(IconButtonShape::Square)
2043                    .tooltip({
2044                        let focus_handle = self.editor.focus_handle(cx);
2045                        move |cx| {
2046                            cx.new_view(|cx| {
2047                                let mut tooltip = Tooltip::new("Next Alternative").key_binding(
2048                                    KeyBinding::for_action_in(
2049                                        &CycleNextInlineAssist,
2050                                        &focus_handle,
2051                                        cx,
2052                                    ),
2053                                );
2054                                if !disabled && current_index != total_models - 1 {
2055                                    tooltip = tooltip.meta(next_model_name.clone());
2056                                }
2057                                tooltip
2058                            })
2059                            .into()
2060                        }
2061                    })
2062                    .on_click(cx.listener(|this, _, cx| {
2063                        this.codegen
2064                            .update(cx, |codegen, cx| codegen.cycle_next(cx))
2065                    })),
2066            )
2067            .into_any_element()
2068    }
2069
2070    fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
2071        let model = LanguageModelRegistry::read_global(cx).active_model()?;
2072        let token_counts = self.token_counts?;
2073        let max_token_count = model.max_token_count();
2074
2075        let remaining_tokens = max_token_count as isize - token_counts.total as isize;
2076        let token_count_color = if remaining_tokens <= 0 {
2077            Color::Error
2078        } else if token_counts.total as f32 / max_token_count as f32 >= 0.8 {
2079            Color::Warning
2080        } else {
2081            Color::Muted
2082        };
2083
2084        let mut token_count = h_flex()
2085            .id("token_count")
2086            .gap_0p5()
2087            .child(
2088                Label::new(humanize_token_count(token_counts.total))
2089                    .size(LabelSize::Small)
2090                    .color(token_count_color),
2091            )
2092            .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2093            .child(
2094                Label::new(humanize_token_count(max_token_count))
2095                    .size(LabelSize::Small)
2096                    .color(Color::Muted),
2097            );
2098        if let Some(workspace) = self.workspace.clone() {
2099            token_count = token_count
2100                .tooltip(move |cx| {
2101                    Tooltip::with_meta(
2102                        format!(
2103                            "Tokens Used ({} from the Assistant Panel)",
2104                            humanize_token_count(token_counts.assistant_panel)
2105                        ),
2106                        None,
2107                        "Click to open the Assistant Panel",
2108                        cx,
2109                    )
2110                })
2111                .cursor_pointer()
2112                .on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
2113                .on_click(move |_, cx| {
2114                    cx.stop_propagation();
2115                    workspace
2116                        .update(cx, |workspace, cx| {
2117                            workspace.focus_panel::<AssistantPanel>(cx)
2118                        })
2119                        .ok();
2120                });
2121        } else {
2122            token_count = token_count
2123                .cursor_default()
2124                .tooltip(|cx| Tooltip::text("Tokens used", cx));
2125        }
2126
2127        Some(token_count)
2128    }
2129
2130    fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2131        let settings = ThemeSettings::get_global(cx);
2132        let text_style = TextStyle {
2133            color: if self.editor.read(cx).read_only(cx) {
2134                cx.theme().colors().text_disabled
2135            } else {
2136                cx.theme().colors().text
2137            },
2138            font_family: settings.buffer_font.family.clone(),
2139            font_fallbacks: settings.buffer_font.fallbacks.clone(),
2140            font_size: settings.buffer_font_size.into(),
2141            font_weight: settings.buffer_font.weight,
2142            line_height: relative(settings.buffer_line_height.value()),
2143            ..Default::default()
2144        };
2145        EditorElement::new(
2146            &self.editor,
2147            EditorStyle {
2148                background: cx.theme().colors().editor_background,
2149                local_player: cx.theme().players().local(),
2150                text: text_style,
2151                ..Default::default()
2152            },
2153        )
2154    }
2155
2156    fn render_rate_limit_notice(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2157        Popover::new().child(
2158            v_flex()
2159                .occlude()
2160                .p_2()
2161                .child(
2162                    Label::new("Out of Tokens")
2163                        .size(LabelSize::Small)
2164                        .weight(FontWeight::BOLD),
2165                )
2166                .child(Label::new(
2167                    "Try Zed Pro for higher limits, a wider range of models, and more.",
2168                ))
2169                .child(
2170                    h_flex()
2171                        .justify_between()
2172                        .child(CheckboxWithLabel::new(
2173                            "dont-show-again",
2174                            Label::new("Don't show again"),
2175                            if dismissed_rate_limit_notice() {
2176                                ui::ToggleState::Selected
2177                            } else {
2178                                ui::ToggleState::Unselected
2179                            },
2180                            |selection, cx| {
2181                                let is_dismissed = match selection {
2182                                    ui::ToggleState::Unselected => false,
2183                                    ui::ToggleState::Indeterminate => return,
2184                                    ui::ToggleState::Selected => true,
2185                                };
2186
2187                                set_rate_limit_notice_dismissed(is_dismissed, cx)
2188                            },
2189                        ))
2190                        .child(
2191                            h_flex()
2192                                .gap_2()
2193                                .child(
2194                                    Button::new("dismiss", "Dismiss")
2195                                        .style(ButtonStyle::Transparent)
2196                                        .on_click(cx.listener(Self::toggle_rate_limit_notice)),
2197                                )
2198                                .child(Button::new("more-info", "More Info").on_click(
2199                                    |_event, cx| {
2200                                        cx.dispatch_action(Box::new(
2201                                            zed_actions::OpenAccountSettings,
2202                                        ))
2203                                    },
2204                                )),
2205                        ),
2206                ),
2207        )
2208    }
2209}
2210
2211const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice";
2212
2213fn dismissed_rate_limit_notice() -> bool {
2214    db::kvp::KEY_VALUE_STORE
2215        .read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY)
2216        .log_err()
2217        .map_or(false, |s| s.is_some())
2218}
2219
2220fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut AppContext) {
2221    db::write_and_log(cx, move || async move {
2222        if is_dismissed {
2223            db::kvp::KEY_VALUE_STORE
2224                .write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into())
2225                .await
2226        } else {
2227            db::kvp::KEY_VALUE_STORE
2228                .delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into())
2229                .await
2230        }
2231    })
2232}
2233
2234struct InlineAssist {
2235    group_id: InlineAssistGroupId,
2236    range: Range<Anchor>,
2237    editor: WeakView<Editor>,
2238    decorations: Option<InlineAssistDecorations>,
2239    codegen: Model<Codegen>,
2240    _subscriptions: Vec<Subscription>,
2241    workspace: Option<WeakView<Workspace>>,
2242    include_context: bool,
2243}
2244
2245impl InlineAssist {
2246    #[allow(clippy::too_many_arguments)]
2247    fn new(
2248        assist_id: InlineAssistId,
2249        group_id: InlineAssistGroupId,
2250        include_context: bool,
2251        editor: &View<Editor>,
2252        prompt_editor: &View<PromptEditor>,
2253        prompt_block_id: CustomBlockId,
2254        end_block_id: CustomBlockId,
2255        range: Range<Anchor>,
2256        codegen: Model<Codegen>,
2257        workspace: Option<WeakView<Workspace>>,
2258        cx: &mut WindowContext,
2259    ) -> Self {
2260        let prompt_editor_focus_handle = prompt_editor.focus_handle(cx);
2261        InlineAssist {
2262            group_id,
2263            include_context,
2264            editor: editor.downgrade(),
2265            decorations: Some(InlineAssistDecorations {
2266                prompt_block_id,
2267                prompt_editor: prompt_editor.clone(),
2268                removed_line_block_ids: HashSet::default(),
2269                end_block_id,
2270            }),
2271            range,
2272            codegen: codegen.clone(),
2273            workspace: workspace.clone(),
2274            _subscriptions: vec![
2275                cx.on_focus_in(&prompt_editor_focus_handle, move |cx| {
2276                    InlineAssistant::update_global(cx, |this, cx| {
2277                        this.handle_prompt_editor_focus_in(assist_id, cx)
2278                    })
2279                }),
2280                cx.on_focus_out(&prompt_editor_focus_handle, move |_, cx| {
2281                    InlineAssistant::update_global(cx, |this, cx| {
2282                        this.handle_prompt_editor_focus_out(assist_id, cx)
2283                    })
2284                }),
2285                cx.subscribe(prompt_editor, |prompt_editor, event, cx| {
2286                    InlineAssistant::update_global(cx, |this, cx| {
2287                        this.handle_prompt_editor_event(prompt_editor, event, cx)
2288                    })
2289                }),
2290                cx.observe(&codegen, {
2291                    let editor = editor.downgrade();
2292                    move |_, cx| {
2293                        if let Some(editor) = editor.upgrade() {
2294                            InlineAssistant::update_global(cx, |this, cx| {
2295                                if let Some(editor_assists) =
2296                                    this.assists_by_editor.get(&editor.downgrade())
2297                                {
2298                                    editor_assists.highlight_updates.send(()).ok();
2299                                }
2300
2301                                this.update_editor_blocks(&editor, assist_id, cx);
2302                            })
2303                        }
2304                    }
2305                }),
2306                cx.subscribe(&codegen, move |codegen, event, cx| {
2307                    InlineAssistant::update_global(cx, |this, cx| match event {
2308                        CodegenEvent::Undone => this.finish_assist(assist_id, false, cx),
2309                        CodegenEvent::Finished => {
2310                            let assist = if let Some(assist) = this.assists.get(&assist_id) {
2311                                assist
2312                            } else {
2313                                return;
2314                            };
2315
2316                            if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
2317                                if assist.decorations.is_none() {
2318                                    if let Some(workspace) = assist
2319                                        .workspace
2320                                        .as_ref()
2321                                        .and_then(|workspace| workspace.upgrade())
2322                                    {
2323                                        let error = format!("Inline assistant error: {}", error);
2324                                        workspace.update(cx, |workspace, cx| {
2325                                            struct InlineAssistantError;
2326
2327                                            let id =
2328                                                NotificationId::composite::<InlineAssistantError>(
2329                                                    assist_id.0,
2330                                                );
2331
2332                                            workspace.show_toast(Toast::new(id, error), cx);
2333                                        })
2334                                    }
2335                                }
2336                            }
2337
2338                            if assist.decorations.is_none() {
2339                                this.finish_assist(assist_id, false, cx);
2340                            }
2341                        }
2342                    })
2343                }),
2344            ],
2345        }
2346    }
2347
2348    fn user_prompt(&self, cx: &AppContext) -> Option<String> {
2349        let decorations = self.decorations.as_ref()?;
2350        Some(decorations.prompt_editor.read(cx).prompt(cx))
2351    }
2352
2353    fn assistant_panel_context(&self, cx: &WindowContext) -> Option<LanguageModelRequest> {
2354        if self.include_context {
2355            let workspace = self.workspace.as_ref()?;
2356            let workspace = workspace.upgrade()?.read(cx);
2357            let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
2358            Some(
2359                assistant_panel
2360                    .read(cx)
2361                    .active_context(cx)?
2362                    .read(cx)
2363                    .to_completion_request(RequestType::Chat, cx),
2364            )
2365        } else {
2366            None
2367        }
2368    }
2369
2370    pub fn count_tokens(&self, cx: &WindowContext) -> BoxFuture<'static, Result<TokenCounts>> {
2371        let Some(user_prompt) = self.user_prompt(cx) else {
2372            return future::ready(Err(anyhow!("no user prompt"))).boxed();
2373        };
2374        let assistant_panel_context = self.assistant_panel_context(cx);
2375        self.codegen
2376            .read(cx)
2377            .count_tokens(user_prompt, assistant_panel_context, cx)
2378    }
2379}
2380
2381struct InlineAssistDecorations {
2382    prompt_block_id: CustomBlockId,
2383    prompt_editor: View<PromptEditor>,
2384    removed_line_block_ids: HashSet<CustomBlockId>,
2385    end_block_id: CustomBlockId,
2386}
2387
2388#[derive(Copy, Clone, Debug)]
2389pub enum CodegenEvent {
2390    Finished,
2391    Undone,
2392}
2393
2394pub struct Codegen {
2395    alternatives: Vec<Model<CodegenAlternative>>,
2396    active_alternative: usize,
2397    seen_alternatives: HashSet<usize>,
2398    subscriptions: Vec<Subscription>,
2399    buffer: Model<MultiBuffer>,
2400    range: Range<Anchor>,
2401    initial_transaction_id: Option<TransactionId>,
2402    telemetry: Arc<Telemetry>,
2403    builder: Arc<PromptBuilder>,
2404    is_insertion: bool,
2405}
2406
2407impl Codegen {
2408    pub fn new(
2409        buffer: Model<MultiBuffer>,
2410        range: Range<Anchor>,
2411        initial_transaction_id: Option<TransactionId>,
2412        telemetry: Arc<Telemetry>,
2413        builder: Arc<PromptBuilder>,
2414        cx: &mut ModelContext<Self>,
2415    ) -> Self {
2416        let codegen = cx.new_model(|cx| {
2417            CodegenAlternative::new(
2418                buffer.clone(),
2419                range.clone(),
2420                false,
2421                Some(telemetry.clone()),
2422                builder.clone(),
2423                cx,
2424            )
2425        });
2426        let mut this = Self {
2427            is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
2428            alternatives: vec![codegen],
2429            active_alternative: 0,
2430            seen_alternatives: HashSet::default(),
2431            subscriptions: Vec::new(),
2432            buffer,
2433            range,
2434            initial_transaction_id,
2435            telemetry,
2436            builder,
2437        };
2438        this.activate(0, cx);
2439        this
2440    }
2441
2442    fn subscribe_to_alternative(&mut self, cx: &mut ModelContext<Self>) {
2443        let codegen = self.active_alternative().clone();
2444        self.subscriptions.clear();
2445        self.subscriptions
2446            .push(cx.observe(&codegen, |_, _, cx| cx.notify()));
2447        self.subscriptions
2448            .push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
2449    }
2450
2451    fn active_alternative(&self) -> &Model<CodegenAlternative> {
2452        &self.alternatives[self.active_alternative]
2453    }
2454
2455    fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus {
2456        &self.active_alternative().read(cx).status
2457    }
2458
2459    fn alternative_count(&self, cx: &AppContext) -> usize {
2460        LanguageModelRegistry::read_global(cx)
2461            .inline_alternative_models()
2462            .len()
2463            + 1
2464    }
2465
2466    pub fn cycle_prev(&mut self, cx: &mut ModelContext<Self>) {
2467        let next_active_ix = if self.active_alternative == 0 {
2468            self.alternatives.len() - 1
2469        } else {
2470            self.active_alternative - 1
2471        };
2472        self.activate(next_active_ix, cx);
2473    }
2474
2475    pub fn cycle_next(&mut self, cx: &mut ModelContext<Self>) {
2476        let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
2477        self.activate(next_active_ix, cx);
2478    }
2479
2480    fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
2481        self.active_alternative()
2482            .update(cx, |codegen, cx| codegen.set_active(false, cx));
2483        self.seen_alternatives.insert(index);
2484        self.active_alternative = index;
2485        self.active_alternative()
2486            .update(cx, |codegen, cx| codegen.set_active(true, cx));
2487        self.subscribe_to_alternative(cx);
2488        cx.notify();
2489    }
2490
2491    pub fn start(
2492        &mut self,
2493        user_prompt: String,
2494        assistant_panel_context: Option<LanguageModelRequest>,
2495        cx: &mut ModelContext<Self>,
2496    ) -> Result<()> {
2497        let alternative_models = LanguageModelRegistry::read_global(cx)
2498            .inline_alternative_models()
2499            .to_vec();
2500
2501        self.active_alternative()
2502            .update(cx, |alternative, cx| alternative.undo(cx));
2503        self.activate(0, cx);
2504        self.alternatives.truncate(1);
2505
2506        for _ in 0..alternative_models.len() {
2507            self.alternatives.push(cx.new_model(|cx| {
2508                CodegenAlternative::new(
2509                    self.buffer.clone(),
2510                    self.range.clone(),
2511                    false,
2512                    Some(self.telemetry.clone()),
2513                    self.builder.clone(),
2514                    cx,
2515                )
2516            }));
2517        }
2518
2519        let primary_model = LanguageModelRegistry::read_global(cx)
2520            .active_model()
2521            .context("no active model")?;
2522
2523        for (model, alternative) in iter::once(primary_model)
2524            .chain(alternative_models)
2525            .zip(&self.alternatives)
2526        {
2527            alternative.update(cx, |alternative, cx| {
2528                alternative.start(
2529                    user_prompt.clone(),
2530                    assistant_panel_context.clone(),
2531                    model.clone(),
2532                    cx,
2533                )
2534            })?;
2535        }
2536
2537        Ok(())
2538    }
2539
2540    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
2541        for codegen in &self.alternatives {
2542            codegen.update(cx, |codegen, cx| codegen.stop(cx));
2543        }
2544    }
2545
2546    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
2547        self.active_alternative()
2548            .update(cx, |codegen, cx| codegen.undo(cx));
2549
2550        self.buffer.update(cx, |buffer, cx| {
2551            if let Some(transaction_id) = self.initial_transaction_id.take() {
2552                buffer.undo_transaction(transaction_id, cx);
2553                buffer.refresh_preview(cx);
2554            }
2555        });
2556    }
2557
2558    pub fn count_tokens(
2559        &self,
2560        user_prompt: String,
2561        assistant_panel_context: Option<LanguageModelRequest>,
2562        cx: &AppContext,
2563    ) -> BoxFuture<'static, Result<TokenCounts>> {
2564        self.active_alternative()
2565            .read(cx)
2566            .count_tokens(user_prompt, assistant_panel_context, cx)
2567    }
2568
2569    pub fn buffer(&self, cx: &AppContext) -> Model<MultiBuffer> {
2570        self.active_alternative().read(cx).buffer.clone()
2571    }
2572
2573    pub fn old_buffer(&self, cx: &AppContext) -> Model<Buffer> {
2574        self.active_alternative().read(cx).old_buffer.clone()
2575    }
2576
2577    pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
2578        self.active_alternative().read(cx).snapshot.clone()
2579    }
2580
2581    pub fn edit_position(&self, cx: &AppContext) -> Option<Anchor> {
2582        self.active_alternative().read(cx).edit_position
2583    }
2584
2585    fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff {
2586        &self.active_alternative().read(cx).diff
2587    }
2588
2589    pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range<Anchor>] {
2590        self.active_alternative().read(cx).last_equal_ranges()
2591    }
2592}
2593
2594impl EventEmitter<CodegenEvent> for Codegen {}
2595
2596pub struct CodegenAlternative {
2597    buffer: Model<MultiBuffer>,
2598    old_buffer: Model<Buffer>,
2599    snapshot: MultiBufferSnapshot,
2600    edit_position: Option<Anchor>,
2601    range: Range<Anchor>,
2602    last_equal_ranges: Vec<Range<Anchor>>,
2603    transformation_transaction_id: Option<TransactionId>,
2604    status: CodegenStatus,
2605    generation: Task<()>,
2606    diff: Diff,
2607    telemetry: Option<Arc<Telemetry>>,
2608    _subscription: gpui::Subscription,
2609    builder: Arc<PromptBuilder>,
2610    active: bool,
2611    edits: Vec<(Range<Anchor>, String)>,
2612    line_operations: Vec<LineOperation>,
2613    request: Option<LanguageModelRequest>,
2614    elapsed_time: Option<f64>,
2615    completion: Option<String>,
2616    message_id: Option<String>,
2617}
2618
2619enum CodegenStatus {
2620    Idle,
2621    Pending,
2622    Done,
2623    Error(anyhow::Error),
2624}
2625
2626#[derive(Default)]
2627struct Diff {
2628    deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
2629    inserted_row_ranges: Vec<Range<Anchor>>,
2630}
2631
2632impl Diff {
2633    fn is_empty(&self) -> bool {
2634        self.deleted_row_ranges.is_empty() && self.inserted_row_ranges.is_empty()
2635    }
2636}
2637
2638impl EventEmitter<CodegenEvent> for CodegenAlternative {}
2639
2640impl CodegenAlternative {
2641    pub fn new(
2642        multi_buffer: Model<MultiBuffer>,
2643        range: Range<Anchor>,
2644        active: bool,
2645        telemetry: Option<Arc<Telemetry>>,
2646        builder: Arc<PromptBuilder>,
2647        cx: &mut ModelContext<Self>,
2648    ) -> Self {
2649        let snapshot = multi_buffer.read(cx).snapshot(cx);
2650
2651        let (old_excerpt, _) = snapshot
2652            .range_to_buffer_ranges(range.clone())
2653            .pop()
2654            .unwrap();
2655        let old_buffer = cx.new_model(|cx| {
2656            let text = old_excerpt.buffer().as_rope().clone();
2657            let line_ending = old_excerpt.buffer().line_ending();
2658            let language = old_excerpt.buffer().language().cloned();
2659            let language_registry = multi_buffer
2660                .read(cx)
2661                .buffer(old_excerpt.buffer_id())
2662                .unwrap()
2663                .read(cx)
2664                .language_registry();
2665
2666            let mut buffer = Buffer::local_normalized(text, line_ending, cx);
2667            buffer.set_language(language, cx);
2668            if let Some(language_registry) = language_registry {
2669                buffer.set_language_registry(language_registry)
2670            }
2671            buffer
2672        });
2673
2674        Self {
2675            buffer: multi_buffer.clone(),
2676            old_buffer,
2677            edit_position: None,
2678            message_id: None,
2679            snapshot,
2680            last_equal_ranges: Default::default(),
2681            transformation_transaction_id: None,
2682            status: CodegenStatus::Idle,
2683            generation: Task::ready(()),
2684            diff: Diff::default(),
2685            telemetry,
2686            _subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
2687            builder,
2688            active,
2689            edits: Vec::new(),
2690            line_operations: Vec::new(),
2691            range,
2692            request: None,
2693            elapsed_time: None,
2694            completion: None,
2695        }
2696    }
2697
2698    fn set_active(&mut self, active: bool, cx: &mut ModelContext<Self>) {
2699        if active != self.active {
2700            self.active = active;
2701
2702            if self.active {
2703                let edits = self.edits.clone();
2704                self.apply_edits(edits, cx);
2705                if matches!(self.status, CodegenStatus::Pending) {
2706                    let line_operations = self.line_operations.clone();
2707                    self.reapply_line_based_diff(line_operations, cx);
2708                } else {
2709                    self.reapply_batch_diff(cx).detach();
2710                }
2711            } else if let Some(transaction_id) = self.transformation_transaction_id.take() {
2712                self.buffer.update(cx, |buffer, cx| {
2713                    buffer.undo_transaction(transaction_id, cx);
2714                    buffer.forget_transaction(transaction_id, cx);
2715                });
2716            }
2717        }
2718    }
2719
2720    fn handle_buffer_event(
2721        &mut self,
2722        _buffer: Model<MultiBuffer>,
2723        event: &multi_buffer::Event,
2724        cx: &mut ModelContext<Self>,
2725    ) {
2726        if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
2727            if self.transformation_transaction_id == Some(*transaction_id) {
2728                self.transformation_transaction_id = None;
2729                self.generation = Task::ready(());
2730                cx.emit(CodegenEvent::Undone);
2731            }
2732        }
2733    }
2734
2735    pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
2736        &self.last_equal_ranges
2737    }
2738
2739    pub fn count_tokens(
2740        &self,
2741        user_prompt: String,
2742        assistant_panel_context: Option<LanguageModelRequest>,
2743        cx: &AppContext,
2744    ) -> BoxFuture<'static, Result<TokenCounts>> {
2745        if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
2746            let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
2747            match request {
2748                Ok(request) => {
2749                    let total_count = model.count_tokens(request.clone(), cx);
2750                    let assistant_panel_count = assistant_panel_context
2751                        .map(|context| model.count_tokens(context, cx))
2752                        .unwrap_or_else(|| future::ready(Ok(0)).boxed());
2753
2754                    async move {
2755                        Ok(TokenCounts {
2756                            total: total_count.await?,
2757                            assistant_panel: assistant_panel_count.await?,
2758                        })
2759                    }
2760                    .boxed()
2761                }
2762                Err(error) => futures::future::ready(Err(error)).boxed(),
2763            }
2764        } else {
2765            future::ready(Err(anyhow!("no active model"))).boxed()
2766        }
2767    }
2768
2769    pub fn start(
2770        &mut self,
2771        user_prompt: String,
2772        assistant_panel_context: Option<LanguageModelRequest>,
2773        model: Arc<dyn LanguageModel>,
2774        cx: &mut ModelContext<Self>,
2775    ) -> Result<()> {
2776        if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
2777            self.buffer.update(cx, |buffer, cx| {
2778                buffer.undo_transaction(transformation_transaction_id, cx);
2779            });
2780        }
2781
2782        self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
2783
2784        let api_key = model.api_key(cx);
2785        let telemetry_id = model.telemetry_id();
2786        let provider_id = model.provider_id();
2787        let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
2788            if user_prompt.trim().to_lowercase() == "delete" {
2789                async { Ok(LanguageModelTextStream::default()) }.boxed_local()
2790            } else {
2791                let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
2792                self.request = Some(request.clone());
2793
2794                cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
2795                    .boxed_local()
2796            };
2797        self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
2798        Ok(())
2799    }
2800
2801    fn build_request(
2802        &self,
2803        user_prompt: String,
2804        assistant_panel_context: Option<LanguageModelRequest>,
2805        cx: &AppContext,
2806    ) -> Result<LanguageModelRequest> {
2807        let buffer = self.buffer.read(cx).snapshot(cx);
2808        let language = buffer.language_at(self.range.start);
2809        let language_name = if let Some(language) = language.as_ref() {
2810            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
2811                None
2812            } else {
2813                Some(language.name())
2814            }
2815        } else {
2816            None
2817        };
2818
2819        let language_name = language_name.as_ref();
2820        let start = buffer.point_to_buffer_offset(self.range.start);
2821        let end = buffer.point_to_buffer_offset(self.range.end);
2822        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
2823            let (start_buffer, start_buffer_offset) = start;
2824            let (end_buffer, end_buffer_offset) = end;
2825            if start_buffer.remote_id() == end_buffer.remote_id() {
2826                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
2827            } else {
2828                return Err(anyhow::anyhow!("invalid transformation range"));
2829            }
2830        } else {
2831            return Err(anyhow::anyhow!("invalid transformation range"));
2832        };
2833
2834        let prompt = self
2835            .builder
2836            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
2837            .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
2838
2839        let mut messages = Vec::new();
2840        if let Some(context_request) = assistant_panel_context {
2841            messages = context_request.messages;
2842        }
2843
2844        messages.push(LanguageModelRequestMessage {
2845            role: Role::User,
2846            content: vec![prompt.into()],
2847            cache: false,
2848        });
2849
2850        Ok(LanguageModelRequest {
2851            messages,
2852            tools: Vec::new(),
2853            stop: Vec::new(),
2854            temperature: None,
2855        })
2856    }
2857
2858    pub fn handle_stream(
2859        &mut self,
2860        model_telemetry_id: String,
2861        model_provider_id: String,
2862        model_api_key: Option<String>,
2863        stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
2864        cx: &mut ModelContext<Self>,
2865    ) {
2866        let start_time = Instant::now();
2867        let snapshot = self.snapshot.clone();
2868        let selected_text = snapshot
2869            .text_for_range(self.range.start..self.range.end)
2870            .collect::<Rope>();
2871
2872        let selection_start = self.range.start.to_point(&snapshot);
2873
2874        // Start with the indentation of the first line in the selection
2875        let mut suggested_line_indent = snapshot
2876            .suggested_indents(selection_start.row..=selection_start.row, cx)
2877            .into_values()
2878            .next()
2879            .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
2880
2881        // If the first line in the selection does not have indentation, check the following lines
2882        if suggested_line_indent.len == 0 && suggested_line_indent.kind == IndentKind::Space {
2883            for row in selection_start.row..=self.range.end.to_point(&snapshot).row {
2884                let line_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
2885                // Prefer tabs if a line in the selection uses tabs as indentation
2886                if line_indent.kind == IndentKind::Tab {
2887                    suggested_line_indent.kind = IndentKind::Tab;
2888                    break;
2889                }
2890            }
2891        }
2892
2893        let http_client = cx.http_client().clone();
2894        let telemetry = self.telemetry.clone();
2895        let language_name = {
2896            let multibuffer = self.buffer.read(cx);
2897            let snapshot = multibuffer.snapshot(cx);
2898            let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
2899            ranges
2900                .first()
2901                .and_then(|(excerpt, _)| excerpt.buffer().language())
2902                .map(|language| language.name())
2903        };
2904
2905        self.diff = Diff::default();
2906        self.status = CodegenStatus::Pending;
2907        let mut edit_start = self.range.start.to_offset(&snapshot);
2908        let completion = Arc::new(Mutex::new(String::new()));
2909        let completion_clone = completion.clone();
2910
2911        self.generation = cx.spawn(|codegen, mut cx| {
2912            async move {
2913                let stream = stream.await;
2914                let message_id = stream
2915                    .as_ref()
2916                    .ok()
2917                    .and_then(|stream| stream.message_id.clone());
2918                let generate = async {
2919                    let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
2920                    let executor = cx.background_executor().clone();
2921                    let message_id = message_id.clone();
2922                    let line_based_stream_diff: Task<anyhow::Result<()>> =
2923                        cx.background_executor().spawn(async move {
2924                            let mut response_latency = None;
2925                            let request_start = Instant::now();
2926                            let diff = async {
2927                                let chunks = StripInvalidSpans::new(stream?.stream);
2928                                futures::pin_mut!(chunks);
2929                                let mut diff = StreamingDiff::new(selected_text.to_string());
2930                                let mut line_diff = LineDiff::default();
2931
2932                                let mut new_text = String::new();
2933                                let mut base_indent = None;
2934                                let mut line_indent = None;
2935                                let mut first_line = true;
2936
2937                                while let Some(chunk) = chunks.next().await {
2938                                    if response_latency.is_none() {
2939                                        response_latency = Some(request_start.elapsed());
2940                                    }
2941                                    let chunk = chunk?;
2942                                    completion_clone.lock().push_str(&chunk);
2943
2944                                    let mut lines = chunk.split('\n').peekable();
2945                                    while let Some(line) = lines.next() {
2946                                        new_text.push_str(line);
2947                                        if line_indent.is_none() {
2948                                            if let Some(non_whitespace_ch_ix) =
2949                                                new_text.find(|ch: char| !ch.is_whitespace())
2950                                            {
2951                                                line_indent = Some(non_whitespace_ch_ix);
2952                                                base_indent = base_indent.or(line_indent);
2953
2954                                                let line_indent = line_indent.unwrap();
2955                                                let base_indent = base_indent.unwrap();
2956                                                let indent_delta =
2957                                                    line_indent as i32 - base_indent as i32;
2958                                                let mut corrected_indent_len = cmp::max(
2959                                                    0,
2960                                                    suggested_line_indent.len as i32 + indent_delta,
2961                                                )
2962                                                    as usize;
2963                                                if first_line {
2964                                                    corrected_indent_len = corrected_indent_len
2965                                                        .saturating_sub(
2966                                                            selection_start.column as usize,
2967                                                        );
2968                                                }
2969
2970                                                let indent_char = suggested_line_indent.char();
2971                                                let mut indent_buffer = [0; 4];
2972                                                let indent_str =
2973                                                    indent_char.encode_utf8(&mut indent_buffer);
2974                                                new_text.replace_range(
2975                                                    ..line_indent,
2976                                                    &indent_str.repeat(corrected_indent_len),
2977                                                );
2978                                            }
2979                                        }
2980
2981                                        if line_indent.is_some() {
2982                                            let char_ops = diff.push_new(&new_text);
2983                                            line_diff
2984                                                .push_char_operations(&char_ops, &selected_text);
2985                                            diff_tx
2986                                                .send((char_ops, line_diff.line_operations()))
2987                                                .await?;
2988                                            new_text.clear();
2989                                        }
2990
2991                                        if lines.peek().is_some() {
2992                                            let char_ops = diff.push_new("\n");
2993                                            line_diff
2994                                                .push_char_operations(&char_ops, &selected_text);
2995                                            diff_tx
2996                                                .send((char_ops, line_diff.line_operations()))
2997                                                .await?;
2998                                            if line_indent.is_none() {
2999                                                // Don't write out the leading indentation in empty lines on the next line
3000                                                // This is the case where the above if statement didn't clear the buffer
3001                                                new_text.clear();
3002                                            }
3003                                            line_indent = None;
3004                                            first_line = false;
3005                                        }
3006                                    }
3007                                }
3008
3009                                let mut char_ops = diff.push_new(&new_text);
3010                                char_ops.extend(diff.finish());
3011                                line_diff.push_char_operations(&char_ops, &selected_text);
3012                                line_diff.finish(&selected_text);
3013                                diff_tx
3014                                    .send((char_ops, line_diff.line_operations()))
3015                                    .await?;
3016
3017                                anyhow::Ok(())
3018                            };
3019
3020                            let result = diff.await;
3021
3022                            let error_message =
3023                                result.as_ref().err().map(|error| error.to_string());
3024                            report_assistant_event(
3025                                AssistantEvent {
3026                                    conversation_id: None,
3027                                    message_id,
3028                                    kind: AssistantKind::Inline,
3029                                    phase: AssistantPhase::Response,
3030                                    model: model_telemetry_id,
3031                                    model_provider: model_provider_id.to_string(),
3032                                    response_latency,
3033                                    error_message,
3034                                    language_name: language_name.map(|name| name.to_proto()),
3035                                },
3036                                telemetry,
3037                                http_client,
3038                                model_api_key,
3039                                &executor,
3040                            );
3041
3042                            result?;
3043                            Ok(())
3044                        });
3045
3046                    while let Some((char_ops, line_ops)) = diff_rx.next().await {
3047                        codegen.update(&mut cx, |codegen, cx| {
3048                            codegen.last_equal_ranges.clear();
3049
3050                            let edits = char_ops
3051                                .into_iter()
3052                                .filter_map(|operation| match operation {
3053                                    CharOperation::Insert { text } => {
3054                                        let edit_start = snapshot.anchor_after(edit_start);
3055                                        Some((edit_start..edit_start, text))
3056                                    }
3057                                    CharOperation::Delete { bytes } => {
3058                                        let edit_end = edit_start + bytes;
3059                                        let edit_range = snapshot.anchor_after(edit_start)
3060                                            ..snapshot.anchor_before(edit_end);
3061                                        edit_start = edit_end;
3062                                        Some((edit_range, String::new()))
3063                                    }
3064                                    CharOperation::Keep { bytes } => {
3065                                        let edit_end = edit_start + bytes;
3066                                        let edit_range = snapshot.anchor_after(edit_start)
3067                                            ..snapshot.anchor_before(edit_end);
3068                                        edit_start = edit_end;
3069                                        codegen.last_equal_ranges.push(edit_range);
3070                                        None
3071                                    }
3072                                })
3073                                .collect::<Vec<_>>();
3074
3075                            if codegen.active {
3076                                codegen.apply_edits(edits.iter().cloned(), cx);
3077                                codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
3078                            }
3079                            codegen.edits.extend(edits);
3080                            codegen.line_operations = line_ops;
3081                            codegen.edit_position = Some(snapshot.anchor_after(edit_start));
3082
3083                            cx.notify();
3084                        })?;
3085                    }
3086
3087                    // Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
3088                    // That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
3089                    // It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
3090                    let batch_diff_task =
3091                        codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
3092                    let (line_based_stream_diff, ()) =
3093                        join!(line_based_stream_diff, batch_diff_task);
3094                    line_based_stream_diff?;
3095
3096                    anyhow::Ok(())
3097                };
3098
3099                let result = generate.await;
3100                let elapsed_time = start_time.elapsed().as_secs_f64();
3101
3102                codegen
3103                    .update(&mut cx, |this, cx| {
3104                        this.message_id = message_id;
3105                        this.last_equal_ranges.clear();
3106                        if let Err(error) = result {
3107                            this.status = CodegenStatus::Error(error);
3108                        } else {
3109                            this.status = CodegenStatus::Done;
3110                        }
3111                        this.elapsed_time = Some(elapsed_time);
3112                        this.completion = Some(completion.lock().clone());
3113                        cx.emit(CodegenEvent::Finished);
3114                        cx.notify();
3115                    })
3116                    .ok();
3117            }
3118        });
3119        cx.notify();
3120    }
3121
3122    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
3123        self.last_equal_ranges.clear();
3124        if self.diff.is_empty() {
3125            self.status = CodegenStatus::Idle;
3126        } else {
3127            self.status = CodegenStatus::Done;
3128        }
3129        self.generation = Task::ready(());
3130        cx.emit(CodegenEvent::Finished);
3131        cx.notify();
3132    }
3133
3134    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
3135        self.buffer.update(cx, |buffer, cx| {
3136            if let Some(transaction_id) = self.transformation_transaction_id.take() {
3137                buffer.undo_transaction(transaction_id, cx);
3138                buffer.refresh_preview(cx);
3139            }
3140        });
3141    }
3142
3143    fn apply_edits(
3144        &mut self,
3145        edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
3146        cx: &mut ModelContext<CodegenAlternative>,
3147    ) {
3148        let transaction = self.buffer.update(cx, |buffer, cx| {
3149            // Avoid grouping assistant edits with user edits.
3150            buffer.finalize_last_transaction(cx);
3151            buffer.start_transaction(cx);
3152            buffer.edit(edits, None, cx);
3153            buffer.end_transaction(cx)
3154        });
3155
3156        if let Some(transaction) = transaction {
3157            if let Some(first_transaction) = self.transformation_transaction_id {
3158                // Group all assistant edits into the first transaction.
3159                self.buffer.update(cx, |buffer, cx| {
3160                    buffer.merge_transactions(transaction, first_transaction, cx)
3161                });
3162            } else {
3163                self.transformation_transaction_id = Some(transaction);
3164                self.buffer
3165                    .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
3166            }
3167        }
3168    }
3169
3170    fn reapply_line_based_diff(
3171        &mut self,
3172        line_operations: impl IntoIterator<Item = LineOperation>,
3173        cx: &mut ModelContext<Self>,
3174    ) {
3175        let old_snapshot = self.snapshot.clone();
3176        let old_range = self.range.to_point(&old_snapshot);
3177        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3178        let new_range = self.range.to_point(&new_snapshot);
3179
3180        let mut old_row = old_range.start.row;
3181        let mut new_row = new_range.start.row;
3182
3183        self.diff.deleted_row_ranges.clear();
3184        self.diff.inserted_row_ranges.clear();
3185        for operation in line_operations {
3186            match operation {
3187                LineOperation::Keep { lines } => {
3188                    old_row += lines;
3189                    new_row += lines;
3190                }
3191                LineOperation::Delete { lines } => {
3192                    let old_end_row = old_row + lines - 1;
3193                    let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3194
3195                    if let Some((_, last_deleted_row_range)) =
3196                        self.diff.deleted_row_ranges.last_mut()
3197                    {
3198                        if *last_deleted_row_range.end() + 1 == old_row {
3199                            *last_deleted_row_range = *last_deleted_row_range.start()..=old_end_row;
3200                        } else {
3201                            self.diff
3202                                .deleted_row_ranges
3203                                .push((new_row, old_row..=old_end_row));
3204                        }
3205                    } else {
3206                        self.diff
3207                            .deleted_row_ranges
3208                            .push((new_row, old_row..=old_end_row));
3209                    }
3210
3211                    old_row += lines;
3212                }
3213                LineOperation::Insert { lines } => {
3214                    let new_end_row = new_row + lines - 1;
3215                    let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3216                    let end = new_snapshot.anchor_before(Point::new(
3217                        new_end_row,
3218                        new_snapshot.line_len(MultiBufferRow(new_end_row)),
3219                    ));
3220                    self.diff.inserted_row_ranges.push(start..end);
3221                    new_row += lines;
3222                }
3223            }
3224
3225            cx.notify();
3226        }
3227    }
3228
3229    fn reapply_batch_diff(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
3230        let old_snapshot = self.snapshot.clone();
3231        let old_range = self.range.to_point(&old_snapshot);
3232        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3233        let new_range = self.range.to_point(&new_snapshot);
3234
3235        cx.spawn(|codegen, mut cx| async move {
3236            let (deleted_row_ranges, inserted_row_ranges) = cx
3237                .background_executor()
3238                .spawn(async move {
3239                    let old_text = old_snapshot
3240                        .text_for_range(
3241                            Point::new(old_range.start.row, 0)
3242                                ..Point::new(
3243                                    old_range.end.row,
3244                                    old_snapshot.line_len(MultiBufferRow(old_range.end.row)),
3245                                ),
3246                        )
3247                        .collect::<String>();
3248                    let new_text = new_snapshot
3249                        .text_for_range(
3250                            Point::new(new_range.start.row, 0)
3251                                ..Point::new(
3252                                    new_range.end.row,
3253                                    new_snapshot.line_len(MultiBufferRow(new_range.end.row)),
3254                                ),
3255                        )
3256                        .collect::<String>();
3257
3258                    let mut old_row = old_range.start.row;
3259                    let mut new_row = new_range.start.row;
3260                    let batch_diff =
3261                        similar::TextDiff::from_lines(old_text.as_str(), new_text.as_str());
3262
3263                    let mut deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)> = Vec::new();
3264                    let mut inserted_row_ranges = Vec::new();
3265                    for change in batch_diff.iter_all_changes() {
3266                        let line_count = change.value().lines().count() as u32;
3267                        match change.tag() {
3268                            similar::ChangeTag::Equal => {
3269                                old_row += line_count;
3270                                new_row += line_count;
3271                            }
3272                            similar::ChangeTag::Delete => {
3273                                let old_end_row = old_row + line_count - 1;
3274                                let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3275
3276                                if let Some((_, last_deleted_row_range)) =
3277                                    deleted_row_ranges.last_mut()
3278                                {
3279                                    if *last_deleted_row_range.end() + 1 == old_row {
3280                                        *last_deleted_row_range =
3281                                            *last_deleted_row_range.start()..=old_end_row;
3282                                    } else {
3283                                        deleted_row_ranges.push((new_row, old_row..=old_end_row));
3284                                    }
3285                                } else {
3286                                    deleted_row_ranges.push((new_row, old_row..=old_end_row));
3287                                }
3288
3289                                old_row += line_count;
3290                            }
3291                            similar::ChangeTag::Insert => {
3292                                let new_end_row = new_row + line_count - 1;
3293                                let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3294                                let end = new_snapshot.anchor_before(Point::new(
3295                                    new_end_row,
3296                                    new_snapshot.line_len(MultiBufferRow(new_end_row)),
3297                                ));
3298                                inserted_row_ranges.push(start..end);
3299                                new_row += line_count;
3300                            }
3301                        }
3302                    }
3303
3304                    (deleted_row_ranges, inserted_row_ranges)
3305                })
3306                .await;
3307
3308            codegen
3309                .update(&mut cx, |codegen, cx| {
3310                    codegen.diff.deleted_row_ranges = deleted_row_ranges;
3311                    codegen.diff.inserted_row_ranges = inserted_row_ranges;
3312                    cx.notify();
3313                })
3314                .ok();
3315        })
3316    }
3317}
3318
3319struct StripInvalidSpans<T> {
3320    stream: T,
3321    stream_done: bool,
3322    buffer: String,
3323    first_line: bool,
3324    line_end: bool,
3325    starts_with_code_block: bool,
3326}
3327
3328impl<T> StripInvalidSpans<T>
3329where
3330    T: Stream<Item = Result<String>>,
3331{
3332    fn new(stream: T) -> Self {
3333        Self {
3334            stream,
3335            stream_done: false,
3336            buffer: String::new(),
3337            first_line: true,
3338            line_end: false,
3339            starts_with_code_block: false,
3340        }
3341    }
3342}
3343
3344impl<T> Stream for StripInvalidSpans<T>
3345where
3346    T: Stream<Item = Result<String>>,
3347{
3348    type Item = Result<String>;
3349
3350    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
3351        const CODE_BLOCK_DELIMITER: &str = "```";
3352        const CURSOR_SPAN: &str = "<|CURSOR|>";
3353
3354        let this = unsafe { self.get_unchecked_mut() };
3355        loop {
3356            if !this.stream_done {
3357                let mut stream = unsafe { Pin::new_unchecked(&mut this.stream) };
3358                match stream.as_mut().poll_next(cx) {
3359                    Poll::Ready(Some(Ok(chunk))) => {
3360                        this.buffer.push_str(&chunk);
3361                    }
3362                    Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(error))),
3363                    Poll::Ready(None) => {
3364                        this.stream_done = true;
3365                    }
3366                    Poll::Pending => return Poll::Pending,
3367                }
3368            }
3369
3370            let mut chunk = String::new();
3371            let mut consumed = 0;
3372            if !this.buffer.is_empty() {
3373                let mut lines = this.buffer.split('\n').enumerate().peekable();
3374                while let Some((line_ix, line)) = lines.next() {
3375                    if line_ix > 0 {
3376                        this.first_line = false;
3377                    }
3378
3379                    if this.first_line {
3380                        let trimmed_line = line.trim();
3381                        if lines.peek().is_some() {
3382                            if trimmed_line.starts_with(CODE_BLOCK_DELIMITER) {
3383                                consumed += line.len() + 1;
3384                                this.starts_with_code_block = true;
3385                                continue;
3386                            }
3387                        } else if trimmed_line.is_empty()
3388                            || prefixes(CODE_BLOCK_DELIMITER)
3389                                .any(|prefix| trimmed_line.starts_with(prefix))
3390                        {
3391                            break;
3392                        }
3393                    }
3394
3395                    let line_without_cursor = line.replace(CURSOR_SPAN, "");
3396                    if lines.peek().is_some() {
3397                        if this.line_end {
3398                            chunk.push('\n');
3399                        }
3400
3401                        chunk.push_str(&line_without_cursor);
3402                        this.line_end = true;
3403                        consumed += line.len() + 1;
3404                    } else if this.stream_done {
3405                        if !this.starts_with_code_block
3406                            || !line_without_cursor.trim().ends_with(CODE_BLOCK_DELIMITER)
3407                        {
3408                            if this.line_end {
3409                                chunk.push('\n');
3410                            }
3411
3412                            chunk.push_str(&line);
3413                        }
3414
3415                        consumed += line.len();
3416                    } else {
3417                        let trimmed_line = line.trim();
3418                        if trimmed_line.is_empty()
3419                            || prefixes(CURSOR_SPAN).any(|prefix| trimmed_line.ends_with(prefix))
3420                            || prefixes(CODE_BLOCK_DELIMITER)
3421                                .any(|prefix| trimmed_line.ends_with(prefix))
3422                        {
3423                            break;
3424                        } else {
3425                            if this.line_end {
3426                                chunk.push('\n');
3427                                this.line_end = false;
3428                            }
3429
3430                            chunk.push_str(&line_without_cursor);
3431                            consumed += line.len();
3432                        }
3433                    }
3434                }
3435            }
3436
3437            this.buffer = this.buffer.split_off(consumed);
3438            if !chunk.is_empty() {
3439                return Poll::Ready(Some(Ok(chunk)));
3440            } else if this.stream_done {
3441                return Poll::Ready(None);
3442            }
3443        }
3444    }
3445}
3446
3447struct AssistantCodeActionProvider {
3448    editor: WeakView<Editor>,
3449    workspace: WeakView<Workspace>,
3450}
3451
3452const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant";
3453
3454impl CodeActionProvider for AssistantCodeActionProvider {
3455    fn id(&self) -> Arc<str> {
3456        ASSISTANT_CODE_ACTION_PROVIDER_ID.into()
3457    }
3458
3459    fn code_actions(
3460        &self,
3461        buffer: &Model<Buffer>,
3462        range: Range<text::Anchor>,
3463        cx: &mut WindowContext,
3464    ) -> Task<Result<Vec<CodeAction>>> {
3465        if !AssistantSettings::get_global(cx).enabled {
3466            return Task::ready(Ok(Vec::new()));
3467        }
3468
3469        let snapshot = buffer.read(cx).snapshot();
3470        let mut range = range.to_point(&snapshot);
3471
3472        // Expand the range to line boundaries.
3473        range.start.column = 0;
3474        range.end.column = snapshot.line_len(range.end.row);
3475
3476        let mut has_diagnostics = false;
3477        for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) {
3478            range.start = cmp::min(range.start, diagnostic.range.start);
3479            range.end = cmp::max(range.end, diagnostic.range.end);
3480            has_diagnostics = true;
3481        }
3482        if has_diagnostics {
3483            if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) {
3484                if let Some(symbol) = symbols_containing_start.last() {
3485                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3486                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3487                }
3488            }
3489
3490            if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) {
3491                if let Some(symbol) = symbols_containing_end.last() {
3492                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3493                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3494                }
3495            }
3496
3497            Task::ready(Ok(vec![CodeAction {
3498                server_id: language::LanguageServerId(0),
3499                range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
3500                lsp_action: lsp::CodeAction {
3501                    title: "Fix with Assistant".into(),
3502                    ..Default::default()
3503                },
3504            }]))
3505        } else {
3506            Task::ready(Ok(Vec::new()))
3507        }
3508    }
3509
3510    fn apply_code_action(
3511        &self,
3512        buffer: Model<Buffer>,
3513        action: CodeAction,
3514        excerpt_id: ExcerptId,
3515        _push_to_history: bool,
3516        cx: &mut WindowContext,
3517    ) -> Task<Result<ProjectTransaction>> {
3518        let editor = self.editor.clone();
3519        let workspace = self.workspace.clone();
3520        cx.spawn(|mut cx| async move {
3521            let editor = editor.upgrade().context("editor was released")?;
3522            let range = editor
3523                .update(&mut cx, |editor, cx| {
3524                    editor.buffer().update(cx, |multibuffer, cx| {
3525                        let buffer = buffer.read(cx);
3526                        let multibuffer_snapshot = multibuffer.read(cx);
3527
3528                        let old_context_range =
3529                            multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
3530                        let mut new_context_range = old_context_range.clone();
3531                        if action
3532                            .range
3533                            .start
3534                            .cmp(&old_context_range.start, buffer)
3535                            .is_lt()
3536                        {
3537                            new_context_range.start = action.range.start;
3538                        }
3539                        if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
3540                            new_context_range.end = action.range.end;
3541                        }
3542                        drop(multibuffer_snapshot);
3543
3544                        if new_context_range != old_context_range {
3545                            multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
3546                        }
3547
3548                        let multibuffer_snapshot = multibuffer.read(cx);
3549                        Some(
3550                            multibuffer_snapshot
3551                                .anchor_in_excerpt(excerpt_id, action.range.start)?
3552                                ..multibuffer_snapshot
3553                                    .anchor_in_excerpt(excerpt_id, action.range.end)?,
3554                        )
3555                    })
3556                })?
3557                .context("invalid range")?;
3558            let assistant_panel = workspace.update(&mut cx, |workspace, cx| {
3559                workspace
3560                    .panel::<AssistantPanel>(cx)
3561                    .context("assistant panel was released")
3562            })??;
3563
3564            cx.update_global(|assistant: &mut InlineAssistant, cx| {
3565                let assist_id = assistant.suggest_assist(
3566                    &editor,
3567                    range,
3568                    "Fix Diagnostics".into(),
3569                    None,
3570                    true,
3571                    Some(workspace),
3572                    Some(&assistant_panel),
3573                    cx,
3574                );
3575                assistant.start_assist(assist_id, cx);
3576            })?;
3577
3578            Ok(ProjectTransaction::default())
3579        })
3580    }
3581}
3582
3583fn prefixes(text: &str) -> impl Iterator<Item = &str> {
3584    (0..text.len() - 1).map(|ix| &text[..ix + 1])
3585}
3586
3587fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
3588    ranges.sort_unstable_by(|a, b| {
3589        a.start
3590            .cmp(&b.start, buffer)
3591            .then_with(|| b.end.cmp(&a.end, buffer))
3592    });
3593
3594    let mut ix = 0;
3595    while ix + 1 < ranges.len() {
3596        let b = ranges[ix + 1].clone();
3597        let a = &mut ranges[ix];
3598        if a.end.cmp(&b.start, buffer).is_gt() {
3599            if a.end.cmp(&b.end, buffer).is_lt() {
3600                a.end = b.end;
3601            }
3602            ranges.remove(ix + 1);
3603        } else {
3604            ix += 1;
3605        }
3606    }
3607}
3608
3609#[cfg(test)]
3610mod tests {
3611    use super::*;
3612    use futures::stream::{self};
3613    use gpui::{Context, TestAppContext};
3614    use indoc::indoc;
3615    use language::{
3616        language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
3617        Point,
3618    };
3619    use language_model::LanguageModelRegistry;
3620    use rand::prelude::*;
3621    use serde::Serialize;
3622    use settings::SettingsStore;
3623    use std::{future, sync::Arc};
3624
3625    #[derive(Serialize)]
3626    pub struct DummyCompletionRequest {
3627        pub name: String,
3628    }
3629
3630    #[gpui::test(iterations = 10)]
3631    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
3632        cx.set_global(cx.update(SettingsStore::test));
3633        cx.update(language_model::LanguageModelRegistry::test);
3634        cx.update(language_settings::init);
3635
3636        let text = indoc! {"
3637            fn main() {
3638                let x = 0;
3639                for _ in 0..10 {
3640                    x += 1;
3641                }
3642            }
3643        "};
3644        let buffer =
3645            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3646        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3647        let range = buffer.read_with(cx, |buffer, cx| {
3648            let snapshot = buffer.snapshot(cx);
3649            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
3650        });
3651        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3652        let codegen = cx.new_model(|cx| {
3653            CodegenAlternative::new(
3654                buffer.clone(),
3655                range.clone(),
3656                true,
3657                None,
3658                prompt_builder,
3659                cx,
3660            )
3661        });
3662
3663        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3664
3665        let mut new_text = concat!(
3666            "       let mut x = 0;\n",
3667            "       while x < 10 {\n",
3668            "           x += 1;\n",
3669            "       }",
3670        );
3671        while !new_text.is_empty() {
3672            let max_len = cmp::min(new_text.len(), 10);
3673            let len = rng.gen_range(1..=max_len);
3674            let (chunk, suffix) = new_text.split_at(len);
3675            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3676            new_text = suffix;
3677            cx.background_executor.run_until_parked();
3678        }
3679        drop(chunks_tx);
3680        cx.background_executor.run_until_parked();
3681
3682        assert_eq!(
3683            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3684            indoc! {"
3685                fn main() {
3686                    let mut x = 0;
3687                    while x < 10 {
3688                        x += 1;
3689                    }
3690                }
3691            "}
3692        );
3693    }
3694
3695    #[gpui::test(iterations = 10)]
3696    async fn test_autoindent_when_generating_past_indentation(
3697        cx: &mut TestAppContext,
3698        mut rng: StdRng,
3699    ) {
3700        cx.set_global(cx.update(SettingsStore::test));
3701        cx.update(language_settings::init);
3702
3703        let text = indoc! {"
3704            fn main() {
3705                le
3706            }
3707        "};
3708        let buffer =
3709            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3710        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3711        let range = buffer.read_with(cx, |buffer, cx| {
3712            let snapshot = buffer.snapshot(cx);
3713            snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
3714        });
3715        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3716        let codegen = cx.new_model(|cx| {
3717            CodegenAlternative::new(
3718                buffer.clone(),
3719                range.clone(),
3720                true,
3721                None,
3722                prompt_builder,
3723                cx,
3724            )
3725        });
3726
3727        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3728
3729        cx.background_executor.run_until_parked();
3730
3731        let mut new_text = concat!(
3732            "t mut x = 0;\n",
3733            "while x < 10 {\n",
3734            "    x += 1;\n",
3735            "}", //
3736        );
3737        while !new_text.is_empty() {
3738            let max_len = cmp::min(new_text.len(), 10);
3739            let len = rng.gen_range(1..=max_len);
3740            let (chunk, suffix) = new_text.split_at(len);
3741            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3742            new_text = suffix;
3743            cx.background_executor.run_until_parked();
3744        }
3745        drop(chunks_tx);
3746        cx.background_executor.run_until_parked();
3747
3748        assert_eq!(
3749            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3750            indoc! {"
3751                fn main() {
3752                    let mut x = 0;
3753                    while x < 10 {
3754                        x += 1;
3755                    }
3756                }
3757            "}
3758        );
3759    }
3760
3761    #[gpui::test(iterations = 10)]
3762    async fn test_autoindent_when_generating_before_indentation(
3763        cx: &mut TestAppContext,
3764        mut rng: StdRng,
3765    ) {
3766        cx.update(LanguageModelRegistry::test);
3767        cx.set_global(cx.update(SettingsStore::test));
3768        cx.update(language_settings::init);
3769
3770        let text = concat!(
3771            "fn main() {\n",
3772            "  \n",
3773            "}\n" //
3774        );
3775        let buffer =
3776            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3777        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3778        let range = buffer.read_with(cx, |buffer, cx| {
3779            let snapshot = buffer.snapshot(cx);
3780            snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
3781        });
3782        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3783        let codegen = cx.new_model(|cx| {
3784            CodegenAlternative::new(
3785                buffer.clone(),
3786                range.clone(),
3787                true,
3788                None,
3789                prompt_builder,
3790                cx,
3791            )
3792        });
3793
3794        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3795
3796        cx.background_executor.run_until_parked();
3797
3798        let mut new_text = concat!(
3799            "let mut x = 0;\n",
3800            "while x < 10 {\n",
3801            "    x += 1;\n",
3802            "}", //
3803        );
3804        while !new_text.is_empty() {
3805            let max_len = cmp::min(new_text.len(), 10);
3806            let len = rng.gen_range(1..=max_len);
3807            let (chunk, suffix) = new_text.split_at(len);
3808            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3809            new_text = suffix;
3810            cx.background_executor.run_until_parked();
3811        }
3812        drop(chunks_tx);
3813        cx.background_executor.run_until_parked();
3814
3815        assert_eq!(
3816            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3817            indoc! {"
3818                fn main() {
3819                    let mut x = 0;
3820                    while x < 10 {
3821                        x += 1;
3822                    }
3823                }
3824            "}
3825        );
3826    }
3827
3828    #[gpui::test(iterations = 10)]
3829    async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) {
3830        cx.update(LanguageModelRegistry::test);
3831        cx.set_global(cx.update(SettingsStore::test));
3832        cx.update(language_settings::init);
3833
3834        let text = indoc! {"
3835            func main() {
3836            \tx := 0
3837            \tfor i := 0; i < 10; i++ {
3838            \t\tx++
3839            \t}
3840            }
3841        "};
3842        let buffer = cx.new_model(|cx| Buffer::local(text, cx));
3843        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3844        let range = buffer.read_with(cx, |buffer, cx| {
3845            let snapshot = buffer.snapshot(cx);
3846            snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
3847        });
3848        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3849        let codegen = cx.new_model(|cx| {
3850            CodegenAlternative::new(
3851                buffer.clone(),
3852                range.clone(),
3853                true,
3854                None,
3855                prompt_builder,
3856                cx,
3857            )
3858        });
3859
3860        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3861        let new_text = concat!(
3862            "func main() {\n",
3863            "\tx := 0\n",
3864            "\tfor x < 10 {\n",
3865            "\t\tx++\n",
3866            "\t}", //
3867        );
3868        chunks_tx.unbounded_send(new_text.to_string()).unwrap();
3869        drop(chunks_tx);
3870        cx.background_executor.run_until_parked();
3871
3872        assert_eq!(
3873            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3874            indoc! {"
3875                func main() {
3876                \tx := 0
3877                \tfor x < 10 {
3878                \t\tx++
3879                \t}
3880                }
3881            "}
3882        );
3883    }
3884
3885    #[gpui::test]
3886    async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) {
3887        cx.update(LanguageModelRegistry::test);
3888        cx.set_global(cx.update(SettingsStore::test));
3889        cx.update(language_settings::init);
3890
3891        let text = indoc! {"
3892            fn main() {
3893                let x = 0;
3894            }
3895        "};
3896        let buffer =
3897            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3898        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3899        let range = buffer.read_with(cx, |buffer, cx| {
3900            let snapshot = buffer.snapshot(cx);
3901            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
3902        });
3903        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3904        let codegen = cx.new_model(|cx| {
3905            CodegenAlternative::new(
3906                buffer.clone(),
3907                range.clone(),
3908                false,
3909                None,
3910                prompt_builder,
3911                cx,
3912            )
3913        });
3914
3915        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3916        chunks_tx
3917            .unbounded_send("let mut x = 0;\nx += 1;".to_string())
3918            .unwrap();
3919        drop(chunks_tx);
3920        cx.run_until_parked();
3921
3922        // The codegen is inactive, so the buffer doesn't get modified.
3923        assert_eq!(
3924            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3925            text
3926        );
3927
3928        // Activating the codegen applies the changes.
3929        codegen.update(cx, |codegen, cx| codegen.set_active(true, cx));
3930        assert_eq!(
3931            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3932            indoc! {"
3933                fn main() {
3934                    let mut x = 0;
3935                    x += 1;
3936                }
3937            "}
3938        );
3939
3940        // Deactivating the codegen undoes the changes.
3941        codegen.update(cx, |codegen, cx| codegen.set_active(false, cx));
3942        cx.run_until_parked();
3943        assert_eq!(
3944            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3945            text
3946        );
3947    }
3948
3949    #[gpui::test]
3950    async fn test_strip_invalid_spans_from_codeblock() {
3951        assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
3952        assert_chunks("```\nLorem ipsum dolor", "Lorem ipsum dolor").await;
3953        assert_chunks("```\nLorem ipsum dolor\n```", "Lorem ipsum dolor").await;
3954        assert_chunks(
3955            "```html\n```js\nLorem ipsum dolor\n```\n```",
3956            "```js\nLorem ipsum dolor\n```",
3957        )
3958        .await;
3959        assert_chunks("``\nLorem ipsum dolor\n```", "``\nLorem ipsum dolor\n```").await;
3960        assert_chunks("Lorem<|CURSOR|> ipsum", "Lorem ipsum").await;
3961        assert_chunks("Lorem ipsum", "Lorem ipsum").await;
3962        assert_chunks("```\n<|CURSOR|>Lorem ipsum\n```", "Lorem ipsum").await;
3963
3964        async fn assert_chunks(text: &str, expected_text: &str) {
3965            for chunk_size in 1..=text.len() {
3966                let actual_text = StripInvalidSpans::new(chunks(text, chunk_size))
3967                    .map(|chunk| chunk.unwrap())
3968                    .collect::<String>()
3969                    .await;
3970                assert_eq!(
3971                    actual_text, expected_text,
3972                    "failed to strip invalid spans, chunk size: {}",
3973                    chunk_size
3974                );
3975            }
3976        }
3977
3978        fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
3979            stream::iter(
3980                text.chars()
3981                    .collect::<Vec<_>>()
3982                    .chunks(size)
3983                    .map(|chunk| Ok(chunk.iter().collect::<String>()))
3984                    .collect::<Vec<_>>(),
3985            )
3986        }
3987    }
3988
3989    fn simulate_response_stream(
3990        codegen: Model<CodegenAlternative>,
3991        cx: &mut TestAppContext,
3992    ) -> mpsc::UnboundedSender<String> {
3993        let (chunks_tx, chunks_rx) = mpsc::unbounded();
3994        codegen.update(cx, |codegen, cx| {
3995            codegen.handle_stream(
3996                String::new(),
3997                String::new(),
3998                None,
3999                future::ready(Ok(LanguageModelTextStream {
4000                    message_id: None,
4001                    stream: chunks_rx.map(Ok).boxed(),
4002                })),
4003                cx,
4004            );
4005        });
4006        chunks_tx
4007    }
4008
4009    fn rust_lang() -> Language {
4010        Language::new(
4011            LanguageConfig {
4012                name: "Rust".into(),
4013                matcher: LanguageMatcher {
4014                    path_suffixes: vec!["rs".to_string()],
4015                    ..Default::default()
4016                },
4017                ..Default::default()
4018            },
4019            Some(tree_sitter_rust::LANGUAGE.into()),
4020        )
4021        .with_indents_query(
4022            r#"
4023            (call_expression) @indent
4024            (field_expression) @indent
4025            (_ "(" ")" @end) @indent
4026            (_ "{" "}" @end) @indent
4027            "#,
4028        )
4029        .unwrap()
4030    }
4031}