2598 lines · 90.5 KB · Rust
RawDownload
use std::cmp::Reverse;use std::ops::Range;use std::path::PathBuf;use std::sync::Arc;use std::sync::atomic::AtomicBool; use crate::ThreadHistory;use acp_thread::MentionUri;use agent_client_protocol as acp;use anyhow::Result;use editor::{    CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,};use futures::FutureExt as _;use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};use gpui::{App, BackgroundExecutor, Entity, SharedString, Task, WeakEntity};use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};use lsp::CompletionContext;use multi_buffer::ToOffset as _;use ordered_float::OrderedFloat;use project::lsp_store::{CompletionDocumentation, SymbolLocation};use project::{    Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, DiagnosticSummary,    PathMatchCandidateSet, Project, ProjectPath, Symbol, WorktreeId,};use prompt_store::{PromptStore, UserPromptId};use rope::Point;use settings::{Settings, TerminalDockPosition};use terminal::terminal_settings::TerminalSettings;use terminal_view::terminal_panel::TerminalPanel;use text::{Anchor, ToOffset as _, ToPoint as _};use ui::IconName;use ui::prelude::*;use util::ResultExt as _;use util::paths::PathStyle;use util::rel_path::RelPath;use util::truncate_and_remove_front;use workspace::Workspace;use workspace::dock::DockPosition; use crate::AgentPanel;use crate::mention_set::MentionSet; #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum PromptContextEntry {    Mode(PromptContextType),    Action(PromptContextAction),} impl PromptContextEntry {    pub fn keyword(&self) -> &'static str {        match self {            Self::Mode(mode) => mode.keyword(),            Self::Action(action) => action.keyword(),        }    }} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum PromptContextType {    File,    Symbol,    Fetch,    Thread,    Rules,    Diagnostics,    BranchDiff,} #[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum PromptContextAction {    AddSelections,} impl PromptContextAction {    pub fn keyword(&self) -> &'static str {        match self {            Self::AddSelections => "selection",        }    }     pub fn label(&self) -> &'static str {        match self {            Self::AddSelections => "Selection",        }    }     pub fn icon(&self) -> IconName {        match self {            Self::AddSelections => IconName::Reader,        }    }} impl TryFrom<&str> for PromptContextType {    type Error = String;     fn try_from(value: &str) -> Result<Self, Self::Error> {        match value {            "file" => Ok(Self::File),            "symbol" => Ok(Self::Symbol),            "fetch" => Ok(Self::Fetch),            "thread" => Ok(Self::Thread),            "rule" => Ok(Self::Rules),            "diagnostics" => Ok(Self::Diagnostics),            "diff" => Ok(Self::BranchDiff),            _ => Err(format!("Invalid context picker mode: {}", value)),        }    }} impl PromptContextType {    pub fn keyword(&self) -> &'static str {        match self {            Self::File => "file",            Self::Symbol => "symbol",            Self::Fetch => "fetch",            Self::Thread => "thread",            Self::Rules => "rule",            Self::Diagnostics => "diagnostics",            Self::BranchDiff => "branch diff",        }    }     pub fn label(&self) -> &'static str {        match self {            Self::File => "Files & Directories",            Self::Symbol => "Symbols",            Self::Fetch => "Fetch",            Self::Thread => "Threads",            Self::Rules => "Rules",            Self::Diagnostics => "Diagnostics",            Self::BranchDiff => "Branch Diff",        }    }     pub fn icon(&self) -> IconName {        match self {            Self::File => IconName::File,            Self::Symbol => IconName::Code,            Self::Fetch => IconName::ToolWeb,            Self::Thread => IconName::Thread,            Self::Rules => IconName::Reader,            Self::Diagnostics => IconName::Warning,            Self::BranchDiff => IconName::GitBranch,        }    }} pub(crate) enum Match {    File(FileMatch),    Symbol(SymbolMatch),    Thread(SessionMatch),    RecentThread(SessionMatch),    Fetch(SharedString),    Rules(RulesContextEntry),    Entry(EntryMatch),    BranchDiff(BranchDiffMatch),} #[derive(Debug, Clone)]pub struct BranchDiffMatch {    pub base_ref: SharedString,} impl Match {    pub fn score(&self) -> f64 {        match self {            Match::File(file) => file.mat.score,            Match::Entry(mode) => mode.mat.as_ref().map(|mat| mat.score).unwrap_or(1.),            Match::Thread(_) => 1.,            Match::RecentThread(_) => 1.,            Match::Symbol(_) => 1.,            Match::Rules(_) => 1.,            Match::Fetch(_) => 1.,            Match::BranchDiff(_) => 1.,        }    }} #[derive(Debug, Clone)]pub struct SessionMatch {    session_id: acp::SessionId,    title: SharedString,} pub struct EntryMatch {    mat: Option<StringMatch>,    entry: PromptContextEntry,} fn session_title(title: Option<SharedString>) -> SharedString {    title        .filter(|title| !title.is_empty())        .unwrap_or_else(|| SharedString::new_static("New Thread"))} #[derive(Debug, Clone)]pub struct RulesContextEntry {    pub prompt_id: UserPromptId,    pub title: SharedString,} #[derive(Debug, Clone)]pub struct AvailableCommand {    pub name: Arc<str>,    pub description: Arc<str>,    pub requires_argument: bool,} pub trait PromptCompletionProviderDelegate: Send + Sync + 'static {    fn supports_context(&self, mode: PromptContextType, cx: &App) -> bool {        self.supported_modes(cx).contains(&mode)    }    fn supported_modes(&self, cx: &App) -> Vec<PromptContextType>;    fn supports_images(&self, cx: &App) -> bool;     fn available_commands(&self, cx: &App) -> Vec<AvailableCommand>;    fn confirm_command(&self, cx: &mut App);} pub struct PromptCompletionProvider<T: PromptCompletionProviderDelegate> {    source: Arc<T>,    editor: WeakEntity<Editor>,    mention_set: Entity<MentionSet>,    history: WeakEntity<ThreadHistory>,    prompt_store: Option<Entity<PromptStore>>,    workspace: WeakEntity<Workspace>,} impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {    pub fn new(        source: T,        editor: WeakEntity<Editor>,        mention_set: Entity<MentionSet>,        history: WeakEntity<ThreadHistory>,        prompt_store: Option<Entity<PromptStore>>,        workspace: WeakEntity<Workspace>,    ) -> Self {        Self {            source: Arc::new(source),            editor,            mention_set,            workspace,            history,            prompt_store,        }    }     fn completion_for_entry(        entry: PromptContextEntry,        source_range: Range<Anchor>,        editor: WeakEntity<Editor>,        mention_set: WeakEntity<MentionSet>,        workspace: &Entity<Workspace>,        cx: &mut App,    ) -> Option<Completion> {        match entry {            PromptContextEntry::Mode(mode) => Some(Completion {                replace_range: source_range,                new_text: format!("@{} ", mode.keyword()),                label: CodeLabel::plain(mode.label().to_string(), None),                icon_path: Some(mode.icon().path().into()),                documentation: None,                source: project::CompletionSource::Custom,                match_start: None,                snippet_deduplication_key: None,                insert_text_mode: None,                // This ensures that when a user accepts this completion, the                // completion menu will still be shown after "@category " is                // inserted                confirm: Some(Arc::new(|_, _, _| true)),            }),            PromptContextEntry::Action(action) => Self::completion_for_action(                action,                source_range,                editor,                mention_set,                workspace,                cx,            ),        }    }     fn completion_for_thread(        session_id: acp::SessionId,        title: Option<SharedString>,        source_range: Range<Anchor>,        recent: bool,        source: Arc<T>,        editor: WeakEntity<Editor>,        mention_set: WeakEntity<MentionSet>,        workspace: Entity<Workspace>,        cx: &mut App,    ) -> Completion {        let title = session_title(title);        let uri = MentionUri::Thread {            id: session_id,            name: title.to_string(),        };         let icon_for_completion = if recent {            IconName::HistoryRerun.path().into()        } else {            uri.icon_path(cx)        };         let new_text = format!("{} ", uri.as_link());         let new_text_len = new_text.len();        Completion {            replace_range: source_range.clone(),            new_text,            label: CodeLabel::plain(title.to_string(), None),            documentation: None,            insert_text_mode: None,            source: project::CompletionSource::Custom,            match_start: None,            snippet_deduplication_key: None,            icon_path: Some(icon_for_completion),            confirm: Some(confirm_completion_callback(                title,                source_range.start,                new_text_len - 1,                uri,                source,                editor,                mention_set,                workspace,            )),        }    }     fn completion_for_rules(        rule: RulesContextEntry,        source_range: Range<Anchor>,        source: Arc<T>,        editor: WeakEntity<Editor>,        mention_set: WeakEntity<MentionSet>,        workspace: Entity<Workspace>,        cx: &mut App,    ) -> Completion {        let uri = MentionUri::Rule {            id: rule.prompt_id.into(),            name: rule.title.to_string(),        };        let new_text = format!("{} ", uri.as_link());        let new_text_len = new_text.len();        let icon_path = uri.icon_path(cx);        Completion {            replace_range: source_range.clone(),            new_text,            label: CodeLabel::plain(rule.title.to_string(), None),            documentation: None,            insert_text_mode: None,            source: project::CompletionSource::Custom,            match_start: None,            snippet_deduplication_key: None,            icon_path: Some(icon_path),            confirm: Some(confirm_completion_callback(                rule.title,                source_range.start,                new_text_len - 1,                uri,                source,                editor,                mention_set,                workspace,            )),        }    }     pub(crate) fn completion_for_path(        project_path: ProjectPath,        path_prefix: &RelPath,        is_recent: bool,        is_directory: bool,        source_range: Range<Anchor>,        source: Arc<T>,        editor: WeakEntity<Editor>,        mention_set: WeakEntity<MentionSet>,        workspace: Entity<Workspace>,        project: Entity<Project>,        label_max_chars: usize,        cx: &mut App,    ) -> Option<Completion> {        let path_style = project.read(cx).path_style(cx);        let (file_name, directory) =            extract_file_name_and_directory(&project_path.path, path_prefix, path_style);         let label = build_code_label_for_path(            &file_name,            directory.as_ref().map(|s| s.as_ref()),            None,            label_max_chars,            cx,        );         let abs_path = project.read(cx).absolute_path(&project_path, cx)?;         let uri = if is_directory {            MentionUri::Directory { abs_path }        } else {            MentionUri::File { abs_path }        };         let crease_icon_path = uri.icon_path(cx);        let completion_icon_path = if is_recent {            IconName::HistoryRerun.path().into()        } else {            crease_icon_path        };         let new_text = format!("{} ", uri.as_link());        let new_text_len = new_text.len();        Some(Completion {            replace_range: source_range.clone(),            new_text,            label,            documentation: None,            source: project::CompletionSource::Custom,            icon_path: Some(completion_icon_path),            match_start: None,            snippet_deduplication_key: None,            insert_text_mode: None,            confirm: Some(confirm_completion_callback(                file_name,                source_range.start,                new_text_len - 1,                uri,                source,                editor,                mention_set,                workspace,            )),        })    }     fn completion_for_symbol(        symbol: Symbol,        source_range: Range<Anchor>,        source: Arc<T>,        editor: WeakEntity<Editor>,        mention_set: WeakEntity<MentionSet>,        workspace: Entity<Workspace>,        label_max_chars: usize,        cx: &mut App,    ) -> Option<Completion> {        let project = workspace.read(cx).project().clone();         let (abs_path, file_name) = match &symbol.path {            SymbolLocation::InProject(project_path) => (                project.read(cx).absolute_path(&project_path, cx)?,                project_path.path.file_name()?.to_string().into(),            ),            SymbolLocation::OutsideProject {                abs_path,                signature: _,            } => (                PathBuf::from(abs_path.as_ref()),                abs_path.file_name().map(|f| f.to_string_lossy())?,            ),        };         let label = build_code_label_for_path(            &symbol.name,            Some(&file_name),            Some(symbol.range.start.0.row + 1),            label_max_chars,            cx,        );         let uri = MentionUri::Symbol {            abs_path,            name: symbol.name.clone(),            line_range: symbol.range.start.0.row..=symbol.range.end.0.row,        };        let new_text = format!("{} ", uri.as_link());        let new_text_len = new_text.len();        let icon_path = uri.icon_path(cx);        Some(Completion {            replace_range: source_range.clone(),            new_text,            label,            documentation: None,            source: project::CompletionSource::Custom,            icon_path: Some(icon_path),            match_start: None,            snippet_deduplication_key: None,            insert_text_mode: None,            confirm: Some(confirm_completion_callback(                symbol.name.into(),                source_range.start,                new_text_len - 1,                uri,                source,                editor,                mention_set,                workspace,            )),

Lines 1–500 of 2598.