inline_assistant.rs

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