1949 lines · 73.0 KB · Rust
RawDownload
use std::cmp;use std::mem;use std::ops::Range;use std::rc::Rc;use std::sync::Arc; use crate::{    AgentPanel,    buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent},    inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent},    terminal_inline_assistant::TerminalInlineAssistant,};use agent::{    context_store::ContextStore,    thread_store::{TextThreadStore, ThreadStore},};use agent_settings::AgentSettings;use anyhow::{Context as _, Result};use client::{DisableAiSettings, telemetry::Telemetry};use collections::{HashMap, HashSet, VecDeque, hash_map};use editor::SelectionEffects;use editor::{    Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,    MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,    actions::SelectAll,    display_map::{        BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,        RenderBlock, ToDisplayPoint,    },};use fs::Fs;use gpui::{    App, Context, Entity, Focusable, Global, HighlightStyle, Subscription, Task, UpdateGlobal,    WeakEntity, Window, point,};use language::{Buffer, Point, Selection, TransactionId};use language_model::{    ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,};use multi_buffer::MultiBufferRow;use parking_lot::Mutex;use project::{CodeAction, LspAction, Project, ProjectTransaction};use prompt_store::{PromptBuilder, PromptStore};use settings::{Settings, SettingsStore};use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};use terminal_view::{TerminalView, terminal_panel::TerminalPanel};use text::{OffsetRangeExt, ToPoint as _};use ui::prelude::*;use util::{RangeExt, ResultExt, maybe};use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};use zed_actions::agent::OpenSettings; pub fn init(    fs: Arc<dyn Fs>,    prompt_builder: Arc<PromptBuilder>,    telemetry: Arc<Telemetry>,    cx: &mut App,) {    cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry));     cx.observe_global::<SettingsStore>(|cx| {        if DisableAiSettings::get_global(cx).disable_ai {            // Hide any active inline assist UI when AI is disabled            InlineAssistant::update_global(cx, |assistant, cx| {                assistant.cancel_all_active_completions(cx);            });        }    })    .detach();     cx.observe_new(|_workspace: &mut Workspace, window, cx| {        let Some(window) = window else {            return;        };        let workspace = cx.entity().clone();        InlineAssistant::update_global(cx, |inline_assistant, cx| {            inline_assistant.register_workspace(&workspace, window, cx)        });    })    .detach();} const PROMPT_HISTORY_MAX_LEN: usize = 20; enum InlineAssistTarget {    Editor(Entity<Editor>),    Terminal(Entity<TerminalView>),} pub struct InlineAssistant {    next_assist_id: InlineAssistId,    next_assist_group_id: InlineAssistGroupId,    assists: HashMap<InlineAssistId, InlineAssist>,    assists_by_editor: HashMap<WeakEntity<Editor>, EditorInlineAssists>,    assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,    confirmed_assists: HashMap<InlineAssistId, Entity<CodegenAlternative>>,    prompt_history: VecDeque<String>,    prompt_builder: Arc<PromptBuilder>,    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: &Entity<Workspace>,        window: &mut Window,        cx: &mut App,    ) {        window            .subscribe(workspace, cx, |workspace, event, window, cx| {                Self::update_global(cx, |this, cx| {                    this.handle_workspace_event(workspace, event, window, cx)                });            })            .detach();         let workspace = workspace.downgrade();        cx.observe_global::<SettingsStore>(move |cx| {            let Some(workspace) = workspace.upgrade() else {                return;            };            let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {                return;            };            let enabled = AgentSettings::get_global(cx).enabled;            terminal_panel.update(cx, |terminal_panel, cx| {                terminal_panel.set_assistant_enabled(enabled, cx)            });        })        .detach();    }     /// Hides all active inline assists when AI is disabled    pub fn cancel_all_active_completions(&mut self, cx: &mut App) {        // Cancel all active completions in editors        for (editor_handle, _) in self.assists_by_editor.iter() {            if let Some(editor) = editor_handle.upgrade() {                let windows = cx.windows();                if !windows.is_empty() {                    let window = windows[0];                    let _ = window.update(cx, |_, window, cx| {                        editor.update(cx, |editor, cx| {                            if editor.has_active_inline_completion() {                                editor.cancel(&Default::default(), window, cx);                            }                        });                    });                }            }        }    }     fn handle_workspace_event(        &mut self,        workspace: Entity<Workspace>,        event: &workspace::Event,        window: &mut Window,        cx: &mut App,    ) {        match event {            workspace::Event::UserSavedItem { item, .. } => {                // When the user manually saves an editor, automatically accepts all finished transformations.                if let Some(editor) = item.upgrade().and_then(|item| item.act_as::<Editor>(cx)) {                    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, window, cx)                            }                        }                    }                }            }            workspace::Event::ItemAdded { item } => {                self.register_workspace_item(&workspace, item.as_ref(), window, cx);            }            _ => (),        }    }     fn register_workspace_item(        &mut self,        workspace: &Entity<Workspace>,        item: &dyn ItemHandle,        window: &mut Window,        cx: &mut App,    ) {        let is_assistant2_enabled = !DisableAiSettings::get_global(cx).disable_ai;         if let Some(editor) = item.act_as::<Editor>(cx) {            editor.update(cx, |editor, cx| {                if is_assistant2_enabled {                    let panel = workspace.read(cx).panel::<AgentPanel>(cx);                    let thread_store = panel                        .as_ref()                        .map(|agent_panel| agent_panel.read(cx).thread_store().downgrade());                    let text_thread_store = panel                        .map(|agent_panel| agent_panel.read(cx).text_thread_store().downgrade());                     editor.add_code_action_provider(                        Rc::new(AssistantCodeActionProvider {                            editor: cx.entity().downgrade(),                            workspace: workspace.downgrade(),                            thread_store,                            text_thread_store,                        }),                        window,                        cx,                    );                     if DisableAiSettings::get_global(cx).disable_ai {                        // Cancel any active completions                        if editor.has_active_inline_completion() {                            editor.cancel(&Default::default(), window, cx);                        }                    }                     // Remove the Assistant1 code action provider, as it still might be registered.                    editor.remove_code_action_provider("assistant".into(), window, cx);                } else {                    editor.remove_code_action_provider(                        ASSISTANT_CODE_ACTION_PROVIDER_ID.into(),                        window,                        cx,                    );                }            });        }    }     pub fn inline_assist(        workspace: &mut Workspace,        action: &zed_actions::assistant::InlineAssist,        window: &mut Window,        cx: &mut Context<Workspace>,    ) {        let settings = AgentSettings::get_global(cx);        if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {            return;        }         let Some(inline_assist_target) = Self::resolve_inline_assist_target(            workspace,            workspace.panel::<AgentPanel>(cx),            window,            cx,        ) else {            return;        };         let configuration_error = || {            let model_registry = LanguageModelRegistry::read_global(cx);            model_registry.configuration_error(model_registry.inline_assistant_model(), cx)        };         let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else {            return;        };        let agent_panel = agent_panel.read(cx);         let prompt_store = agent_panel.prompt_store().as_ref().cloned();        let thread_store = Some(agent_panel.thread_store().downgrade());        let text_thread_store = Some(agent_panel.text_thread_store().downgrade());        let context_store = agent_panel.inline_assist_context_store().clone();         let handle_assist =            |window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {                InlineAssistTarget::Editor(active_editor) => {                    InlineAssistant::update_global(cx, |assistant, cx| {                        assistant.assist(                            &active_editor,                            cx.entity().downgrade(),                            context_store,                            workspace.project().downgrade(),                            prompt_store,                            thread_store,                            text_thread_store,                            action.prompt.clone(),                            window,                            cx,                        )                    })                }                InlineAssistTarget::Terminal(active_terminal) => {                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {                        assistant.assist(                            &active_terminal,                            cx.entity().downgrade(),                            workspace.project().downgrade(),                            prompt_store,                            thread_store,                            text_thread_store,                            action.prompt.clone(),                            window,                            cx,                        )                    })                }            };         if let Some(error) = configuration_error() {            if let ConfigurationError::ProviderNotAuthenticated(provider) = error {                cx.spawn(async move |_, cx| {                    cx.update(|cx| provider.authenticate(cx))?.await?;                    anyhow::Ok(())                })                .detach_and_log_err(cx);                 if configuration_error().is_none() {                    handle_assist(window, cx);                }            } else {                cx.spawn_in(window, async move |_, cx| {                    let answer = cx                        .prompt(                            gpui::PromptLevel::Warning,                            &error.to_string(),                            None,                            &["Configure", "Cancel"],                        )                        .await                        .ok();                    if let Some(answer) = answer {                        if answer == 0 {                            cx.update(|window, cx| {                                window.dispatch_action(Box::new(OpenSettings), cx)                            })                            .ok();                        }                    }                    anyhow::Ok(())                })                .detach_and_log_err(cx);            }        } else {            handle_assist(window, cx);        }    }     pub fn assist(        &mut self,        editor: &Entity<Editor>,        workspace: WeakEntity<Workspace>,        context_store: Entity<ContextStore>,        project: WeakEntity<Project>,        prompt_store: Option<Entity<PromptStore>>,        thread_store: Option<WeakEntity<ThreadStore>>,        text_thread_store: Option<WeakEntity<TextThreadStore>>,        initial_prompt: Option<String>,        window: &mut Window,        cx: &mut App,    ) {        let (snapshot, initial_selections, newest_selection) = editor.update(cx, |editor, cx| {            let selections = editor.selections.all::<Point>(cx);            let newest_selection = editor.selections.newest::<Point>(cx);            (editor.snapshot(window, cx), selections, newest_selection)        });         // Check if there is already an inline assistant that contains the        // newest selection, if there is, focus it        if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) {            for assist_id in &editor_assists.assist_ids {                let assist = &self.assists[assist_id];                let range = assist.range.to_point(&snapshot.buffer_snapshot);                if range.start.row <= newest_selection.start.row                    && newest_selection.end.row <= range.end.row                {                    self.focus_assist(*assist_id, window, cx);                    return;                }            }        }         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                    .buffer_snapshot                    .line_len(MultiBufferRow(selection.end.row));            } else if let Some(fold) =                snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))            {                selection.start = fold.range().start;                selection.end = fold.range().end;                if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {                    let chars = snapshot                        .buffer_snapshot                        .chars_at(Point::new(selection.end.row + 1, 0));                     for c in chars {                        if c == '\n' {                            break;                        }                        if c.is_whitespace() {                            continue;                        }                        if snapshot                            .language_at(selection.end)                            .is_some_and(|language| language.config().brackets.is_closing_brace(c))                        {                            selection.end.row += 1;                            selection.end.column = snapshot                                .buffer_snapshot                                .line_len(MultiBufferRow(selection.end.row));                        }                    }                }            }             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 snapshot = &snapshot.buffer_snapshot;        let newest_selection = newest_selection.unwrap();         let mut codegen_ranges = Vec::new();        for (buffer, buffer_range, excerpt_id) in            snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {                snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)            }))        {            let anchor_range = Anchor::range_in_buffer(                excerpt_id,                buffer.remote_id(),                buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end),            );             codegen_ranges.push(anchor_range);             if let Some(model) = LanguageModelRegistry::read_global(cx).inline_assistant_model() {                self.telemetry.report_assistant_event(AssistantEventData {                    conversation_id: None,                    kind: AssistantKind::Inline,                    phase: AssistantPhase::Invoked,                    message_id: None,                    model: 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(|cx| {            MultiBuffer::singleton(                cx.new(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)),                cx,            )        });         let mut assists = Vec::new();        let mut assist_to_focus = None;        for range in codegen_ranges {            let assist_id = self.next_assist_id.post_inc();            let codegen = cx.new(|cx| {                BufferCodegen::new(                    editor.read(cx).buffer().clone(),                    range.clone(),                    None,                    context_store.clone(),

Lines 1–500 of 1949.