inline_assistant.rs

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