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. Load more
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, )),