use crate::{ humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist, RequestType,};use anyhow::{anyhow, Context as _, Result};use assistant_settings::AssistantSettings;use client::{telemetry::Telemetry, ErrorExt};use collections::{hash_map, HashMap, HashSet, VecDeque};use editor::{ actions::{MoveDown, MoveUp, SelectAll}, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,};use feature_flags::{ Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedPro,};use fs::Fs;use futures::{ channel::mpsc, future::{BoxFuture, LocalBoxFuture}, join, SinkExt, Stream, StreamExt,};use gpui::{ anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter, FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,};use language::{Buffer, IndentKind, Point, Selection, TransactionId};use language_model::{ LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelTextStream, Role,};use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};use language_models::report_assistant_event;use multi_buffer::MultiBufferRow;use parking_lot::Mutex;use project::{CodeAction, ProjectTransaction};use prompt_library::PromptBuilder;use rope::Rope;use settings::{update_settings_file, Settings, SettingsStore};use smol::future::FutureExt;use std::{ cmp, future::{self, Future}, iter, mem, ops::{Range, RangeInclusive}, pin::Pin, rc::Rc, sync::Arc, task::{self, Poll}, time::{Duration, Instant},};use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};use terminal_view::terminal_panel::TerminalPanel;use text::{OffsetRangeExt, ToPoint as _};use theme::ThemeSettings;use ui::{ prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,};use util::{RangeExt, ResultExt};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(|_, cx| { let workspace = cx.view().clone(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, cx) }); cx.observe_flag::<Assistant2FeatureFlag, _>({ |is_assistant2_enabled, _view, cx| { InlineAssistant::update_global(cx, |inline_assistant, _cx| { inline_assistant.is_assistant2_enabled = is_assistant2_enabled; }); } }) .detach(); }) .detach();} const PROMPT_HISTORY_MAX_LEN: usize = 20; 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>, is_assistant2_enabled: bool,} 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, is_assistant2_enabled: false, } } 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, ) { let is_assistant2_enabled = self.is_assistant2_enabled; if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { if is_assistant2_enabled { editor .remove_code_action_provider(ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), cx); } else { editor.add_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.view().downgrade(), workspace: workspace.downgrade(), }), cx, ); } }); } } pub fn assist( &mut self, editor: &View<Editor>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, initial_prompt: Option<String>, 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| Buffer::local(initial_prompt.unwrap_or_default(), cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, 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_model(|cx| { Codegen::new( editor.read(cx).buffer().clone(), range.clone(), None, 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( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), editor, assistant_panel, workspace.clone(), self.fs.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 { self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, assistant_panel.is_some(), editor, &prompt_editor, prompt_block_id, end_block_id, range, prompt_editor.read(cx).codegen.clone(), 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: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, 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 codegen = cx.new_model(|cx| { Codegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id, 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( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), editor, assistant_panel, workspace.clone(), self.fs.clone(), 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, assistant_panel.is_some(), editor, &prompt_editor, prompt_block_id, end_block_id, range, prompt_editor.read(cx).codegen.clone(), 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 focus { self.focus_assist(assist_id, cx); } assist_id } fn insert_assist_blocks( &self, editor: &View<Editor>, range: &Range<Anchor>, prompt_editor: &View<PromptEditor>, cx: &mut WindowContext, ) -> [CustomBlockId; 2] { let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor .editor .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1 + 2) }); let assist_blocks = vec![ BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Above(range.start), height: prompt_editor_height, render: build_assist_editor_renderer(prompt_editor), priority: 0, }, BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Below(range.end), height: 0, render: Arc::new(|cx| { v_flex() .h_full() .w_full() .border_t_1() .border_color(cx.theme().status().info_border) .into_any_element() }), priority: 0, }, ]; editor.update(cx, |editor, cx| {Lines 1–500 of 4031. Load more
use crate::{ humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist, RequestType, }; use anyhow::{anyhow, Context as _, Result}; use assistant_settings::AssistantSettings; use client::{telemetry::Telemetry, ErrorExt}; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ actions::{MoveDown, MoveUp, SelectAll}, display_map::{ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, }; use feature_flags::{ Assistant2FeatureFlag, FeatureFlagAppExt as _, FeatureFlagViewExt as _, ZedPro, }; use fs::Fs; use futures::{ channel::mpsc, future::{BoxFuture, LocalBoxFuture}, join, SinkExt, Stream, StreamExt, }; use gpui::{ anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter, FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext, }; use language::{Buffer, IndentKind, Point, Selection, TransactionId}; use language_model::{ LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelTextStream, Role, }; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_models::report_assistant_event; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, ProjectTransaction}; use prompt_library::PromptBuilder; use rope::Rope; use settings::{update_settings_file, Settings, SettingsStore}; use smol::future::FutureExt; use std::{ cmp, future::{self, Future}, iter, mem, ops::{Range, RangeInclusive}, pin::Pin, rc::Rc, sync::Arc, task::{self, Poll}, time::{Duration, Instant}, }; use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use terminal_view::terminal_panel::TerminalPanel; use text::{OffsetRangeExt, ToPoint as _}; use theme::ThemeSettings; use ui::{ prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip, }; use util::{RangeExt, ResultExt}; 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(|_, cx| { let workspace = cx.view().clone(); InlineAssistant::update_global(cx, |inline_assistant, cx| { inline_assistant.register_workspace(&workspace, cx) }); cx.observe_flag::<Assistant2FeatureFlag, _>({ |is_assistant2_enabled, _view, cx| { InlineAssistant::update_global(cx, |inline_assistant, _cx| { inline_assistant.is_assistant2_enabled = is_assistant2_enabled; }); } }) .detach(); }) .detach(); } const PROMPT_HISTORY_MAX_LEN: usize = 20; 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>, is_assistant2_enabled: bool, } 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, is_assistant2_enabled: false, } } 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, ) { let is_assistant2_enabled = self.is_assistant2_enabled; if let Some(editor) = item.act_as::<Editor>(cx) { editor.update(cx, |editor, cx| { if is_assistant2_enabled { editor .remove_code_action_provider(ASSISTANT_CODE_ACTION_PROVIDER_ID.into(), cx); } else { editor.add_code_action_provider( Rc::new(AssistantCodeActionProvider { editor: cx.view().downgrade(), workspace: workspace.downgrade(), }), cx, ); } }); } } pub fn assist( &mut self, editor: &View<Editor>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, initial_prompt: Option<String>, 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| Buffer::local(initial_prompt.unwrap_or_default(), cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, 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_model(|cx| { Codegen::new( editor.read(cx).buffer().clone(), range.clone(), None, 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( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), editor, assistant_panel, workspace.clone(), self.fs.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 { self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, assistant_panel.is_some(), editor, &prompt_editor, prompt_block_id, end_block_id, range, prompt_editor.read(cx).codegen.clone(), 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: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, 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 codegen = cx.new_model(|cx| { Codegen::new( editor.read(cx).buffer().clone(), range.clone(), initial_transaction_id, 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( assist_id, gutter_dimensions.clone(), self.prompt_history.clone(), prompt_buffer.clone(), codegen.clone(), editor, assistant_panel, workspace.clone(), self.fs.clone(), 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, assistant_panel.is_some(), editor, &prompt_editor, prompt_block_id, end_block_id, range, prompt_editor.read(cx).codegen.clone(), 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 focus { self.focus_assist(assist_id, cx); } assist_id } fn insert_assist_blocks( &self, editor: &View<Editor>, range: &Range<Anchor>, prompt_editor: &View<PromptEditor>, cx: &mut WindowContext, ) -> [CustomBlockId; 2] { let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor .editor .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1 + 2) }); let assist_blocks = vec![ BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Above(range.start), height: prompt_editor_height, render: build_assist_editor_renderer(prompt_editor), priority: 0, }, BlockProperties { style: BlockStyle::Sticky, placement: BlockPlacement::Below(range.end), height: 0, render: Arc::new(|cx| { v_flex() .h_full() .w_full() .border_t_1() .border_color(cx.theme().status().info_border) .into_any_element() }), priority: 0, }, ]; editor.update(cx, |editor, cx| {