use language_model::AnthropicEventData;use language_model::report_anthropic_event;use std::cmp;use std::mem;use std::ops::Range;use std::rc::Rc;use std::sync::Arc;use uuid::Uuid; use crate::context::load_context;use crate::mention_set::MentionSet;use crate::{ AgentPanel, buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent}, inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent}, terminal_inline_assistant::TerminalInlineAssistant,};use agent::HistoryStore;use agent_settings::AgentSettings;use anyhow::{Context as _, Result};use collections::{HashMap, HashSet, VecDeque, hash_map};use editor::EditorSnapshot;use editor::MultiBufferOffset;use editor::RowExt;use editor::SelectionEffects;use editor::scroll::ScrollOffset;use editor::{ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, actions::SelectAll, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins, RenderBlock, ToDisplayPoint, },};use fs::Fs;use futures::{FutureExt, channel::mpsc};use gpui::{ App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal, WeakEntity, Window, point,};use language::{Buffer, Point, Selection, TransactionId};use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry};use multi_buffer::MultiBufferRow;use parking_lot::Mutex;use project::{CodeAction, DisableAiSettings, LspAction, Project, ProjectTransaction};use prompt_store::{PromptBuilder, PromptStore};use settings::{Settings, SettingsStore}; use terminal_view::{TerminalView, terminal_panel::TerminalPanel};use text::{OffsetRangeExt, ToPoint as _};use ui::prelude::*;use util::{RangeExt, ResultExt, maybe};use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};use zed_actions::agent::OpenSettings; pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) { cx.set_global(InlineAssistant::new(fs, prompt_builder)); cx.observe_global::<SettingsStore>(|cx| { if DisableAiSettings::get_global(cx).disable_ai { // Hide any active inline assist UI when AI is disabled InlineAssistant::update_global(cx, |assistant, cx| { assistant.cancel_all_active_completions(cx); }); } }) .detach(); cx.observe_new(|_workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; let workspace = cx.entity(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, window, cx) }); }) .detach();} const PROMPT_HISTORY_MAX_LEN: usize = 20; enum InlineAssistTarget { Editor(Entity<Editor>), Terminal(Entity<TerminalView>),} pub struct InlineAssistant { next_assist_id: InlineAssistId, next_assist_group_id: InlineAssistGroupId, assists: HashMap<InlineAssistId, InlineAssist>, assists_by_editor: HashMap<WeakEntity<Editor>, EditorInlineAssists>, assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>, confirmed_assists: HashMap<InlineAssistId, Entity<CodegenAlternative>>, prompt_history: VecDeque<String>, prompt_builder: Arc<PromptBuilder>, fs: Arc<dyn Fs>, _inline_assistant_completions: Option<mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>>,} impl Global for InlineAssistant {} impl InlineAssistant { pub fn new(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>) -> Self { Self { next_assist_id: InlineAssistId::default(), next_assist_group_id: InlineAssistGroupId::default(), assists: HashMap::default(), assists_by_editor: HashMap::default(), assist_groups: HashMap::default(), confirmed_assists: HashMap::default(), prompt_history: VecDeque::default(), prompt_builder, fs, _inline_assistant_completions: None, } } pub fn register_workspace( &mut self, workspace: &Entity<Workspace>, window: &mut Window, cx: &mut App, ) { window .subscribe(workspace, cx, |workspace, event, window, cx| { Self::update_global(cx, |this, cx| { this.handle_workspace_event(workspace, event, window, cx) }); }) .detach(); let workspace = workspace.downgrade(); cx.observe_global::<SettingsStore>(move |cx| { let Some(workspace) = workspace.upgrade() else { return; }; let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else { return; }; let enabled = AgentSettings::get_global(cx).enabled(cx); terminal_panel.update(cx, |terminal_panel, cx| { terminal_panel.set_assistant_enabled(enabled, cx) }); }) .detach(); } /// Hides all active inline assists when AI is disabled pub fn cancel_all_active_completions(&mut self, cx: &mut App) { // Cancel all active completions in editors for (editor_handle, _) in self.assists_by_editor.iter() { if let Some(editor) = editor_handle.upgrade() { let windows = cx.windows(); if !windows.is_empty() { let window = windows[0]; let _ = window.update(cx, |_, window, cx| { editor.update(cx, |editor, cx| { if editor.has_active_edit_prediction() { editor.cancel(&Default::default(), window, cx); } }); }); } } } } fn handle_workspace_event( &mut self, workspace: Entity<Workspace>, event: &workspace::Event, window: &mut Window, cx: &mut App, ) { match event { workspace::Event::UserSavedItem { item, .. } => { // When the user manually saves an editor, automatically accepts all finished transformations. if let Some(editor) = item.upgrade().and_then(|item| item.act_as::<Editor>(cx)) && let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { self.finish_assist(assist_id, false, window, cx) } } } } workspace::Event::ItemAdded { item } => { self.register_workspace_item(&workspace, item.as_ref(), window, cx); } _ => (), } } fn register_workspace_item( &mut self, workspace: &Entity<Workspace>, item: &dyn ItemHandle, window: &mut Window, cx: &mut App, ) { let is_ai_enabled = !DisableAiSettings::get_global(cx).disable_ai; if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { if is_ai_enabled { editor.add_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.entity().downgrade(), workspace: workspace.downgrade(), }), window, cx, ); if DisableAiSettings::get_global(cx).disable_ai { // Cancel any active edit predictions if editor.has_active_edit_prediction() { editor.cancel(&Default::default(), window, cx); } } } else { editor.remove_code_action_provider( ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), window, cx, ); } }); } } pub fn inline_assist( workspace: &mut Workspace, action: &zed_actions::assistant::InlineAssist, window: &mut Window, cx: &mut Context<Workspace>, ) { if !AgentSettings::get_global(cx).enabled(cx) { return; } let Some(inline_assist_target) = Self::resolve_inline_assist_target( workspace, workspace.panel::<AgentPanel>(cx), window, cx, ) else { return; }; let configuration_error = || { let model_registry = LanguageModelRegistry::read_global(cx); model_registry.configuration_error(model_registry.inline_assistant_model(), cx) }; let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else { return; }; let agent_panel = agent_panel.read(cx); let prompt_store = agent_panel.prompt_store().as_ref().cloned(); let thread_store = agent_panel.thread_store().clone(); let handle_assist = |window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target { InlineAssistTarget::Editor(active_editor) => { InlineAssistant::update_global(cx, |assistant, cx| { assistant.assist( &active_editor, cx.entity().downgrade(), workspace.project().downgrade(), thread_store, prompt_store, action.prompt.clone(), window, cx, ); }) } InlineAssistTarget::Terminal(active_terminal) => { TerminalInlineAssistant::update_global(cx, |assistant, cx| { assistant.assist( &active_terminal, cx.entity().downgrade(), workspace.project().downgrade(), thread_store, prompt_store, action.prompt.clone(), window, cx, ); }); } }; if let Some(error) = configuration_error() { if let ConfigurationError::ProviderNotAuthenticated(provider) = error { cx.spawn(async move |_, cx| { cx.update(|cx| provider.authenticate(cx))?.await?; anyhow::Ok(()) }) .detach_and_log_err(cx); if configuration_error().is_none() { handle_assist(window, cx); } } else { cx.spawn_in(window, async move |_, cx| { let answer = cx .prompt( gpui::PromptLevel::Warning, &error.to_string(), None, &["Configure", "Cancel"], ) .await .ok(); if let Some(answer) = answer && answer == 0 { cx.update(|window, cx| window.dispatch_action(Box::new(OpenSettings), cx)) .ok(); } anyhow::Ok(()) }) .detach_and_log_err(cx); } } else { handle_assist(window, cx); } } fn codegen_ranges( &mut self, editor: &Entity<Editor>, snapshot: &EditorSnapshot, window: &mut Window, cx: &mut App, ) -> Option<(Vec<Range<Anchor>>, Selection<Point>)> { let (initial_selections, newest_selection) = editor.update(cx, |editor, _| { ( editor.selections.all::<Point>(&snapshot.display_snapshot), editor .selections .newest::<Point>(&snapshot.display_snapshot), ) }); // Check if there is already an inline assistant that contains the // newest selection, if there is, focus it if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let range = assist.range.to_point(&snapshot.buffer_snapshot()); if range.start.row <= newest_selection.start.row && newest_selection.end.row <= range.end.row { self.focus_assist(*assist_id, window, cx); return None; } } } let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in initial_selections { if selection.end == selection.start && let Some(fold) = snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) { selection.start = fold.range().start; selection.end = fold.range().end; if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot().max_row() { let chars = snapshot .buffer_snapshot() .chars_at(Point::new(selection.end.row + 1, 0)); for c in chars { if c == '\n' { break; } if c.is_whitespace() { continue; } if snapshot .language_at(selection.end) .is_some_and(|language| language.config().brackets.is_closing_brace(c)) { selection.end.row += 1; selection.end.column = snapshot .buffer_snapshot() .line_len(MultiBufferRow(selection.end.row)); } } } } else { selection.start.column = 0; // If the selection ends at the start of the line, we don't want to include it. if selection.end.column == 0 && selection.start.row != selection.end.row { selection.end.row -= 1; } selection.end.column = snapshot .buffer_snapshot() .line_len(MultiBufferRow(selection.end.row)); } if let Some(prev_selection) = selections.last_mut() && selection.start <= prev_selection.end { prev_selection.end = selection.end; continue; } let latest_selection = newest_selection.get_or_insert_with(|| selection.clone()); if selection.id > latest_selection.id { *latest_selection = selection.clone(); } selections.push(selection); } let snapshot = &snapshot.buffer_snapshot(); let newest_selection = newest_selection.unwrap(); let mut codegen_ranges = Vec::new(); for (buffer, buffer_range, excerpt_id) in snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| { snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end) })) { let anchor_range = Anchor::range_in_buffer( excerpt_id, buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end), ); codegen_ranges.push(anchor_range); if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() { telemetry::event!( "Assistant Invoked", kind = "inline", phase = "invoked", model = model.model.telemetry_id(), model_provider = model.provider.id().to_string(), language_name = buffer.language().map(|language| language.name().to_proto()) ); report_anthropic_event( &model.model, AnthropicEventData { completion_type: language_model::AnthropicCompletionType::Editor, event: language_model::AnthropicEventType::Invoked, language_name: buffer.language().map(|language| language.name().to_proto()), message_id: None, }, cx, ); } } Some((codegen_ranges, newest_selection)) } fn batch_assist( &mut self, editor: &Entity<Editor>, workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, thread_store: Entity<HistoryStore>, prompt_store: Option<Entity<PromptStore>>, initial_prompt: Option<String>, window: &mut Window, codegen_ranges: &[Range<Anchor>], newest_selection: Option<Selection<Point>>, initial_transaction_id: Option<TransactionId>, cx: &mut App, ) -> Option<InlineAssistId> { let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx)); let assist_group_id = self.next_assist_group_id.post_inc(); let session_id = Uuid::new_v4(); let prompt_buffer = cx.new(|cx| { MultiBuffer::singleton( cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)), cx, ) }); let mut assists = Vec::new(); let mut assist_to_focus = None; for range in codegen_ranges { let assist_id = self.next_assist_id.post_inc(); let codegen = cx.new(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id,Lines 1–500 of 2410. Load more
use language_model::AnthropicEventData; use language_model::report_anthropic_event; use std::cmp; use std::mem; use std::ops::Range; use std::rc::Rc; use std::sync::Arc; use uuid::Uuid; use crate::context::load_context; use crate::mention_set::MentionSet; use crate::{ AgentPanel, buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent}, inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent}, terminal_inline_assistant::TerminalInlineAssistant, }; use agent::HistoryStore; use agent_settings::AgentSettings; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet, VecDeque, hash_map}; use editor::EditorSnapshot; use editor::MultiBufferOffset; use editor::RowExt; use editor::SelectionEffects; use editor::scroll::ScrollOffset; use editor::{ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, actions::SelectAll, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins, RenderBlock, ToDisplayPoint, }, }; use fs::Fs; use futures::{FutureExt, channel::mpsc}; use gpui::{ App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal, WeakEntity, Window, point, }; use language::{Buffer, Point, Selection, TransactionId}; use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry}; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, DisableAiSettings, LspAction, Project, ProjectTransaction}; use prompt_store::{PromptBuilder, PromptStore}; use settings::{Settings, SettingsStore}; use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use text::{OffsetRangeExt, ToPoint as _}; use ui::prelude::*; use util::{RangeExt, ResultExt, maybe}; use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId}; use zed_actions::agent::OpenSettings; pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) { cx.set_global(InlineAssistant::new(fs, prompt_builder)); cx.observe_global::<SettingsStore>(|cx| { if DisableAiSettings::get_global(cx).disable_ai { // Hide any active inline assist UI when AI is disabled InlineAssistant::update_global(cx, |assistant, cx| { assistant.cancel_all_active_completions(cx); }); } }) .detach(); cx.observe_new(|_workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; let workspace = cx.entity(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, window, cx) }); }) .detach(); } const PROMPT_HISTORY_MAX_LEN: usize = 20; enum InlineAssistTarget { Editor(Entity<Editor>), Terminal(Entity<TerminalView>), } pub struct InlineAssistant { next_assist_id: InlineAssistId, next_assist_group_id: InlineAssistGroupId, assists: HashMap<InlineAssistId, InlineAssist>, assists_by_editor: HashMap<WeakEntity<Editor>, EditorInlineAssists>, assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>, confirmed_assists: HashMap<InlineAssistId, Entity<CodegenAlternative>>, prompt_history: VecDeque<String>, prompt_builder: Arc<PromptBuilder>, fs: Arc<dyn Fs>, _inline_assistant_completions: Option<mpsc::UnboundedSender<anyhow::Result<InlineAssistId>>>, } impl Global for InlineAssistant {} impl InlineAssistant { pub fn new(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>) -> Self { Self { next_assist_id: InlineAssistId::default(), next_assist_group_id: InlineAssistGroupId::default(), assists: HashMap::default(), assists_by_editor: HashMap::default(), assist_groups: HashMap::default(), confirmed_assists: HashMap::default(), prompt_history: VecDeque::default(), prompt_builder, fs, _inline_assistant_completions: None, } } pub fn register_workspace( &mut self, workspace: &Entity<Workspace>, window: &mut Window, cx: &mut App, ) { window .subscribe(workspace, cx, |workspace, event, window, cx| { Self::update_global(cx, |this, cx| { this.handle_workspace_event(workspace, event, window, cx) }); }) .detach(); let workspace = workspace.downgrade(); cx.observe_global::<SettingsStore>(move |cx| { let Some(workspace) = workspace.upgrade() else { return; }; let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else { return; }; let enabled = AgentSettings::get_global(cx).enabled(cx); terminal_panel.update(cx, |terminal_panel, cx| { terminal_panel.set_assistant_enabled(enabled, cx) }); }) .detach(); } /// Hides all active inline assists when AI is disabled pub fn cancel_all_active_completions(&mut self, cx: &mut App) { // Cancel all active completions in editors for (editor_handle, _) in self.assists_by_editor.iter() { if let Some(editor) = editor_handle.upgrade() { let windows = cx.windows(); if !windows.is_empty() { let window = windows[0]; let _ = window.update(cx, |_, window, cx| { editor.update(cx, |editor, cx| { if editor.has_active_edit_prediction() { editor.cancel(&Default::default(), window, cx); } }); }); } } } } fn handle_workspace_event( &mut self, workspace: Entity<Workspace>, event: &workspace::Event, window: &mut Window, cx: &mut App, ) { match event { workspace::Event::UserSavedItem { item, .. } => { // When the user manually saves an editor, automatically accepts all finished transformations. if let Some(editor) = item.upgrade().and_then(|item| item.act_as::<Editor>(cx)) && let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { self.finish_assist(assist_id, false, window, cx) } } } } workspace::Event::ItemAdded { item } => { self.register_workspace_item(&workspace, item.as_ref(), window, cx); } _ => (), } } fn register_workspace_item( &mut self, workspace: &Entity<Workspace>, item: &dyn ItemHandle, window: &mut Window, cx: &mut App, ) { let is_ai_enabled = !DisableAiSettings::get_global(cx).disable_ai; if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { if is_ai_enabled { editor.add_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.entity().downgrade(), workspace: workspace.downgrade(), }), window, cx, ); if DisableAiSettings::get_global(cx).disable_ai { // Cancel any active edit predictions if editor.has_active_edit_prediction() { editor.cancel(&Default::default(), window, cx); } } } else { editor.remove_code_action_provider( ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), window, cx, ); } }); } } pub fn inline_assist( workspace: &mut Workspace, action: &zed_actions::assistant::InlineAssist, window: &mut Window, cx: &mut Context<Workspace>, ) { if !AgentSettings::get_global(cx).enabled(cx) { return; } let Some(inline_assist_target) = Self::resolve_inline_assist_target( workspace, workspace.panel::<AgentPanel>(cx), window, cx, ) else { return; }; let configuration_error = || { let model_registry = LanguageModelRegistry::read_global(cx); model_registry.configuration_error(model_registry.inline_assistant_model(), cx) }; let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else { return; }; let agent_panel = agent_panel.read(cx); let prompt_store = agent_panel.prompt_store().as_ref().cloned(); let thread_store = agent_panel.thread_store().clone(); let handle_assist = |window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target { InlineAssistTarget::Editor(active_editor) => { InlineAssistant::update_global(cx, |assistant, cx| { assistant.assist( &active_editor, cx.entity().downgrade(), workspace.project().downgrade(), thread_store, prompt_store, action.prompt.clone(), window, cx, ); }) } InlineAssistTarget::Terminal(active_terminal) => { TerminalInlineAssistant::update_global(cx, |assistant, cx| { assistant.assist( &active_terminal, cx.entity().downgrade(), workspace.project().downgrade(), thread_store, prompt_store, action.prompt.clone(), window, cx, ); }); } }; if let Some(error) = configuration_error() { if let ConfigurationError::ProviderNotAuthenticated(provider) = error { cx.spawn(async move |_, cx| { cx.update(|cx| provider.authenticate(cx))?.await?; anyhow::Ok(()) }) .detach_and_log_err(cx); if configuration_error().is_none() { handle_assist(window, cx); } } else { cx.spawn_in(window, async move |_, cx| { let answer = cx .prompt( gpui::PromptLevel::Warning, &error.to_string(), None, &["Configure", "Cancel"], ) .await .ok(); if let Some(answer) = answer && answer == 0 { cx.update(|window, cx| window.dispatch_action(Box::new(OpenSettings), cx)) .ok(); } anyhow::Ok(()) }) .detach_and_log_err(cx); } } else { handle_assist(window, cx); } } fn codegen_ranges( &mut self, editor: &Entity<Editor>, snapshot: &EditorSnapshot, window: &mut Window, cx: &mut App, ) -> Option<(Vec<Range<Anchor>>, Selection<Point>)> { let (initial_selections, newest_selection) = editor.update(cx, |editor, _| { ( editor.selections.all::<Point>(&snapshot.display_snapshot), editor .selections .newest::<Point>(&snapshot.display_snapshot), ) }); // Check if there is already an inline assistant that contains the // newest selection, if there is, focus it if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let range = assist.range.to_point(&snapshot.buffer_snapshot()); if range.start.row <= newest_selection.start.row && newest_selection.end.row <= range.end.row { self.focus_assist(*assist_id, window, cx); return None; } } } let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in initial_selections { if selection.end == selection.start && let Some(fold) = snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) { selection.start = fold.range().start; selection.end = fold.range().end; if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot().max_row() { let chars = snapshot .buffer_snapshot() .chars_at(Point::new(selection.end.row + 1, 0)); for c in chars { if c == '\n' { break; } if c.is_whitespace() { continue; } if snapshot .language_at(selection.end) .is_some_and(|language| language.config().brackets.is_closing_brace(c)) { selection.end.row += 1; selection.end.column = snapshot .buffer_snapshot() .line_len(MultiBufferRow(selection.end.row)); } } } } else { selection.start.column = 0; // If the selection ends at the start of the line, we don't want to include it. if selection.end.column == 0 && selection.start.row != selection.end.row { selection.end.row -= 1; } selection.end.column = snapshot .buffer_snapshot() .line_len(MultiBufferRow(selection.end.row)); } if let Some(prev_selection) = selections.last_mut() && selection.start <= prev_selection.end { prev_selection.end = selection.end; continue; } let latest_selection = newest_selection.get_or_insert_with(|| selection.clone()); if selection.id > latest_selection.id { *latest_selection = selection.clone(); } selections.push(selection); } let snapshot = &snapshot.buffer_snapshot(); let newest_selection = newest_selection.unwrap(); let mut codegen_ranges = Vec::new(); for (buffer, buffer_range, excerpt_id) in snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| { snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end) })) { let anchor_range = Anchor::range_in_buffer( excerpt_id, buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end), ); codegen_ranges.push(anchor_range); if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() { telemetry::event!( "Assistant Invoked", kind = "inline", phase = "invoked", model = model.model.telemetry_id(), model_provider = model.provider.id().to_string(), language_name = buffer.language().map(|language| language.name().to_proto()) ); report_anthropic_event( &model.model, AnthropicEventData { completion_type: language_model::AnthropicCompletionType::Editor, event: language_model::AnthropicEventType::Invoked, language_name: buffer.language().map(|language| language.name().to_proto()), message_id: None, }, cx, ); } } Some((codegen_ranges, newest_selection)) } fn batch_assist( &mut self, editor: &Entity<Editor>, workspace: WeakEntity<Workspace>, project: WeakEntity<Project>, thread_store: Entity<HistoryStore>, prompt_store: Option<Entity<PromptStore>>, initial_prompt: Option<String>, window: &mut Window, codegen_ranges: &[Range<Anchor>], newest_selection: Option<Selection<Point>>, initial_transaction_id: Option<TransactionId>, cx: &mut App, ) -> Option<InlineAssistId> { let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx)); let assist_group_id = self.next_assist_group_id.post_inc(); let session_id = Uuid::new_v4(); let prompt_buffer = cx.new(|cx| { MultiBuffer::singleton( cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)), cx, ) }); let mut assists = Vec::new(); let mut assist_to_focus = None; for range in codegen_ranges { let assist_id = self.next_assist_id.post_inc(); let codegen = cx.new(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id,