inline_assistant.rs

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