use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};use crate::context_store::ContextStore;use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};use crate::thread_store::ThreadStore;use crate::AssistantPanel;use crate::{ assistant_settings::AssistantSettings, prompts::PromptBuilder, terminal_inline_assistant::TerminalInlineAssistant,};use anyhow::{Context as _, Result};use client::telemetry::Telemetry;use collections::{hash_map, HashMap, HashSet, VecDeque};use editor::{ actions::SelectAll, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,};use fs::Fs;use util::ResultExt; use gpui::{ point, AppContext, FocusableView, Global, HighlightStyle, Model, Subscription, Task, UpdateGlobal, View, ViewContext, WeakModel, WeakView, WindowContext,};use language::{Buffer, Point, Selection, TransactionId};use language_model::LanguageModelRegistry;use language_models::report_assistant_event;use multi_buffer::MultiBufferRow;use parking_lot::Mutex;use project::{CodeAction, ProjectTransaction};use settings::{Settings, SettingsStore};use std::{cmp, mem, ops::Range, rc::Rc, sync::Arc};use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};use terminal_view::{terminal_panel::TerminalPanel, TerminalView};use text::{OffsetRangeExt, ToPoint as _};use ui::prelude::*;use util::RangeExt;use workspace::{dock::Panel, ShowConfiguration};use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; pub fn init( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, cx: &mut AppContext,) { cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry)); cx.observe_new_views(|_workspace: &mut Workspace, cx| { let workspace = cx.view().clone(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, cx) }) }) .detach();} const PROMPT_HISTORY_MAX_LEN: usize = 20; enum InlineAssistTarget { Editor(View<Editor>), Terminal(View<TerminalView>),} pub struct InlineAssistant { next_assist_id: InlineAssistId, next_assist_group_id: InlineAssistGroupId, assists: HashMap<InlineAssistId, InlineAssist>, assists_by_editor: HashMap<WeakView<Editor>, EditorInlineAssists>, assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>, confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>, prompt_history: VecDeque<String>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, fs: Arc<dyn Fs>,} impl Global for InlineAssistant {} impl InlineAssistant { pub fn new( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, ) -> 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, telemetry, fs, } } pub fn register_workspace(&mut self, workspace: &View<Workspace>, cx: &mut WindowContext) { cx.subscribe(workspace, |workspace, event, cx| { Self::update_global(cx, |this, cx| { this.handle_workspace_event(workspace, event, 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 = AssistantSettings::get_global(cx).enabled; terminal_panel.update(cx, |terminal_panel, cx| { terminal_panel.set_assistant_enabled(enabled, cx) }); }) .detach(); } fn handle_workspace_event( &mut self, workspace: View<Workspace>, event: &workspace::Event, cx: &mut WindowContext, ) { 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)) { if 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, cx) } } } } } workspace::Event::ItemAdded { item } => { self.register_workspace_item(&workspace, item.as_ref(), cx); } _ => (), } } fn register_workspace_item( &mut self, workspace: &View<Workspace>, item: &dyn ItemHandle, cx: &mut WindowContext, ) { if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { let thread_store = workspace .read(cx) .panel::<AssistantPanel>(cx) .map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade()); editor.push_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.view().downgrade(), workspace: workspace.downgrade(), thread_store, }), cx, ); }); } } pub fn inline_assist( workspace: &mut Workspace, _action: &zed_actions::InlineAssist, cx: &mut ViewContext<Workspace>, ) { let settings = AssistantSettings::get_global(cx); if !settings.enabled { return; } let Some(inline_assist_target) = Self::resolve_inline_assist_target(workspace, cx) else { return; }; let is_authenticated = || { LanguageModelRegistry::read_global(cx) .active_provider() .map_or(false, |provider| provider.is_authenticated(cx)) }; let thread_store = workspace .panel::<AssistantPanel>(cx) .map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade()); let handle_assist = |cx: &mut ViewContext<Workspace>| match inline_assist_target { InlineAssistTarget::Editor(active_editor) => { InlineAssistant::update_global(cx, |assistant, cx| { assistant.assist(&active_editor, cx.view().downgrade(), thread_store, cx) }) } InlineAssistTarget::Terminal(active_terminal) => { TerminalInlineAssistant::update_global(cx, |assistant, cx| { assistant.assist(&active_terminal, cx.view().downgrade(), thread_store, cx) }) } }; if is_authenticated() { handle_assist(cx); } else { cx.spawn(|_workspace, mut cx| async move { let Some(task) = cx.update(|cx| { LanguageModelRegistry::read_global(cx) .active_provider() .map_or(None, |provider| Some(provider.authenticate(cx))) })? else { let answer = cx .prompt( gpui::PromptLevel::Warning, "No language model provider configured", None, &["Configure", "Cancel"], ) .await .ok(); if let Some(answer) = answer { if answer == 0 { cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration))) .ok(); } } return Ok(()); }; task.await?; anyhow::Ok(()) }) .detach_and_log_err(cx); if is_authenticated() { handle_assist(cx); } } } pub fn assist( &mut self, editor: &View<Editor>, workspace: WeakView<Workspace>, thread_store: Option<WeakModel<ThreadStore>>, cx: &mut WindowContext, ) { let (snapshot, initial_selections) = editor.update(cx, |editor, cx| { ( editor.buffer().read(cx).snapshot(cx), editor.selections.all::<Point>(cx), ) }); let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in initial_selections { if selection.end > selection.start { 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.end.row -= 1; } selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row)); } if let Some(prev_selection) = selections.last_mut() { if 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 newest_selection = newest_selection.unwrap(); let mut codegen_ranges = Vec::new(); for (excerpt_id, buffer, buffer_range) in snapshot.excerpts_in_ranges(selections.iter().map(|selection| { snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end) })) { let start = Anchor { buffer_id: Some(buffer.remote_id()), excerpt_id, text_anchor: buffer.anchor_before(buffer_range.start), }; let end = Anchor { buffer_id: Some(buffer.remote_id()), excerpt_id, text_anchor: buffer.anchor_after(buffer_range.end), }; codegen_ranges.push(start..end); if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { self.telemetry.report_assistant_event(AssistantEvent { conversation_id: None, kind: AssistantKind::Inline, phase: AssistantPhase::Invoked, message_id: None, model: model.telemetry_id(), model_provider: model.provider_id().to_string(), response_latency: None, error_message: None, language_name: buffer.language().map(|language| language.name().to_proto()), }); } } let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| { MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), 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 context_store = cx.new_model(|_cx| ContextStore::new()); let codegen = cx.new_model(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), None, context_store.clone(), self.telemetry.clone(), self.prompt_builder.clone(), cx, ) }); let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new_buffer( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), self.fs.clone(), context_store, workspace.clone(), thread_store.clone(), cx, ) }); if assist_to_focus.is_none() { let focus_assist = if newest_selection.reversed { range.start.to_point(&snapshot) == newest_selection.start } else { range.end.to_point(&snapshot) == newest_selection.end }; if focus_assist { assist_to_focus = Some(assist_id); } } let [prompt_block_id, end_block_id] = self.insert_assist_blocks(editor, &range, &prompt_editor, cx); assists.push(( assist_id, range, prompt_editor, prompt_block_id, end_block_id, )); } let editor_assists = self .assists_by_editor .entry(editor.downgrade()) .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); let mut assist_group = InlineAssistGroup::new(); for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists { let codegen = prompt_editor.read(cx).codegen().clone(); self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, editor, &prompt_editor, prompt_block_id, end_block_id, range, codegen, workspace.clone(), cx, ), ); assist_group.assist_ids.push(assist_id); editor_assists.assist_ids.push(assist_id); } self.assist_groups.insert(assist_group_id, assist_group); if let Some(assist_id) = assist_to_focus { self.focus_assist(assist_id, cx); } } #[allow(clippy::too_many_arguments)] pub fn suggest_assist( &mut self, editor: &View<Editor>, mut range: Range<Anchor>, initial_prompt: String, initial_transaction_id: Option<TransactionId>, focus: bool, workspace: WeakView<Workspace>, thread_store: Option<WeakModel<ThreadStore>>, cx: &mut WindowContext, ) -> InlineAssistId { let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local(&initial_prompt, cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let assist_id = self.next_assist_id.post_inc(); let buffer = editor.read(cx).buffer().clone(); { let snapshot = buffer.read(cx).read(cx); range.start = range.start.bias_left(&snapshot); range.end = range.end.bias_right(&snapshot); } let context_store = cx.new_model(|_cx| ContextStore::new()); let codegen = cx.new_model(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id, context_store.clone(), self.telemetry.clone(), self.prompt_builder.clone(), cx, ) }); let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new_buffer( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), self.fs.clone(), context_store, workspace.clone(), thread_store, cx, ) }); let [prompt_block_id, end_block_id] = self.insert_assist_blocks(editor, &range, &prompt_editor, cx); let editor_assists = self .assists_by_editor .entry(editor.downgrade()) .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); let mut assist_group = InlineAssistGroup::new(); self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, editor, &prompt_editor, prompt_block_id, end_block_id, range, codegen.clone(), workspace.clone(), cx, ),Lines 1–500 of 1717. Load more
use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent}; use crate::context_store::ContextStore; use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent}; use crate::thread_store::ThreadStore; use crate::AssistantPanel; use crate::{ assistant_settings::AssistantSettings, prompts::PromptBuilder, terminal_inline_assistant::TerminalInlineAssistant, }; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ actions::SelectAll, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, }; use fs::Fs; use util::ResultExt; use gpui::{ point, AppContext, FocusableView, Global, HighlightStyle, Model, Subscription, Task, UpdateGlobal, View, ViewContext, WeakModel, WeakView, WindowContext, }; use language::{Buffer, Point, Selection, TransactionId}; use language_model::LanguageModelRegistry; use language_models::report_assistant_event; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, ProjectTransaction}; use settings::{Settings, SettingsStore}; use std::{cmp, mem, ops::Range, rc::Rc, sync::Arc}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use text::{OffsetRangeExt, ToPoint as _}; use ui::prelude::*; use util::RangeExt; use workspace::{dock::Panel, ShowConfiguration}; use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; pub fn init( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, cx: &mut AppContext, ) { cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry)); cx.observe_new_views(|_workspace: &mut Workspace, cx| { let workspace = cx.view().clone(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, cx) }) }) .detach(); } const PROMPT_HISTORY_MAX_LEN: usize = 20; enum InlineAssistTarget { Editor(View<Editor>), Terminal(View<TerminalView>), } pub struct InlineAssistant { next_assist_id: InlineAssistId, next_assist_group_id: InlineAssistGroupId, assists: HashMap<InlineAssistId, InlineAssist>, assists_by_editor: HashMap<WeakView<Editor>, EditorInlineAssists>, assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>, confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>, prompt_history: VecDeque<String>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, fs: Arc<dyn Fs>, } impl Global for InlineAssistant {} impl InlineAssistant { pub fn new( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, ) -> 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, telemetry, fs, } } pub fn register_workspace(&mut self, workspace: &View<Workspace>, cx: &mut WindowContext) { cx.subscribe(workspace, |workspace, event, cx| { Self::update_global(cx, |this, cx| { this.handle_workspace_event(workspace, event, 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 = AssistantSettings::get_global(cx).enabled; terminal_panel.update(cx, |terminal_panel, cx| { terminal_panel.set_assistant_enabled(enabled, cx) }); }) .detach(); } fn handle_workspace_event( &mut self, workspace: View<Workspace>, event: &workspace::Event, cx: &mut WindowContext, ) { 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)) { if 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, cx) } } } } } workspace::Event::ItemAdded { item } => { self.register_workspace_item(&workspace, item.as_ref(), cx); } _ => (), } } fn register_workspace_item( &mut self, workspace: &View<Workspace>, item: &dyn ItemHandle, cx: &mut WindowContext, ) { if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { let thread_store = workspace .read(cx) .panel::<AssistantPanel>(cx) .map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade()); editor.push_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.view().downgrade(), workspace: workspace.downgrade(), thread_store, }), cx, ); }); } } pub fn inline_assist( workspace: &mut Workspace, _action: &zed_actions::InlineAssist, cx: &mut ViewContext<Workspace>, ) { let settings = AssistantSettings::get_global(cx); if !settings.enabled { return; } let Some(inline_assist_target) = Self::resolve_inline_assist_target(workspace, cx) else { return; }; let is_authenticated = || { LanguageModelRegistry::read_global(cx) .active_provider() .map_or(false, |provider| provider.is_authenticated(cx)) }; let thread_store = workspace .panel::<AssistantPanel>(cx) .map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade()); let handle_assist = |cx: &mut ViewContext<Workspace>| match inline_assist_target { InlineAssistTarget::Editor(active_editor) => { InlineAssistant::update_global(cx, |assistant, cx| { assistant.assist(&active_editor, cx.view().downgrade(), thread_store, cx) }) } InlineAssistTarget::Terminal(active_terminal) => { TerminalInlineAssistant::update_global(cx, |assistant, cx| { assistant.assist(&active_terminal, cx.view().downgrade(), thread_store, cx) }) } }; if is_authenticated() { handle_assist(cx); } else { cx.spawn(|_workspace, mut cx| async move { let Some(task) = cx.update(|cx| { LanguageModelRegistry::read_global(cx) .active_provider() .map_or(None, |provider| Some(provider.authenticate(cx))) })? else { let answer = cx .prompt( gpui::PromptLevel::Warning, "No language model provider configured", None, &["Configure", "Cancel"], ) .await .ok(); if let Some(answer) = answer { if answer == 0 { cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration))) .ok(); } } return Ok(()); }; task.await?; anyhow::Ok(()) }) .detach_and_log_err(cx); if is_authenticated() { handle_assist(cx); } } } pub fn assist( &mut self, editor: &View<Editor>, workspace: WeakView<Workspace>, thread_store: Option<WeakModel<ThreadStore>>, cx: &mut WindowContext, ) { let (snapshot, initial_selections) = editor.update(cx, |editor, cx| { ( editor.buffer().read(cx).snapshot(cx), editor.selections.all::<Point>(cx), ) }); let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in initial_selections { if selection.end > selection.start { 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.end.row -= 1; } selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row)); } if let Some(prev_selection) = selections.last_mut() { if 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 newest_selection = newest_selection.unwrap(); let mut codegen_ranges = Vec::new(); for (excerpt_id, buffer, buffer_range) in snapshot.excerpts_in_ranges(selections.iter().map(|selection| { snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end) })) { let start = Anchor { buffer_id: Some(buffer.remote_id()), excerpt_id, text_anchor: buffer.anchor_before(buffer_range.start), }; let end = Anchor { buffer_id: Some(buffer.remote_id()), excerpt_id, text_anchor: buffer.anchor_after(buffer_range.end), }; codegen_ranges.push(start..end); if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { self.telemetry.report_assistant_event(AssistantEvent { conversation_id: None, kind: AssistantKind::Inline, phase: AssistantPhase::Invoked, message_id: None, model: model.telemetry_id(), model_provider: model.provider_id().to_string(), response_latency: None, error_message: None, language_name: buffer.language().map(|language| language.name().to_proto()), }); } } let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| { MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), 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 context_store = cx.new_model(|_cx| ContextStore::new()); let codegen = cx.new_model(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), None, context_store.clone(), self.telemetry.clone(), self.prompt_builder.clone(), cx, ) }); let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new_buffer( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), self.fs.clone(), context_store, workspace.clone(), thread_store.clone(), cx, ) }); if assist_to_focus.is_none() { let focus_assist = if newest_selection.reversed { range.start.to_point(&snapshot) == newest_selection.start } else { range.end.to_point(&snapshot) == newest_selection.end }; if focus_assist { assist_to_focus = Some(assist_id); } } let [prompt_block_id, end_block_id] = self.insert_assist_blocks(editor, &range, &prompt_editor, cx); assists.push(( assist_id, range, prompt_editor, prompt_block_id, end_block_id, )); } let editor_assists = self .assists_by_editor .entry(editor.downgrade()) .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); let mut assist_group = InlineAssistGroup::new(); for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists { let codegen = prompt_editor.read(cx).codegen().clone(); self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, editor, &prompt_editor, prompt_block_id, end_block_id, range, codegen, workspace.clone(), cx, ), ); assist_group.assist_ids.push(assist_id); editor_assists.assist_ids.push(assist_id); } self.assist_groups.insert(assist_group_id, assist_group); if let Some(assist_id) = assist_to_focus { self.focus_assist(assist_id, cx); } } #[allow(clippy::too_many_arguments)] pub fn suggest_assist( &mut self, editor: &View<Editor>, mut range: Range<Anchor>, initial_prompt: String, initial_transaction_id: Option<TransactionId>, focus: bool, workspace: WeakView<Workspace>, thread_store: Option<WeakModel<ThreadStore>>, cx: &mut WindowContext, ) -> InlineAssistId { let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local(&initial_prompt, cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let assist_id = self.next_assist_id.post_inc(); let buffer = editor.read(cx).buffer().clone(); { let snapshot = buffer.read(cx).read(cx); range.start = range.start.bias_left(&snapshot); range.end = range.end.bias_right(&snapshot); } let context_store = cx.new_model(|_cx| ContextStore::new()); let codegen = cx.new_model(|cx| { BufferCodegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id, context_store.clone(), self.telemetry.clone(), self.prompt_builder.clone(), cx, ) }); let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new_buffer( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), self.fs.clone(), context_store, workspace.clone(), thread_store, cx, ) }); let [prompt_block_id, end_block_id] = self.insert_assist_blocks(editor, &range, &prompt_editor, cx); let editor_assists = self .assists_by_editor .entry(editor.downgrade()) .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); let mut assist_group = InlineAssistGroup::new(); self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, editor, &prompt_editor, prompt_block_id, end_block_id, range, codegen.clone(), workspace.clone(), cx, ),