use crate::{ assistant_settings::AssistantSettings, humanize_token_count, prompts::generate_content_prompt, AssistantPanel, AssistantPanelEvent, CompletionProvider, Hunk, LanguageModelRequest, LanguageModelRequestMessage, Role, StreamingDiff,};use anyhow::{anyhow, Context as _, Result};use client::telemetry::Telemetry;use collections::{hash_map, HashMap, HashSet, VecDeque};use editor::{ actions::{MoveDown, MoveUp, SelectAll}, display_map::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,};use fs::Fs;use futures::{channel::mpsc, SinkExt, Stream, StreamExt};use gpui::{ point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WhiteSpace, WindowContext,};use language::{Buffer, Point, Selection, TransactionId};use multi_buffer::MultiBufferRow;use parking_lot::Mutex;use rope::Rope;use settings::{update_settings_file, Settings};use similar::TextDiff;use std::{ cmp, mem, ops::{Range, RangeInclusive}, pin::Pin, sync::Arc, task::{self, Poll}, time::{Duration, Instant},};use theme::ThemeSettings;use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};use util::RangeExt;use workspace::{notifications::NotificationId, Toast, Workspace}; pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) { cx.set_global(InlineAssistant::new(fs, telemetry));} 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>, prompt_history: VecDeque<String>, telemetry: Option<Arc<Telemetry>>, fs: Arc<dyn Fs>,} impl Global for InlineAssistant {} impl InlineAssistant { pub fn new(fs: Arc<dyn Fs>, 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(), prompt_history: VecDeque::default(), telemetry: Some(telemetry), fs, } } pub fn assist( &mut self, editor: &View<Editor>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, cx: &mut WindowContext, ) { let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in editor.read(cx).selections.all::<Point>(cx) { 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); } let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local("", cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let mut assists = Vec::new(); let mut assist_blocks = 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(), self.telemetry.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); } } assist_blocks.push(BlockProperties { style: BlockStyle::Sticky, position: range.start, height: prompt_editor.read(cx).height_in_lines, render: build_assist_editor_renderer(&prompt_editor), disposition: BlockDisposition::Above, }); assist_blocks.push(BlockProperties { style: BlockStyle::Sticky, position: range.end, height: 1, render: Box::new(|cx| { v_flex() .h_full() .w_full() .border_t_1() .border_color(cx.theme().status().info_border) .into_any_element() }), disposition: BlockDisposition::Below, }); assists.push((assist_id, prompt_editor)); } let assist_block_ids = editor.update(cx, |editor, cx| { editor.insert_blocks(assist_blocks, None, cx) }); 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, prompt_editor), block_ids) in assists.into_iter().zip(assist_block_ids.chunks_exact(2)) { self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, assistant_panel.is_some(), editor, &prompt_editor, block_ids[0], block_ids[1], 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); } } fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { let assist = &self.assists[&assist_id]; let Some(decorations) = assist.decorations.as_ref() else { return; }; let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); let editor_assists = self.assists_by_editor.get_mut(&assist.editor).unwrap(); assist_group.active_assist_id = Some(assist_id); if assist_group.linked { for assist_id in &assist_group.assist_ids { if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { decorations.prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor.set_show_cursor_when_unfocused(true, cx) }); } } } assist .editor .update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.); let prompt_row = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32; if (scroll_top..scroll_bottom).contains(&prompt_row) { editor_assists.scroll_lock = Some(InlineAssistScrollLock { assist_id, distance_from_top: prompt_row - scroll_top, }); } else { editor_assists.scroll_lock = None; } }) .ok(); } fn handle_prompt_editor_focus_out( &mut self, assist_id: InlineAssistId, cx: &mut WindowContext, ) { let assist = &self.assists[&assist_id]; let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); if assist_group.active_assist_id == Some(assist_id) { assist_group.active_assist_id = None; if assist_group.linked { for assist_id in &assist_group.assist_ids { if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { decorations.prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor.set_show_cursor_when_unfocused(false, cx) }); } } } } } fn handle_prompt_editor_event( &mut self, prompt_editor: View<PromptEditor>, event: &PromptEditorEvent, cx: &mut WindowContext, ) { let assist_id = prompt_editor.read(cx).id; match event { PromptEditorEvent::StartRequested => { self.start_assist(assist_id, cx); } PromptEditorEvent::StopRequested => { self.stop_assist(assist_id, cx); } PromptEditorEvent::ConfirmRequested => { self.finish_assist(assist_id, false, cx); } PromptEditorEvent::CancelRequested => { self.finish_assist(assist_id, true, cx); } PromptEditorEvent::DismissRequested => { self.dismiss_assist(assist_id, cx); } PromptEditorEvent::Resized { height_in_lines } => { self.resize_assist(assist_id, *height_in_lines, cx); } } } fn handle_editor_newline(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let editor = editor.read(cx); if editor.selections.count() == 1 { let selection = editor.selections.newest::<usize>(cx); let buffer = editor.buffer().read(cx).snapshot(cx); for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let assist_range = assist.codegen.read(cx).range.to_offset(&buffer); if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) { if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) { self.dismiss_assist(*assist_id, cx); } else { self.finish_assist(*assist_id, false, cx); } return; } } } cx.propagate(); } fn handle_editor_cancel(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let editor = editor.read(cx); if editor.selections.count() == 1 { let selection = editor.selections.newest::<usize>(cx); let buffer = editor.buffer().read(cx).snapshot(cx); for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let assist_range = assist.codegen.read(cx).range.to_offset(&buffer); if assist.decorations.is_some() && assist_range.contains(&selection.start) && assist_range.contains(&selection.end) { self.focus_assist(*assist_id, cx); return; } } } cx.propagate(); } fn handle_editor_change(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() else { return; }; let assist = &self.assists[&scroll_lock.assist_id]; let Some(decorations) = assist.decorations.as_ref() else { return; }; editor.update(cx, |editor, cx| { let scroll_position = editor.scroll_position(cx); let target_scroll_top = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32 - scroll_lock.distance_from_top; if target_scroll_top != scroll_position.y { editor.set_scroll_position(point(scroll_position.x, target_scroll_top), cx); } }); } fn handle_editor_event( &mut self, editor: View<Editor>, event: &EditorEvent, cx: &mut WindowContext, ) { let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) else { return; }; match event { EditorEvent::Saved => { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let CodegenStatus::Done = &assist.codegen.read(cx).status { self.finish_assist(assist_id, false, cx) } } } EditorEvent::Edited { transaction_id } => { let buffer = editor.read(cx).buffer().read(cx); let edited_ranges = buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx); let snapshot = buffer.snapshot(cx); for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if matches!( assist.codegen.read(cx).status, CodegenStatus::Error(_) | CodegenStatus::Done ) { let assist_range = assist.codegen.read(cx).range.to_offset(&snapshot); if edited_ranges .iter() .any(|range| range.overlaps(&assist_range)) { self.finish_assist(assist_id, false, cx); } } } } EditorEvent::ScrollPositionChanged { .. } => { if let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() { let assist = &self.assists[&scroll_lock.assist_id]; if let Some(decorations) = assist.decorations.as_ref() { let distance_from_top = editor.update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; let prompt_row = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32; prompt_row - scroll_top }); if distance_from_top != scroll_lock.distance_from_top { editor_assists.scroll_lock = None; } } } } EditorEvent::SelectionsChanged { .. } => { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let Some(decorations) = assist.decorations.as_ref() { if decorations.prompt_editor.focus_handle(cx).is_focused(cx) { return; } } } editor_assists.scroll_lock = None; } _ => {} } } fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) { if let Some(assist) = self.assists.get(&assist_id) { let assist_group_id = assist.group_id; if self.assist_groups[&assist_group_id].linked { for assist_id in self.unlink_assist_group(assist_group_id, cx) { self.finish_assist(assist_id, undo, cx); } return; } } self.dismiss_assist(assist_id, cx); if let Some(assist) = self.assists.remove(&assist_id) { if let hash_map::Entry::Occupied(mut entry) = self.assist_groups.entry(assist.group_id) { entry.get_mut().assist_ids.retain(|id| *id != assist_id); if entry.get().assist_ids.is_empty() {Lines 1–500 of 2712. Load more
use crate::{ assistant_settings::AssistantSettings, humanize_token_count, prompts::generate_content_prompt, AssistantPanel, AssistantPanelEvent, CompletionProvider, Hunk, LanguageModelRequest, LanguageModelRequestMessage, Role, StreamingDiff, }; use anyhow::{anyhow, Context as _, Result}; use client::telemetry::Telemetry; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ actions::{MoveDown, MoveUp, SelectAll}, display_map::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, ToDisplayPoint, }, Anchor, AnchorRangeExt, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; use fs::Fs; use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{ point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WhiteSpace, WindowContext, }; use language::{Buffer, Point, Selection, TransactionId}; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use rope::Rope; use settings::{update_settings_file, Settings}; use similar::TextDiff; use std::{ cmp, mem, ops::{Range, RangeInclusive}, pin::Pin, sync::Arc, task::{self, Poll}, time::{Duration, Instant}, }; use theme::ThemeSettings; use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip}; use util::RangeExt; use workspace::{notifications::NotificationId, Toast, Workspace}; pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) { cx.set_global(InlineAssistant::new(fs, telemetry)); } 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>, prompt_history: VecDeque<String>, telemetry: Option<Arc<Telemetry>>, fs: Arc<dyn Fs>, } impl Global for InlineAssistant {} impl InlineAssistant { pub fn new(fs: Arc<dyn Fs>, 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(), prompt_history: VecDeque::default(), telemetry: Some(telemetry), fs, } } pub fn assist( &mut self, editor: &View<Editor>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, cx: &mut WindowContext, ) { let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); let mut selections = Vec::<Selection<Point>>::new(); let mut newest_selection = None; for mut selection in editor.read(cx).selections.all::<Point>(cx) { 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); } let assist_group_id = self.next_assist_group_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local("", cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let mut assists = Vec::new(); let mut assist_blocks = 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(), self.telemetry.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); } } assist_blocks.push(BlockProperties { style: BlockStyle::Sticky, position: range.start, height: prompt_editor.read(cx).height_in_lines, render: build_assist_editor_renderer(&prompt_editor), disposition: BlockDisposition::Above, }); assist_blocks.push(BlockProperties { style: BlockStyle::Sticky, position: range.end, height: 1, render: Box::new(|cx| { v_flex() .h_full() .w_full() .border_t_1() .border_color(cx.theme().status().info_border) .into_any_element() }), disposition: BlockDisposition::Below, }); assists.push((assist_id, prompt_editor)); } let assist_block_ids = editor.update(cx, |editor, cx| { editor.insert_blocks(assist_blocks, None, cx) }); 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, prompt_editor), block_ids) in assists.into_iter().zip(assist_block_ids.chunks_exact(2)) { self.assists.insert( assist_id, InlineAssist::new( assist_id, assist_group_id, assistant_panel.is_some(), editor, &prompt_editor, block_ids[0], block_ids[1], 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); } } fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { let assist = &self.assists[&assist_id]; let Some(decorations) = assist.decorations.as_ref() else { return; }; let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); let editor_assists = self.assists_by_editor.get_mut(&assist.editor).unwrap(); assist_group.active_assist_id = Some(assist_id); if assist_group.linked { for assist_id in &assist_group.assist_ids { if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { decorations.prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor.set_show_cursor_when_unfocused(true, cx) }); } } } assist .editor .update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.); let prompt_row = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32; if (scroll_top..scroll_bottom).contains(&prompt_row) { editor_assists.scroll_lock = Some(InlineAssistScrollLock { assist_id, distance_from_top: prompt_row - scroll_top, }); } else { editor_assists.scroll_lock = None; } }) .ok(); } fn handle_prompt_editor_focus_out( &mut self, assist_id: InlineAssistId, cx: &mut WindowContext, ) { let assist = &self.assists[&assist_id]; let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); if assist_group.active_assist_id == Some(assist_id) { assist_group.active_assist_id = None; if assist_group.linked { for assist_id in &assist_group.assist_ids { if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { decorations.prompt_editor.update(cx, |prompt_editor, cx| { prompt_editor.set_show_cursor_when_unfocused(false, cx) }); } } } } } fn handle_prompt_editor_event( &mut self, prompt_editor: View<PromptEditor>, event: &PromptEditorEvent, cx: &mut WindowContext, ) { let assist_id = prompt_editor.read(cx).id; match event { PromptEditorEvent::StartRequested => { self.start_assist(assist_id, cx); } PromptEditorEvent::StopRequested => { self.stop_assist(assist_id, cx); } PromptEditorEvent::ConfirmRequested => { self.finish_assist(assist_id, false, cx); } PromptEditorEvent::CancelRequested => { self.finish_assist(assist_id, true, cx); } PromptEditorEvent::DismissRequested => { self.dismiss_assist(assist_id, cx); } PromptEditorEvent::Resized { height_in_lines } => { self.resize_assist(assist_id, *height_in_lines, cx); } } } fn handle_editor_newline(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let editor = editor.read(cx); if editor.selections.count() == 1 { let selection = editor.selections.newest::<usize>(cx); let buffer = editor.buffer().read(cx).snapshot(cx); for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let assist_range = assist.codegen.read(cx).range.to_offset(&buffer); if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) { if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) { self.dismiss_assist(*assist_id, cx); } else { self.finish_assist(*assist_id, false, cx); } return; } } } cx.propagate(); } fn handle_editor_cancel(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let editor = editor.read(cx); if editor.selections.count() == 1 { let selection = editor.selections.newest::<usize>(cx); let buffer = editor.buffer().read(cx).snapshot(cx); for assist_id in &editor_assists.assist_ids { let assist = &self.assists[assist_id]; let assist_range = assist.codegen.read(cx).range.to_offset(&buffer); if assist.decorations.is_some() && assist_range.contains(&selection.start) && assist_range.contains(&selection.end) { self.focus_assist(*assist_id, cx); return; } } } cx.propagate(); } fn handle_editor_change(&mut self, editor: View<Editor>, cx: &mut WindowContext) { let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { return; }; let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() else { return; }; let assist = &self.assists[&scroll_lock.assist_id]; let Some(decorations) = assist.decorations.as_ref() else { return; }; editor.update(cx, |editor, cx| { let scroll_position = editor.scroll_position(cx); let target_scroll_top = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32 - scroll_lock.distance_from_top; if target_scroll_top != scroll_position.y { editor.set_scroll_position(point(scroll_position.x, target_scroll_top), cx); } }); } fn handle_editor_event( &mut self, editor: View<Editor>, event: &EditorEvent, cx: &mut WindowContext, ) { let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) else { return; }; match event { EditorEvent::Saved => { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let CodegenStatus::Done = &assist.codegen.read(cx).status { self.finish_assist(assist_id, false, cx) } } } EditorEvent::Edited { transaction_id } => { let buffer = editor.read(cx).buffer().read(cx); let edited_ranges = buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx); let snapshot = buffer.snapshot(cx); for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if matches!( assist.codegen.read(cx).status, CodegenStatus::Error(_) | CodegenStatus::Done ) { let assist_range = assist.codegen.read(cx).range.to_offset(&snapshot); if edited_ranges .iter() .any(|range| range.overlaps(&assist_range)) { self.finish_assist(assist_id, false, cx); } } } } EditorEvent::ScrollPositionChanged { .. } => { if let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() { let assist = &self.assists[&scroll_lock.assist_id]; if let Some(decorations) = assist.decorations.as_ref() { let distance_from_top = editor.update(cx, |editor, cx| { let scroll_top = editor.scroll_position(cx).y; let prompt_row = editor .row_for_block(decorations.prompt_block_id, cx) .unwrap() .0 as f32; prompt_row - scroll_top }); if distance_from_top != scroll_lock.distance_from_top { editor_assists.scroll_lock = None; } } } } EditorEvent::SelectionsChanged { .. } => { for assist_id in editor_assists.assist_ids.clone() { let assist = &self.assists[&assist_id]; if let Some(decorations) = assist.decorations.as_ref() { if decorations.prompt_editor.focus_handle(cx).is_focused(cx) { return; } } } editor_assists.scroll_lock = None; } _ => {} } } fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) { if let Some(assist) = self.assists.get(&assist_id) { let assist_group_id = assist.group_id; if self.assist_groups[&assist_group_id].linked { for assist_id in self.unlink_assist_group(assist_group_id, cx) { self.finish_assist(assist_id, undo, cx); } return; } } self.dismiss_assist(assist_id, cx); if let Some(assist) = self.assists.remove(&assist_id) { if let hash_map::Entry::Occupied(mut entry) = self.assist_groups.entry(assist.group_id) { entry.get_mut().assist_ids.retain(|id| *id != assist_id); if entry.get().assist_ids.is_empty() {