2712 lines · 104.6 KB · Rust
RawDownload
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.