inline_assistant.rs

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