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, Some(cx.view().downgrade()), cx)
 216                })
 217            }
 218            InlineAssistTarget::Terminal(active_terminal) => {
 219                TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 220                    assistant.assist(&active_terminal, Some(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: Option<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: Option<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: Option<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: Option<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
2244                                        .workspace
2245                                        .as_ref()
2246                                        .and_then(|workspace| workspace.upgrade())
2247                                    {
2248                                        let error = format!("Inline assistant error: {}", error);
2249                                        workspace.update(cx, |workspace, cx| {
2250                                            struct InlineAssistantError;
2251
2252                                            let id =
2253                                                NotificationId::composite::<InlineAssistantError>(
2254                                                    assist_id.0,
2255                                                );
2256
2257                                            workspace.show_toast(Toast::new(id, error), cx);
2258                                        })
2259                                    }
2260                                }
2261                            }
2262
2263                            if assist.decorations.is_none() {
2264                                this.finish_assist(assist_id, false, cx);
2265                            }
2266                        }
2267                    })
2268                }),
2269            ],
2270        }
2271    }
2272
2273    fn user_prompt(&self, cx: &AppContext) -> Option<String> {
2274        let decorations = self.decorations.as_ref()?;
2275        Some(decorations.prompt_editor.read(cx).prompt(cx))
2276    }
2277}
2278
2279struct InlineAssistDecorations {
2280    prompt_block_id: CustomBlockId,
2281    prompt_editor: View<PromptEditor>,
2282    removed_line_block_ids: HashSet<CustomBlockId>,
2283    end_block_id: CustomBlockId,
2284}
2285
2286#[derive(Copy, Clone, Debug)]
2287pub enum CodegenEvent {
2288    Finished,
2289    Undone,
2290}
2291
2292pub struct Codegen {
2293    alternatives: Vec<Model<CodegenAlternative>>,
2294    active_alternative: usize,
2295    seen_alternatives: HashSet<usize>,
2296    subscriptions: Vec<Subscription>,
2297    buffer: Model<MultiBuffer>,
2298    range: Range<Anchor>,
2299    initial_transaction_id: Option<TransactionId>,
2300    telemetry: Arc<Telemetry>,
2301    builder: Arc<PromptBuilder>,
2302    is_insertion: bool,
2303}
2304
2305impl Codegen {
2306    pub fn new(
2307        buffer: Model<MultiBuffer>,
2308        range: Range<Anchor>,
2309        initial_transaction_id: Option<TransactionId>,
2310        telemetry: Arc<Telemetry>,
2311        builder: Arc<PromptBuilder>,
2312        cx: &mut ModelContext<Self>,
2313    ) -> Self {
2314        let codegen = cx.new_model(|cx| {
2315            CodegenAlternative::new(
2316                buffer.clone(),
2317                range.clone(),
2318                false,
2319                Some(telemetry.clone()),
2320                builder.clone(),
2321                cx,
2322            )
2323        });
2324        let mut this = Self {
2325            is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
2326            alternatives: vec![codegen],
2327            active_alternative: 0,
2328            seen_alternatives: HashSet::default(),
2329            subscriptions: Vec::new(),
2330            buffer,
2331            range,
2332            initial_transaction_id,
2333            telemetry,
2334            builder,
2335        };
2336        this.activate(0, cx);
2337        this
2338    }
2339
2340    fn subscribe_to_alternative(&mut self, cx: &mut ModelContext<Self>) {
2341        let codegen = self.active_alternative().clone();
2342        self.subscriptions.clear();
2343        self.subscriptions
2344            .push(cx.observe(&codegen, |_, _, cx| cx.notify()));
2345        self.subscriptions
2346            .push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
2347    }
2348
2349    fn active_alternative(&self) -> &Model<CodegenAlternative> {
2350        &self.alternatives[self.active_alternative]
2351    }
2352
2353    fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus {
2354        &self.active_alternative().read(cx).status
2355    }
2356
2357    fn alternative_count(&self, cx: &AppContext) -> usize {
2358        LanguageModelRegistry::read_global(cx)
2359            .inline_alternative_models()
2360            .len()
2361            + 1
2362    }
2363
2364    pub fn cycle_prev(&mut self, cx: &mut ModelContext<Self>) {
2365        let next_active_ix = if self.active_alternative == 0 {
2366            self.alternatives.len() - 1
2367        } else {
2368            self.active_alternative - 1
2369        };
2370        self.activate(next_active_ix, cx);
2371    }
2372
2373    pub fn cycle_next(&mut self, cx: &mut ModelContext<Self>) {
2374        let next_active_ix = (self.active_alternative + 1) % self.alternatives.len();
2375        self.activate(next_active_ix, cx);
2376    }
2377
2378    fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
2379        self.active_alternative()
2380            .update(cx, |codegen, cx| codegen.set_active(false, cx));
2381        self.seen_alternatives.insert(index);
2382        self.active_alternative = index;
2383        self.active_alternative()
2384            .update(cx, |codegen, cx| codegen.set_active(true, cx));
2385        self.subscribe_to_alternative(cx);
2386        cx.notify();
2387    }
2388
2389    pub fn start(&mut self, user_prompt: String, cx: &mut ModelContext<Self>) -> Result<()> {
2390        let alternative_models = LanguageModelRegistry::read_global(cx)
2391            .inline_alternative_models()
2392            .to_vec();
2393
2394        self.active_alternative()
2395            .update(cx, |alternative, cx| alternative.undo(cx));
2396        self.activate(0, cx);
2397        self.alternatives.truncate(1);
2398
2399        for _ in 0..alternative_models.len() {
2400            self.alternatives.push(cx.new_model(|cx| {
2401                CodegenAlternative::new(
2402                    self.buffer.clone(),
2403                    self.range.clone(),
2404                    false,
2405                    Some(self.telemetry.clone()),
2406                    self.builder.clone(),
2407                    cx,
2408                )
2409            }));
2410        }
2411
2412        let primary_model = LanguageModelRegistry::read_global(cx)
2413            .active_model()
2414            .context("no active model")?;
2415
2416        for (model, alternative) in iter::once(primary_model)
2417            .chain(alternative_models)
2418            .zip(&self.alternatives)
2419        {
2420            alternative.update(cx, |alternative, cx| {
2421                alternative.start(user_prompt.clone(), model.clone(), cx)
2422            })?;
2423        }
2424
2425        Ok(())
2426    }
2427
2428    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
2429        for codegen in &self.alternatives {
2430            codegen.update(cx, |codegen, cx| codegen.stop(cx));
2431        }
2432    }
2433
2434    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
2435        self.active_alternative()
2436            .update(cx, |codegen, cx| codegen.undo(cx));
2437
2438        self.buffer.update(cx, |buffer, cx| {
2439            if let Some(transaction_id) = self.initial_transaction_id.take() {
2440                buffer.undo_transaction(transaction_id, cx);
2441                buffer.refresh_preview(cx);
2442            }
2443        });
2444    }
2445
2446    pub fn buffer(&self, cx: &AppContext) -> Model<MultiBuffer> {
2447        self.active_alternative().read(cx).buffer.clone()
2448    }
2449
2450    pub fn old_buffer(&self, cx: &AppContext) -> Model<Buffer> {
2451        self.active_alternative().read(cx).old_buffer.clone()
2452    }
2453
2454    pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
2455        self.active_alternative().read(cx).snapshot.clone()
2456    }
2457
2458    pub fn edit_position(&self, cx: &AppContext) -> Option<Anchor> {
2459        self.active_alternative().read(cx).edit_position
2460    }
2461
2462    fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff {
2463        &self.active_alternative().read(cx).diff
2464    }
2465
2466    pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range<Anchor>] {
2467        self.active_alternative().read(cx).last_equal_ranges()
2468    }
2469}
2470
2471impl EventEmitter<CodegenEvent> for Codegen {}
2472
2473pub struct CodegenAlternative {
2474    buffer: Model<MultiBuffer>,
2475    old_buffer: Model<Buffer>,
2476    snapshot: MultiBufferSnapshot,
2477    edit_position: Option<Anchor>,
2478    range: Range<Anchor>,
2479    last_equal_ranges: Vec<Range<Anchor>>,
2480    transformation_transaction_id: Option<TransactionId>,
2481    status: CodegenStatus,
2482    generation: Task<()>,
2483    diff: Diff,
2484    telemetry: Option<Arc<Telemetry>>,
2485    _subscription: gpui::Subscription,
2486    builder: Arc<PromptBuilder>,
2487    active: bool,
2488    edits: Vec<(Range<Anchor>, String)>,
2489    line_operations: Vec<LineOperation>,
2490    request: Option<LanguageModelRequest>,
2491    elapsed_time: Option<f64>,
2492    completion: Option<String>,
2493    message_id: Option<String>,
2494}
2495
2496enum CodegenStatus {
2497    Idle,
2498    Pending,
2499    Done,
2500    Error(anyhow::Error),
2501}
2502
2503#[derive(Default)]
2504struct Diff {
2505    deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
2506    inserted_row_ranges: Vec<Range<Anchor>>,
2507}
2508
2509impl Diff {
2510    fn is_empty(&self) -> bool {
2511        self.deleted_row_ranges.is_empty() && self.inserted_row_ranges.is_empty()
2512    }
2513}
2514
2515impl EventEmitter<CodegenEvent> for CodegenAlternative {}
2516
2517impl CodegenAlternative {
2518    pub fn new(
2519        buffer: Model<MultiBuffer>,
2520        range: Range<Anchor>,
2521        active: bool,
2522        telemetry: Option<Arc<Telemetry>>,
2523        builder: Arc<PromptBuilder>,
2524        cx: &mut ModelContext<Self>,
2525    ) -> Self {
2526        let snapshot = buffer.read(cx).snapshot(cx);
2527
2528        let (old_buffer, _, _) = buffer
2529            .read(cx)
2530            .range_to_buffer_ranges(range.clone(), cx)
2531            .pop()
2532            .unwrap();
2533        let old_buffer = cx.new_model(|cx| {
2534            let old_buffer = old_buffer.read(cx);
2535            let text = old_buffer.as_rope().clone();
2536            let line_ending = old_buffer.line_ending();
2537            let language = old_buffer.language().cloned();
2538            let language_registry = old_buffer.language_registry();
2539
2540            let mut buffer = Buffer::local_normalized(text, line_ending, cx);
2541            buffer.set_language(language, cx);
2542            if let Some(language_registry) = language_registry {
2543                buffer.set_language_registry(language_registry)
2544            }
2545            buffer
2546        });
2547
2548        Self {
2549            buffer: buffer.clone(),
2550            old_buffer,
2551            edit_position: None,
2552            message_id: None,
2553            snapshot,
2554            last_equal_ranges: Default::default(),
2555            transformation_transaction_id: None,
2556            status: CodegenStatus::Idle,
2557            generation: Task::ready(()),
2558            diff: Diff::default(),
2559            telemetry,
2560            _subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
2561            builder,
2562            active,
2563            edits: Vec::new(),
2564            line_operations: Vec::new(),
2565            range,
2566            request: None,
2567            elapsed_time: None,
2568            completion: None,
2569        }
2570    }
2571
2572    fn set_active(&mut self, active: bool, cx: &mut ModelContext<Self>) {
2573        if active != self.active {
2574            self.active = active;
2575
2576            if self.active {
2577                let edits = self.edits.clone();
2578                self.apply_edits(edits, cx);
2579                if matches!(self.status, CodegenStatus::Pending) {
2580                    let line_operations = self.line_operations.clone();
2581                    self.reapply_line_based_diff(line_operations, cx);
2582                } else {
2583                    self.reapply_batch_diff(cx).detach();
2584                }
2585            } else if let Some(transaction_id) = self.transformation_transaction_id.take() {
2586                self.buffer.update(cx, |buffer, cx| {
2587                    buffer.undo_transaction(transaction_id, cx);
2588                    buffer.forget_transaction(transaction_id, cx);
2589                });
2590            }
2591        }
2592    }
2593
2594    fn handle_buffer_event(
2595        &mut self,
2596        _buffer: Model<MultiBuffer>,
2597        event: &multi_buffer::Event,
2598        cx: &mut ModelContext<Self>,
2599    ) {
2600        if let multi_buffer::Event::TransactionUndone { transaction_id } = event {
2601            if self.transformation_transaction_id == Some(*transaction_id) {
2602                self.transformation_transaction_id = None;
2603                self.generation = Task::ready(());
2604                cx.emit(CodegenEvent::Undone);
2605            }
2606        }
2607    }
2608
2609    pub fn last_equal_ranges(&self) -> &[Range<Anchor>] {
2610        &self.last_equal_ranges
2611    }
2612
2613    pub fn start(
2614        &mut self,
2615        user_prompt: String,
2616        model: Arc<dyn LanguageModel>,
2617        cx: &mut ModelContext<Self>,
2618    ) -> Result<()> {
2619        if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() {
2620            self.buffer.update(cx, |buffer, cx| {
2621                buffer.undo_transaction(transformation_transaction_id, cx);
2622            });
2623        }
2624
2625        self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
2626
2627        let api_key = model.api_key(cx);
2628        let telemetry_id = model.telemetry_id();
2629        let provider_id = model.provider_id();
2630        let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
2631            if user_prompt.trim().to_lowercase() == "delete" {
2632                async { Ok(LanguageModelTextStream::default()) }.boxed_local()
2633            } else {
2634                let request = self.build_request(user_prompt, cx)?;
2635                self.request = Some(request.clone());
2636
2637                cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
2638                    .boxed_local()
2639            };
2640        self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
2641        Ok(())
2642    }
2643
2644    fn build_request(&self, user_prompt: String, cx: &AppContext) -> Result<LanguageModelRequest> {
2645        let buffer = self.buffer.read(cx).snapshot(cx);
2646        let language = buffer.language_at(self.range.start);
2647        let language_name = if let Some(language) = language.as_ref() {
2648            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
2649                None
2650            } else {
2651                Some(language.name())
2652            }
2653        } else {
2654            None
2655        };
2656
2657        let language_name = language_name.as_ref();
2658        let start = buffer.point_to_buffer_offset(self.range.start);
2659        let end = buffer.point_to_buffer_offset(self.range.end);
2660        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
2661            let (start_buffer, start_buffer_offset) = start;
2662            let (end_buffer, end_buffer_offset) = end;
2663            if start_buffer.remote_id() == end_buffer.remote_id() {
2664                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
2665            } else {
2666                return Err(anyhow::anyhow!("invalid transformation range"));
2667            }
2668        } else {
2669            return Err(anyhow::anyhow!("invalid transformation range"));
2670        };
2671
2672        let prompt = self
2673            .builder
2674            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
2675            .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
2676
2677        Ok(LanguageModelRequest {
2678            tools: Vec::new(),
2679            stop: Vec::new(),
2680            temperature: None,
2681            messages: vec![LanguageModelRequestMessage {
2682                role: Role::User,
2683                content: vec![prompt.into()],
2684                cache: false,
2685            }],
2686        })
2687    }
2688
2689    pub fn handle_stream(
2690        &mut self,
2691        model_telemetry_id: String,
2692        model_provider_id: String,
2693        model_api_key: Option<String>,
2694        stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
2695        cx: &mut ModelContext<Self>,
2696    ) {
2697        let start_time = Instant::now();
2698        let snapshot = self.snapshot.clone();
2699        let selected_text = snapshot
2700            .text_for_range(self.range.start..self.range.end)
2701            .collect::<Rope>();
2702
2703        let selection_start = self.range.start.to_point(&snapshot);
2704
2705        // Start with the indentation of the first line in the selection
2706        let mut suggested_line_indent = snapshot
2707            .suggested_indents(selection_start.row..=selection_start.row, cx)
2708            .into_values()
2709            .next()
2710            .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
2711
2712        // If the first line in the selection does not have indentation, check the following lines
2713        if suggested_line_indent.len == 0 && suggested_line_indent.kind == IndentKind::Space {
2714            for row in selection_start.row..=self.range.end.to_point(&snapshot).row {
2715                let line_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
2716                // Prefer tabs if a line in the selection uses tabs as indentation
2717                if line_indent.kind == IndentKind::Tab {
2718                    suggested_line_indent.kind = IndentKind::Tab;
2719                    break;
2720                }
2721            }
2722        }
2723
2724        let http_client = cx.http_client().clone();
2725        let telemetry = self.telemetry.clone();
2726        let language_name = {
2727            let multibuffer = self.buffer.read(cx);
2728            let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
2729            ranges
2730                .first()
2731                .and_then(|(buffer, _, _)| buffer.read(cx).language())
2732                .map(|language| language.name())
2733        };
2734
2735        self.diff = Diff::default();
2736        self.status = CodegenStatus::Pending;
2737        let mut edit_start = self.range.start.to_offset(&snapshot);
2738        let completion = Arc::new(Mutex::new(String::new()));
2739        let completion_clone = completion.clone();
2740
2741        self.generation = cx.spawn(|codegen, mut cx| {
2742            async move {
2743                let stream = stream.await;
2744                let message_id = stream
2745                    .as_ref()
2746                    .ok()
2747                    .and_then(|stream| stream.message_id.clone());
2748                let generate = async {
2749                    let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
2750                    let executor = cx.background_executor().clone();
2751                    let message_id = message_id.clone();
2752                    let line_based_stream_diff: Task<anyhow::Result<()>> =
2753                        cx.background_executor().spawn(async move {
2754                            let mut response_latency = None;
2755                            let request_start = Instant::now();
2756                            let diff = async {
2757                                let chunks = StripInvalidSpans::new(stream?.stream);
2758                                futures::pin_mut!(chunks);
2759                                let mut diff = StreamingDiff::new(selected_text.to_string());
2760                                let mut line_diff = LineDiff::default();
2761
2762                                let mut new_text = String::new();
2763                                let mut base_indent = None;
2764                                let mut line_indent = None;
2765                                let mut first_line = true;
2766
2767                                while let Some(chunk) = chunks.next().await {
2768                                    if response_latency.is_none() {
2769                                        response_latency = Some(request_start.elapsed());
2770                                    }
2771                                    let chunk = chunk?;
2772                                    completion_clone.lock().push_str(&chunk);
2773
2774                                    let mut lines = chunk.split('\n').peekable();
2775                                    while let Some(line) = lines.next() {
2776                                        new_text.push_str(line);
2777                                        if line_indent.is_none() {
2778                                            if let Some(non_whitespace_ch_ix) =
2779                                                new_text.find(|ch: char| !ch.is_whitespace())
2780                                            {
2781                                                line_indent = Some(non_whitespace_ch_ix);
2782                                                base_indent = base_indent.or(line_indent);
2783
2784                                                let line_indent = line_indent.unwrap();
2785                                                let base_indent = base_indent.unwrap();
2786                                                let indent_delta =
2787                                                    line_indent as i32 - base_indent as i32;
2788                                                let mut corrected_indent_len = cmp::max(
2789                                                    0,
2790                                                    suggested_line_indent.len as i32 + indent_delta,
2791                                                )
2792                                                    as usize;
2793                                                if first_line {
2794                                                    corrected_indent_len = corrected_indent_len
2795                                                        .saturating_sub(
2796                                                            selection_start.column as usize,
2797                                                        );
2798                                                }
2799
2800                                                let indent_char = suggested_line_indent.char();
2801                                                let mut indent_buffer = [0; 4];
2802                                                let indent_str =
2803                                                    indent_char.encode_utf8(&mut indent_buffer);
2804                                                new_text.replace_range(
2805                                                    ..line_indent,
2806                                                    &indent_str.repeat(corrected_indent_len),
2807                                                );
2808                                            }
2809                                        }
2810
2811                                        if line_indent.is_some() {
2812                                            let char_ops = diff.push_new(&new_text);
2813                                            line_diff
2814                                                .push_char_operations(&char_ops, &selected_text);
2815                                            diff_tx
2816                                                .send((char_ops, line_diff.line_operations()))
2817                                                .await?;
2818                                            new_text.clear();
2819                                        }
2820
2821                                        if lines.peek().is_some() {
2822                                            let char_ops = diff.push_new("\n");
2823                                            line_diff
2824                                                .push_char_operations(&char_ops, &selected_text);
2825                                            diff_tx
2826                                                .send((char_ops, line_diff.line_operations()))
2827                                                .await?;
2828                                            if line_indent.is_none() {
2829                                                // Don't write out the leading indentation in empty lines on the next line
2830                                                // This is the case where the above if statement didn't clear the buffer
2831                                                new_text.clear();
2832                                            }
2833                                            line_indent = None;
2834                                            first_line = false;
2835                                        }
2836                                    }
2837                                }
2838
2839                                let mut char_ops = diff.push_new(&new_text);
2840                                char_ops.extend(diff.finish());
2841                                line_diff.push_char_operations(&char_ops, &selected_text);
2842                                line_diff.finish(&selected_text);
2843                                diff_tx
2844                                    .send((char_ops, line_diff.line_operations()))
2845                                    .await?;
2846
2847                                anyhow::Ok(())
2848                            };
2849
2850                            let result = diff.await;
2851
2852                            let error_message =
2853                                result.as_ref().err().map(|error| error.to_string());
2854                            report_assistant_event(
2855                                AssistantEvent {
2856                                    conversation_id: None,
2857                                    message_id,
2858                                    kind: AssistantKind::Inline,
2859                                    phase: AssistantPhase::Response,
2860                                    model: model_telemetry_id,
2861                                    model_provider: model_provider_id.to_string(),
2862                                    response_latency,
2863                                    error_message,
2864                                    language_name: language_name.map(|name| name.to_proto()),
2865                                },
2866                                telemetry,
2867                                http_client,
2868                                model_api_key,
2869                                &executor,
2870                            );
2871
2872                            result?;
2873                            Ok(())
2874                        });
2875
2876                    while let Some((char_ops, line_ops)) = diff_rx.next().await {
2877                        codegen.update(&mut cx, |codegen, cx| {
2878                            codegen.last_equal_ranges.clear();
2879
2880                            let edits = char_ops
2881                                .into_iter()
2882                                .filter_map(|operation| match operation {
2883                                    CharOperation::Insert { text } => {
2884                                        let edit_start = snapshot.anchor_after(edit_start);
2885                                        Some((edit_start..edit_start, text))
2886                                    }
2887                                    CharOperation::Delete { bytes } => {
2888                                        let edit_end = edit_start + bytes;
2889                                        let edit_range = snapshot.anchor_after(edit_start)
2890                                            ..snapshot.anchor_before(edit_end);
2891                                        edit_start = edit_end;
2892                                        Some((edit_range, String::new()))
2893                                    }
2894                                    CharOperation::Keep { bytes } => {
2895                                        let edit_end = edit_start + bytes;
2896                                        let edit_range = snapshot.anchor_after(edit_start)
2897                                            ..snapshot.anchor_before(edit_end);
2898                                        edit_start = edit_end;
2899                                        codegen.last_equal_ranges.push(edit_range);
2900                                        None
2901                                    }
2902                                })
2903                                .collect::<Vec<_>>();
2904
2905                            if codegen.active {
2906                                codegen.apply_edits(edits.iter().cloned(), cx);
2907                                codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx);
2908                            }
2909                            codegen.edits.extend(edits);
2910                            codegen.line_operations = line_ops;
2911                            codegen.edit_position = Some(snapshot.anchor_after(edit_start));
2912
2913                            cx.notify();
2914                        })?;
2915                    }
2916
2917                    // Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer.
2918                    // That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff.
2919                    // It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`.
2920                    let batch_diff_task =
2921                        codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?;
2922                    let (line_based_stream_diff, ()) =
2923                        join!(line_based_stream_diff, batch_diff_task);
2924                    line_based_stream_diff?;
2925
2926                    anyhow::Ok(())
2927                };
2928
2929                let result = generate.await;
2930                let elapsed_time = start_time.elapsed().as_secs_f64();
2931
2932                codegen
2933                    .update(&mut cx, |this, cx| {
2934                        this.message_id = message_id;
2935                        this.last_equal_ranges.clear();
2936                        if let Err(error) = result {
2937                            this.status = CodegenStatus::Error(error);
2938                        } else {
2939                            this.status = CodegenStatus::Done;
2940                        }
2941                        this.elapsed_time = Some(elapsed_time);
2942                        this.completion = Some(completion.lock().clone());
2943                        cx.emit(CodegenEvent::Finished);
2944                        cx.notify();
2945                    })
2946                    .ok();
2947            }
2948        });
2949        cx.notify();
2950    }
2951
2952    pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
2953        self.last_equal_ranges.clear();
2954        if self.diff.is_empty() {
2955            self.status = CodegenStatus::Idle;
2956        } else {
2957            self.status = CodegenStatus::Done;
2958        }
2959        self.generation = Task::ready(());
2960        cx.emit(CodegenEvent::Finished);
2961        cx.notify();
2962    }
2963
2964    pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
2965        self.buffer.update(cx, |buffer, cx| {
2966            if let Some(transaction_id) = self.transformation_transaction_id.take() {
2967                buffer.undo_transaction(transaction_id, cx);
2968                buffer.refresh_preview(cx);
2969            }
2970        });
2971    }
2972
2973    fn apply_edits(
2974        &mut self,
2975        edits: impl IntoIterator<Item = (Range<Anchor>, String)>,
2976        cx: &mut ModelContext<CodegenAlternative>,
2977    ) {
2978        let transaction = self.buffer.update(cx, |buffer, cx| {
2979            // Avoid grouping assistant edits with user edits.
2980            buffer.finalize_last_transaction(cx);
2981            buffer.start_transaction(cx);
2982            buffer.edit(edits, None, cx);
2983            buffer.end_transaction(cx)
2984        });
2985
2986        if let Some(transaction) = transaction {
2987            if let Some(first_transaction) = self.transformation_transaction_id {
2988                // Group all assistant edits into the first transaction.
2989                self.buffer.update(cx, |buffer, cx| {
2990                    buffer.merge_transactions(transaction, first_transaction, cx)
2991                });
2992            } else {
2993                self.transformation_transaction_id = Some(transaction);
2994                self.buffer
2995                    .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
2996            }
2997        }
2998    }
2999
3000    fn reapply_line_based_diff(
3001        &mut self,
3002        line_operations: impl IntoIterator<Item = LineOperation>,
3003        cx: &mut ModelContext<Self>,
3004    ) {
3005        let old_snapshot = self.snapshot.clone();
3006        let old_range = self.range.to_point(&old_snapshot);
3007        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3008        let new_range = self.range.to_point(&new_snapshot);
3009
3010        let mut old_row = old_range.start.row;
3011        let mut new_row = new_range.start.row;
3012
3013        self.diff.deleted_row_ranges.clear();
3014        self.diff.inserted_row_ranges.clear();
3015        for operation in line_operations {
3016            match operation {
3017                LineOperation::Keep { lines } => {
3018                    old_row += lines;
3019                    new_row += lines;
3020                }
3021                LineOperation::Delete { lines } => {
3022                    let old_end_row = old_row + lines - 1;
3023                    let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3024
3025                    if let Some((_, last_deleted_row_range)) =
3026                        self.diff.deleted_row_ranges.last_mut()
3027                    {
3028                        if *last_deleted_row_range.end() + 1 == old_row {
3029                            *last_deleted_row_range = *last_deleted_row_range.start()..=old_end_row;
3030                        } else {
3031                            self.diff
3032                                .deleted_row_ranges
3033                                .push((new_row, old_row..=old_end_row));
3034                        }
3035                    } else {
3036                        self.diff
3037                            .deleted_row_ranges
3038                            .push((new_row, old_row..=old_end_row));
3039                    }
3040
3041                    old_row += lines;
3042                }
3043                LineOperation::Insert { lines } => {
3044                    let new_end_row = new_row + lines - 1;
3045                    let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3046                    let end = new_snapshot.anchor_before(Point::new(
3047                        new_end_row,
3048                        new_snapshot.line_len(MultiBufferRow(new_end_row)),
3049                    ));
3050                    self.diff.inserted_row_ranges.push(start..end);
3051                    new_row += lines;
3052                }
3053            }
3054
3055            cx.notify();
3056        }
3057    }
3058
3059    fn reapply_batch_diff(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
3060        let old_snapshot = self.snapshot.clone();
3061        let old_range = self.range.to_point(&old_snapshot);
3062        let new_snapshot = self.buffer.read(cx).snapshot(cx);
3063        let new_range = self.range.to_point(&new_snapshot);
3064
3065        cx.spawn(|codegen, mut cx| async move {
3066            let (deleted_row_ranges, inserted_row_ranges) = cx
3067                .background_executor()
3068                .spawn(async move {
3069                    let old_text = old_snapshot
3070                        .text_for_range(
3071                            Point::new(old_range.start.row, 0)
3072                                ..Point::new(
3073                                    old_range.end.row,
3074                                    old_snapshot.line_len(MultiBufferRow(old_range.end.row)),
3075                                ),
3076                        )
3077                        .collect::<String>();
3078                    let new_text = new_snapshot
3079                        .text_for_range(
3080                            Point::new(new_range.start.row, 0)
3081                                ..Point::new(
3082                                    new_range.end.row,
3083                                    new_snapshot.line_len(MultiBufferRow(new_range.end.row)),
3084                                ),
3085                        )
3086                        .collect::<String>();
3087
3088                    let mut old_row = old_range.start.row;
3089                    let mut new_row = new_range.start.row;
3090                    let batch_diff =
3091                        similar::TextDiff::from_lines(old_text.as_str(), new_text.as_str());
3092
3093                    let mut deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)> = Vec::new();
3094                    let mut inserted_row_ranges = Vec::new();
3095                    for change in batch_diff.iter_all_changes() {
3096                        let line_count = change.value().lines().count() as u32;
3097                        match change.tag() {
3098                            similar::ChangeTag::Equal => {
3099                                old_row += line_count;
3100                                new_row += line_count;
3101                            }
3102                            similar::ChangeTag::Delete => {
3103                                let old_end_row = old_row + line_count - 1;
3104                                let new_row = new_snapshot.anchor_before(Point::new(new_row, 0));
3105
3106                                if let Some((_, last_deleted_row_range)) =
3107                                    deleted_row_ranges.last_mut()
3108                                {
3109                                    if *last_deleted_row_range.end() + 1 == old_row {
3110                                        *last_deleted_row_range =
3111                                            *last_deleted_row_range.start()..=old_end_row;
3112                                    } else {
3113                                        deleted_row_ranges.push((new_row, old_row..=old_end_row));
3114                                    }
3115                                } else {
3116                                    deleted_row_ranges.push((new_row, old_row..=old_end_row));
3117                                }
3118
3119                                old_row += line_count;
3120                            }
3121                            similar::ChangeTag::Insert => {
3122                                let new_end_row = new_row + line_count - 1;
3123                                let start = new_snapshot.anchor_before(Point::new(new_row, 0));
3124                                let end = new_snapshot.anchor_before(Point::new(
3125                                    new_end_row,
3126                                    new_snapshot.line_len(MultiBufferRow(new_end_row)),
3127                                ));
3128                                inserted_row_ranges.push(start..end);
3129                                new_row += line_count;
3130                            }
3131                        }
3132                    }
3133
3134                    (deleted_row_ranges, inserted_row_ranges)
3135                })
3136                .await;
3137
3138            codegen
3139                .update(&mut cx, |codegen, cx| {
3140                    codegen.diff.deleted_row_ranges = deleted_row_ranges;
3141                    codegen.diff.inserted_row_ranges = inserted_row_ranges;
3142                    cx.notify();
3143                })
3144                .ok();
3145        })
3146    }
3147}
3148
3149struct StripInvalidSpans<T> {
3150    stream: T,
3151    stream_done: bool,
3152    buffer: String,
3153    first_line: bool,
3154    line_end: bool,
3155    starts_with_code_block: bool,
3156}
3157
3158impl<T> StripInvalidSpans<T>
3159where
3160    T: Stream<Item = Result<String>>,
3161{
3162    fn new(stream: T) -> Self {
3163        Self {
3164            stream,
3165            stream_done: false,
3166            buffer: String::new(),
3167            first_line: true,
3168            line_end: false,
3169            starts_with_code_block: false,
3170        }
3171    }
3172}
3173
3174impl<T> Stream for StripInvalidSpans<T>
3175where
3176    T: Stream<Item = Result<String>>,
3177{
3178    type Item = Result<String>;
3179
3180    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Option<Self::Item>> {
3181        const CODE_BLOCK_DELIMITER: &str = "```";
3182        const CURSOR_SPAN: &str = "<|CURSOR|>";
3183
3184        let this = unsafe { self.get_unchecked_mut() };
3185        loop {
3186            if !this.stream_done {
3187                let mut stream = unsafe { Pin::new_unchecked(&mut this.stream) };
3188                match stream.as_mut().poll_next(cx) {
3189                    Poll::Ready(Some(Ok(chunk))) => {
3190                        this.buffer.push_str(&chunk);
3191                    }
3192                    Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(error))),
3193                    Poll::Ready(None) => {
3194                        this.stream_done = true;
3195                    }
3196                    Poll::Pending => return Poll::Pending,
3197                }
3198            }
3199
3200            let mut chunk = String::new();
3201            let mut consumed = 0;
3202            if !this.buffer.is_empty() {
3203                let mut lines = this.buffer.split('\n').enumerate().peekable();
3204                while let Some((line_ix, line)) = lines.next() {
3205                    if line_ix > 0 {
3206                        this.first_line = false;
3207                    }
3208
3209                    if this.first_line {
3210                        let trimmed_line = line.trim();
3211                        if lines.peek().is_some() {
3212                            if trimmed_line.starts_with(CODE_BLOCK_DELIMITER) {
3213                                consumed += line.len() + 1;
3214                                this.starts_with_code_block = true;
3215                                continue;
3216                            }
3217                        } else if trimmed_line.is_empty()
3218                            || prefixes(CODE_BLOCK_DELIMITER)
3219                                .any(|prefix| trimmed_line.starts_with(prefix))
3220                        {
3221                            break;
3222                        }
3223                    }
3224
3225                    let line_without_cursor = line.replace(CURSOR_SPAN, "");
3226                    if lines.peek().is_some() {
3227                        if this.line_end {
3228                            chunk.push('\n');
3229                        }
3230
3231                        chunk.push_str(&line_without_cursor);
3232                        this.line_end = true;
3233                        consumed += line.len() + 1;
3234                    } else if this.stream_done {
3235                        if !this.starts_with_code_block
3236                            || !line_without_cursor.trim().ends_with(CODE_BLOCK_DELIMITER)
3237                        {
3238                            if this.line_end {
3239                                chunk.push('\n');
3240                            }
3241
3242                            chunk.push_str(&line);
3243                        }
3244
3245                        consumed += line.len();
3246                    } else {
3247                        let trimmed_line = line.trim();
3248                        if trimmed_line.is_empty()
3249                            || prefixes(CURSOR_SPAN).any(|prefix| trimmed_line.ends_with(prefix))
3250                            || prefixes(CODE_BLOCK_DELIMITER)
3251                                .any(|prefix| trimmed_line.ends_with(prefix))
3252                        {
3253                            break;
3254                        } else {
3255                            if this.line_end {
3256                                chunk.push('\n');
3257                                this.line_end = false;
3258                            }
3259
3260                            chunk.push_str(&line_without_cursor);
3261                            consumed += line.len();
3262                        }
3263                    }
3264                }
3265            }
3266
3267            this.buffer = this.buffer.split_off(consumed);
3268            if !chunk.is_empty() {
3269                return Poll::Ready(Some(Ok(chunk)));
3270            } else if this.stream_done {
3271                return Poll::Ready(None);
3272            }
3273        }
3274    }
3275}
3276
3277struct AssistantCodeActionProvider {
3278    editor: WeakView<Editor>,
3279    workspace: WeakView<Workspace>,
3280}
3281
3282impl CodeActionProvider for AssistantCodeActionProvider {
3283    fn code_actions(
3284        &self,
3285        buffer: &Model<Buffer>,
3286        range: Range<text::Anchor>,
3287        cx: &mut WindowContext,
3288    ) -> Task<Result<Vec<CodeAction>>> {
3289        if !AssistantSettings::get_global(cx).enabled {
3290            return Task::ready(Ok(Vec::new()));
3291        }
3292
3293        let snapshot = buffer.read(cx).snapshot();
3294        let mut range = range.to_point(&snapshot);
3295
3296        // Expand the range to line boundaries.
3297        range.start.column = 0;
3298        range.end.column = snapshot.line_len(range.end.row);
3299
3300        let mut has_diagnostics = false;
3301        for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) {
3302            range.start = cmp::min(range.start, diagnostic.range.start);
3303            range.end = cmp::max(range.end, diagnostic.range.end);
3304            has_diagnostics = true;
3305        }
3306        if has_diagnostics {
3307            if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) {
3308                if let Some(symbol) = symbols_containing_start.last() {
3309                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3310                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3311                }
3312            }
3313
3314            if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) {
3315                if let Some(symbol) = symbols_containing_end.last() {
3316                    range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot));
3317                    range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot));
3318                }
3319            }
3320
3321            Task::ready(Ok(vec![CodeAction {
3322                server_id: language::LanguageServerId(0),
3323                range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end),
3324                lsp_action: lsp::CodeAction {
3325                    title: "Fix with Assistant".into(),
3326                    ..Default::default()
3327                },
3328            }]))
3329        } else {
3330            Task::ready(Ok(Vec::new()))
3331        }
3332    }
3333
3334    fn apply_code_action(
3335        &self,
3336        buffer: Model<Buffer>,
3337        action: CodeAction,
3338        excerpt_id: ExcerptId,
3339        _push_to_history: bool,
3340        cx: &mut WindowContext,
3341    ) -> Task<Result<ProjectTransaction>> {
3342        let editor = self.editor.clone();
3343        let workspace = self.workspace.clone();
3344        cx.spawn(|mut cx| async move {
3345            let editor = editor.upgrade().context("editor was released")?;
3346            let range = editor
3347                .update(&mut cx, |editor, cx| {
3348                    editor.buffer().update(cx, |multibuffer, cx| {
3349                        let buffer = buffer.read(cx);
3350                        let multibuffer_snapshot = multibuffer.read(cx);
3351
3352                        let old_context_range =
3353                            multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
3354                        let mut new_context_range = old_context_range.clone();
3355                        if action
3356                            .range
3357                            .start
3358                            .cmp(&old_context_range.start, buffer)
3359                            .is_lt()
3360                        {
3361                            new_context_range.start = action.range.start;
3362                        }
3363                        if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
3364                            new_context_range.end = action.range.end;
3365                        }
3366                        drop(multibuffer_snapshot);
3367
3368                        if new_context_range != old_context_range {
3369                            multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
3370                        }
3371
3372                        let multibuffer_snapshot = multibuffer.read(cx);
3373                        Some(
3374                            multibuffer_snapshot
3375                                .anchor_in_excerpt(excerpt_id, action.range.start)?
3376                                ..multibuffer_snapshot
3377                                    .anchor_in_excerpt(excerpt_id, action.range.end)?,
3378                        )
3379                    })
3380                })?
3381                .context("invalid range")?;
3382
3383            cx.update_global(|assistant: &mut InlineAssistant, cx| {
3384                let assist_id = assistant.suggest_assist(
3385                    &editor,
3386                    range,
3387                    "Fix Diagnostics".into(),
3388                    None,
3389                    true,
3390                    Some(workspace),
3391                    cx,
3392                );
3393                assistant.start_assist(assist_id, cx);
3394            })?;
3395
3396            Ok(ProjectTransaction::default())
3397        })
3398    }
3399}
3400
3401fn prefixes(text: &str) -> impl Iterator<Item = &str> {
3402    (0..text.len() - 1).map(|ix| &text[..ix + 1])
3403}
3404
3405fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
3406    ranges.sort_unstable_by(|a, b| {
3407        a.start
3408            .cmp(&b.start, buffer)
3409            .then_with(|| b.end.cmp(&a.end, buffer))
3410    });
3411
3412    let mut ix = 0;
3413    while ix + 1 < ranges.len() {
3414        let b = ranges[ix + 1].clone();
3415        let a = &mut ranges[ix];
3416        if a.end.cmp(&b.start, buffer).is_gt() {
3417            if a.end.cmp(&b.end, buffer).is_lt() {
3418                a.end = b.end;
3419            }
3420            ranges.remove(ix + 1);
3421        } else {
3422            ix += 1;
3423        }
3424    }
3425}
3426
3427#[cfg(test)]
3428mod tests {
3429    use super::*;
3430    use futures::stream::{self};
3431    use gpui::{Context, TestAppContext};
3432    use indoc::indoc;
3433    use language::{
3434        language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
3435        Point,
3436    };
3437    use language_model::LanguageModelRegistry;
3438    use rand::prelude::*;
3439    use serde::Serialize;
3440    use settings::SettingsStore;
3441    use std::{future, sync::Arc};
3442
3443    #[derive(Serialize)]
3444    pub struct DummyCompletionRequest {
3445        pub name: String,
3446    }
3447
3448    #[gpui::test(iterations = 10)]
3449    async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
3450        cx.set_global(cx.update(SettingsStore::test));
3451        cx.update(language_model::LanguageModelRegistry::test);
3452        cx.update(language_settings::init);
3453
3454        let text = indoc! {"
3455            fn main() {
3456                let x = 0;
3457                for _ in 0..10 {
3458                    x += 1;
3459                }
3460            }
3461        "};
3462        let buffer =
3463            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3464        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3465        let range = buffer.read_with(cx, |buffer, cx| {
3466            let snapshot = buffer.snapshot(cx);
3467            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
3468        });
3469        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3470        let codegen = cx.new_model(|cx| {
3471            CodegenAlternative::new(
3472                buffer.clone(),
3473                range.clone(),
3474                true,
3475                None,
3476                prompt_builder,
3477                cx,
3478            )
3479        });
3480
3481        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3482
3483        let mut new_text = concat!(
3484            "       let mut x = 0;\n",
3485            "       while x < 10 {\n",
3486            "           x += 1;\n",
3487            "       }",
3488        );
3489        while !new_text.is_empty() {
3490            let max_len = cmp::min(new_text.len(), 10);
3491            let len = rng.gen_range(1..=max_len);
3492            let (chunk, suffix) = new_text.split_at(len);
3493            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3494            new_text = suffix;
3495            cx.background_executor.run_until_parked();
3496        }
3497        drop(chunks_tx);
3498        cx.background_executor.run_until_parked();
3499
3500        assert_eq!(
3501            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3502            indoc! {"
3503                fn main() {
3504                    let mut x = 0;
3505                    while x < 10 {
3506                        x += 1;
3507                    }
3508                }
3509            "}
3510        );
3511    }
3512
3513    #[gpui::test(iterations = 10)]
3514    async fn test_autoindent_when_generating_past_indentation(
3515        cx: &mut TestAppContext,
3516        mut rng: StdRng,
3517    ) {
3518        cx.set_global(cx.update(SettingsStore::test));
3519        cx.update(language_settings::init);
3520
3521        let text = indoc! {"
3522            fn main() {
3523                le
3524            }
3525        "};
3526        let buffer =
3527            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3528        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3529        let range = buffer.read_with(cx, |buffer, cx| {
3530            let snapshot = buffer.snapshot(cx);
3531            snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6))
3532        });
3533        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3534        let codegen = cx.new_model(|cx| {
3535            CodegenAlternative::new(
3536                buffer.clone(),
3537                range.clone(),
3538                true,
3539                None,
3540                prompt_builder,
3541                cx,
3542            )
3543        });
3544
3545        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3546
3547        cx.background_executor.run_until_parked();
3548
3549        let mut new_text = concat!(
3550            "t mut x = 0;\n",
3551            "while x < 10 {\n",
3552            "    x += 1;\n",
3553            "}", //
3554        );
3555        while !new_text.is_empty() {
3556            let max_len = cmp::min(new_text.len(), 10);
3557            let len = rng.gen_range(1..=max_len);
3558            let (chunk, suffix) = new_text.split_at(len);
3559            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3560            new_text = suffix;
3561            cx.background_executor.run_until_parked();
3562        }
3563        drop(chunks_tx);
3564        cx.background_executor.run_until_parked();
3565
3566        assert_eq!(
3567            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3568            indoc! {"
3569                fn main() {
3570                    let mut x = 0;
3571                    while x < 10 {
3572                        x += 1;
3573                    }
3574                }
3575            "}
3576        );
3577    }
3578
3579    #[gpui::test(iterations = 10)]
3580    async fn test_autoindent_when_generating_before_indentation(
3581        cx: &mut TestAppContext,
3582        mut rng: StdRng,
3583    ) {
3584        cx.update(LanguageModelRegistry::test);
3585        cx.set_global(cx.update(SettingsStore::test));
3586        cx.update(language_settings::init);
3587
3588        let text = concat!(
3589            "fn main() {\n",
3590            "  \n",
3591            "}\n" //
3592        );
3593        let buffer =
3594            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3595        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3596        let range = buffer.read_with(cx, |buffer, cx| {
3597            let snapshot = buffer.snapshot(cx);
3598            snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2))
3599        });
3600        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3601        let codegen = cx.new_model(|cx| {
3602            CodegenAlternative::new(
3603                buffer.clone(),
3604                range.clone(),
3605                true,
3606                None,
3607                prompt_builder,
3608                cx,
3609            )
3610        });
3611
3612        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3613
3614        cx.background_executor.run_until_parked();
3615
3616        let mut new_text = concat!(
3617            "let mut x = 0;\n",
3618            "while x < 10 {\n",
3619            "    x += 1;\n",
3620            "}", //
3621        );
3622        while !new_text.is_empty() {
3623            let max_len = cmp::min(new_text.len(), 10);
3624            let len = rng.gen_range(1..=max_len);
3625            let (chunk, suffix) = new_text.split_at(len);
3626            chunks_tx.unbounded_send(chunk.to_string()).unwrap();
3627            new_text = suffix;
3628            cx.background_executor.run_until_parked();
3629        }
3630        drop(chunks_tx);
3631        cx.background_executor.run_until_parked();
3632
3633        assert_eq!(
3634            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3635            indoc! {"
3636                fn main() {
3637                    let mut x = 0;
3638                    while x < 10 {
3639                        x += 1;
3640                    }
3641                }
3642            "}
3643        );
3644    }
3645
3646    #[gpui::test(iterations = 10)]
3647    async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) {
3648        cx.update(LanguageModelRegistry::test);
3649        cx.set_global(cx.update(SettingsStore::test));
3650        cx.update(language_settings::init);
3651
3652        let text = indoc! {"
3653            func main() {
3654            \tx := 0
3655            \tfor i := 0; i < 10; i++ {
3656            \t\tx++
3657            \t}
3658            }
3659        "};
3660        let buffer = cx.new_model(|cx| Buffer::local(text, cx));
3661        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3662        let range = buffer.read_with(cx, |buffer, cx| {
3663            let snapshot = buffer.snapshot(cx);
3664            snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2))
3665        });
3666        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3667        let codegen = cx.new_model(|cx| {
3668            CodegenAlternative::new(
3669                buffer.clone(),
3670                range.clone(),
3671                true,
3672                None,
3673                prompt_builder,
3674                cx,
3675            )
3676        });
3677
3678        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3679        let new_text = concat!(
3680            "func main() {\n",
3681            "\tx := 0\n",
3682            "\tfor x < 10 {\n",
3683            "\t\tx++\n",
3684            "\t}", //
3685        );
3686        chunks_tx.unbounded_send(new_text.to_string()).unwrap();
3687        drop(chunks_tx);
3688        cx.background_executor.run_until_parked();
3689
3690        assert_eq!(
3691            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3692            indoc! {"
3693                func main() {
3694                \tx := 0
3695                \tfor x < 10 {
3696                \t\tx++
3697                \t}
3698                }
3699            "}
3700        );
3701    }
3702
3703    #[gpui::test]
3704    async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) {
3705        cx.update(LanguageModelRegistry::test);
3706        cx.set_global(cx.update(SettingsStore::test));
3707        cx.update(language_settings::init);
3708
3709        let text = indoc! {"
3710            fn main() {
3711                let x = 0;
3712            }
3713        "};
3714        let buffer =
3715            cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3716        let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
3717        let range = buffer.read_with(cx, |buffer, cx| {
3718            let snapshot = buffer.snapshot(cx);
3719            snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14))
3720        });
3721        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3722        let codegen = cx.new_model(|cx| {
3723            CodegenAlternative::new(
3724                buffer.clone(),
3725                range.clone(),
3726                false,
3727                None,
3728                prompt_builder,
3729                cx,
3730            )
3731        });
3732
3733        let chunks_tx = simulate_response_stream(codegen.clone(), cx);
3734        chunks_tx
3735            .unbounded_send("let mut x = 0;\nx += 1;".to_string())
3736            .unwrap();
3737        drop(chunks_tx);
3738        cx.run_until_parked();
3739
3740        // The codegen is inactive, so the buffer doesn't get modified.
3741        assert_eq!(
3742            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3743            text
3744        );
3745
3746        // Activating the codegen applies the changes.
3747        codegen.update(cx, |codegen, cx| codegen.set_active(true, cx));
3748        assert_eq!(
3749            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3750            indoc! {"
3751                fn main() {
3752                    let mut x = 0;
3753                    x += 1;
3754                }
3755            "}
3756        );
3757
3758        // Deactivating the codegen undoes the changes.
3759        codegen.update(cx, |codegen, cx| codegen.set_active(false, cx));
3760        cx.run_until_parked();
3761        assert_eq!(
3762            buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()),
3763            text
3764        );
3765    }
3766
3767    #[gpui::test]
3768    async fn test_strip_invalid_spans_from_codeblock() {
3769        assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await;
3770        assert_chunks("```\nLorem ipsum dolor", "Lorem ipsum dolor").await;
3771        assert_chunks("```\nLorem ipsum dolor\n```", "Lorem ipsum dolor").await;
3772        assert_chunks(
3773            "```html\n```js\nLorem ipsum dolor\n```\n```",
3774            "```js\nLorem ipsum dolor\n```",
3775        )
3776        .await;
3777        assert_chunks("``\nLorem ipsum dolor\n```", "``\nLorem ipsum dolor\n```").await;
3778        assert_chunks("Lorem<|CURSOR|> ipsum", "Lorem ipsum").await;
3779        assert_chunks("Lorem ipsum", "Lorem ipsum").await;
3780        assert_chunks("```\n<|CURSOR|>Lorem ipsum\n```", "Lorem ipsum").await;
3781
3782        async fn assert_chunks(text: &str, expected_text: &str) {
3783            for chunk_size in 1..=text.len() {
3784                let actual_text = StripInvalidSpans::new(chunks(text, chunk_size))
3785                    .map(|chunk| chunk.unwrap())
3786                    .collect::<String>()
3787                    .await;
3788                assert_eq!(
3789                    actual_text, expected_text,
3790                    "failed to strip invalid spans, chunk size: {}",
3791                    chunk_size
3792                );
3793            }
3794        }
3795
3796        fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
3797            stream::iter(
3798                text.chars()
3799                    .collect::<Vec<_>>()
3800                    .chunks(size)
3801                    .map(|chunk| Ok(chunk.iter().collect::<String>()))
3802                    .collect::<Vec<_>>(),
3803            )
3804        }
3805    }
3806
3807    fn simulate_response_stream(
3808        codegen: Model<CodegenAlternative>,
3809        cx: &mut TestAppContext,
3810    ) -> mpsc::UnboundedSender<String> {
3811        let (chunks_tx, chunks_rx) = mpsc::unbounded();
3812        codegen.update(cx, |codegen, cx| {
3813            codegen.handle_stream(
3814                String::new(),
3815                String::new(),
3816                None,
3817                future::ready(Ok(LanguageModelTextStream {
3818                    message_id: None,
3819                    stream: chunks_rx.map(Ok).boxed(),
3820                })),
3821                cx,
3822            );
3823        });
3824        chunks_tx
3825    }
3826
3827    fn rust_lang() -> Language {
3828        Language::new(
3829            LanguageConfig {
3830                name: "Rust".into(),
3831                matcher: LanguageMatcher {
3832                    path_suffixes: vec!["rs".to_string()],
3833                    ..Default::default()
3834                },
3835                ..Default::default()
3836            },
3837            Some(tree_sitter_rust::LANGUAGE.into()),
3838        )
3839        .with_indents_query(
3840            r#"
3841            (call_expression) @indent
3842            (field_expression) @indent
3843            (_ "(" ")" @end) @indent
3844            (_ "{" "}" @end) @indent
3845            "#,
3846        )
3847        .unwrap()
3848    }
3849}