inline_assistant.rs

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