diff --git a/crates/ty_python_semantic/src/lib.rs b/crates/ty_python_semantic/src/lib.rs index fe8305e77e..007abd253d 100644 --- a/crates/ty_python_semantic/src/lib.rs +++ b/crates/ty_python_semantic/src/lib.rs @@ -15,7 +15,7 @@ pub use program::{ PythonVersionWithSource, SearchPathSettings, }; pub use python_platform::PythonPlatform; -pub use semantic_model::{Completion, HasType, NameKind, SemanticModel}; +pub use semantic_model::{Completion, CompletionKind, HasType, NameKind, SemanticModel}; pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin}; pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic; diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 61ae1bb0ff..e26992a39a 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -183,6 +183,94 @@ pub struct Completion<'db> { pub builtin: bool, } +impl<'db> Completion<'db> { + /// Returns the "kind" of this completion. + /// + /// This is meant to be a very general classification of this completion. + /// Typically, this is communicated from the LSP server to a client, and + /// the client uses this information to help improve the UX (perhaps by + /// assigning an icon of some kind to the completion). + pub fn kind(&self, db: &'db dyn Db) -> Option { + fn imp<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option { + Some(match ty { + Type::FunctionLiteral(_) + | Type::DataclassDecorator(_) + | Type::WrapperDescriptor(_) + | Type::DataclassTransformer(_) + | Type::Callable(_) => CompletionKind::Function, + Type::BoundMethod(_) | Type::MethodWrapper(_) => CompletionKind::Method, + Type::ModuleLiteral(_) => CompletionKind::Module, + Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => { + CompletionKind::Class + } + // This is a little weird for "struct." I'm mostly interpreting + // "struct" here as a more general "object." ---AG + Type::NominalInstance(_) + | Type::PropertyInstance(_) + | Type::Tuple(_) + | Type::BoundSuper(_) => CompletionKind::Struct, + Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::TypeIs(_) + | Type::StringLiteral(_) + | Type::LiteralString + | Type::BytesLiteral(_) => CompletionKind::Value, + Type::ProtocolInstance(_) => CompletionKind::Interface, + Type::TypeVar(_) => CompletionKind::TypeParameter, + Type::Union(union) => union.elements(db).iter().find_map(|&ty| imp(db, ty))?, + Type::Intersection(intersection) => { + intersection.iter_positive(db).find_map(|ty| imp(db, ty))? + } + Type::Dynamic(_) + | Type::Never + | Type::SpecialForm(_) + | Type::KnownInstance(_) + | Type::AlwaysTruthy + | Type::AlwaysFalsy => return None, + }) + } + imp(db, self.ty) + } +} + +/// The "kind" of a completion. +/// +/// This is taken directly from the LSP completion specification: +/// +/// +/// The idea here is that `Completion::kind` defines the mapping to this from +/// `Type` (and possibly other information), which might be interesting and +/// contentious. Then the outer edges map this to the LSP types, which is +/// expected to be mundane and boring. +#[derive(Clone, Copy, Debug)] +pub enum CompletionKind { + Text, + Method, + Function, + Constructor, + Field, + Variable, + Class, + Interface, + Module, + Property, + Unit, + Value, + Enum, + Keyword, + Snippet, + Color, + File, + Reference, + Folder, + EnumMember, + Constant, + Struct, + Event, + Operator, + TypeParameter, +} + pub trait HasType { /// Returns the inferred type of `self`. /// diff --git a/crates/ty_server/src/server/api/requests/completion.rs b/crates/ty_server/src/server/api/requests/completion.rs index e01424b8a8..53e14574e1 100644 --- a/crates/ty_server/src/server/api/requests/completion.rs +++ b/crates/ty_server/src/server/api/requests/completion.rs @@ -1,10 +1,11 @@ use std::borrow::Cow; use lsp_types::request::Completion; -use lsp_types::{CompletionItem, CompletionParams, CompletionResponse, Url}; +use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Url}; use ruff_db::source::{line_index, source_text}; use ty_ide::completion; use ty_project::ProjectDatabase; +use ty_python_semantic::CompletionKind; use crate::DocumentSnapshot; use crate::document::PositionExt; @@ -55,10 +56,14 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { let items: Vec = completions .into_iter() .enumerate() - .map(|(i, comp)| CompletionItem { - label: comp.name.into(), - sort_text: Some(format!("{i:-max_index_len$}")), - ..Default::default() + .map(|(i, comp)| { + let kind = comp.kind(db).map(ty_kind_to_lsp_kind); + CompletionItem { + label: comp.name.into(), + kind, + sort_text: Some(format!("{i:-max_index_len$}")), + ..Default::default() + } }) .collect(); let response = CompletionResponse::Array(items); @@ -69,3 +74,38 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { impl RetriableRequestHandler for CompletionRequestHandler { const RETRY_ON_CANCELLATION: bool = true; } + +fn ty_kind_to_lsp_kind(kind: CompletionKind) -> CompletionItemKind { + // Gimme my dang globs in tight scopes! + #[allow(clippy::enum_glob_use)] + use self::CompletionKind::*; + + // ref https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind + match kind { + Text => CompletionItemKind::TEXT, + Method => CompletionItemKind::METHOD, + Function => CompletionItemKind::FUNCTION, + Constructor => CompletionItemKind::CONSTRUCTOR, + Field => CompletionItemKind::FIELD, + Variable => CompletionItemKind::VARIABLE, + Class => CompletionItemKind::CLASS, + Interface => CompletionItemKind::INTERFACE, + Module => CompletionItemKind::MODULE, + Property => CompletionItemKind::PROPERTY, + Unit => CompletionItemKind::UNIT, + Value => CompletionItemKind::VALUE, + Enum => CompletionItemKind::ENUM, + Keyword => CompletionItemKind::KEYWORD, + Snippet => CompletionItemKind::SNIPPET, + Color => CompletionItemKind::COLOR, + File => CompletionItemKind::FILE, + Reference => CompletionItemKind::REFERENCE, + Folder => CompletionItemKind::FOLDER, + EnumMember => CompletionItemKind::ENUM_MEMBER, + Constant => CompletionItemKind::CONSTANT, + Struct => CompletionItemKind::STRUCT, + Event => CompletionItemKind::EVENT, + Operator => CompletionItemKind::OPERATOR, + TypeParameter => CompletionItemKind::TYPE_PARAMETER, + } +}