diff --git a/crates/red_knot_ide/src/completion.rs b/crates/red_knot_ide/src/completion.rs new file mode 100644 index 0000000000..275b1fcb1e --- /dev/null +++ b/crates/red_knot_ide/src/completion.rs @@ -0,0 +1,39 @@ +use ruff_db::files::File; +use ruff_db::parsed::parsed_module; +use ruff_python_ast::visitor::source_order::SourceOrderVisitor; +use ruff_python_ast::{AnyNodeRef, Identifier}; +use ruff_text_size::TextSize; + +use crate::Db; + +pub struct Completion { + pub label: String, +} + +pub fn completion(db: &dyn Db, file: File, _offset: TextSize) -> Vec { + let parsed = parsed_module(db.upcast(), file); + identifiers(parsed.syntax().into()) + .into_iter() + .map(|label| Completion { label }) + .collect() +} + +fn identifiers(node: AnyNodeRef) -> Vec { + struct Visitor { + identifiers: Vec, + } + + impl<'a> SourceOrderVisitor<'a> for Visitor { + fn visit_identifier(&mut self, id: &'a Identifier) { + self.identifiers.push(id.id.as_str().to_string()); + } + } + + let mut visitor = Visitor { + identifiers: vec![], + }; + node.visit_source_order(&mut visitor); + visitor.identifiers.sort(); + visitor.identifiers.dedup(); + visitor.identifiers +} diff --git a/crates/red_knot_ide/src/lib.rs b/crates/red_knot_ide/src/lib.rs index 48f1145894..a85c0cd1fe 100644 --- a/crates/red_knot_ide/src/lib.rs +++ b/crates/red_knot_ide/src/lib.rs @@ -1,3 +1,4 @@ +mod completion; mod db; mod find_node; mod goto; @@ -5,6 +6,7 @@ mod hover; mod inlay_hints; mod markup; +pub use completion::completion; pub use db::Db; pub use goto::goto_type_definition; pub use hover::hover; diff --git a/crates/red_knot_server/src/server.rs b/crates/red_knot_server/src/server.rs index e57d1f7bbf..3556fec9d4 100644 --- a/crates/red_knot_server/src/server.rs +++ b/crates/red_knot_server/src/server.rs @@ -227,6 +227,9 @@ impl Server { inlay_hint_provider: Some(lsp_types::OneOf::Right( InlayHintServerCapabilities::Options(InlayHintOptions::default()), )), + completion_provider: Some(lsp_types::CompletionOptions { + ..Default::default() + }), ..Default::default() } } diff --git a/crates/red_knot_server/src/server/api.rs b/crates/red_knot_server/src/server/api.rs index 478666ea61..5bfc835cc5 100644 --- a/crates/red_knot_server/src/server/api.rs +++ b/crates/red_knot_server/src/server/api.rs @@ -36,6 +36,11 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> { request::InlayHintRequestHandler::METHOD => background_request_task::< request::InlayHintRequestHandler, >(req, BackgroundSchedule::Worker), + request::CompletionRequestHandler::METHOD => background_request_task::< + request::CompletionRequestHandler, + >( + req, BackgroundSchedule::LatencySensitive + ), method => { tracing::warn!("Received request {method} which does not have a handler"); diff --git a/crates/red_knot_server/src/server/api/requests.rs b/crates/red_knot_server/src/server/api/requests.rs index b6e907aa0c..bfdec09623 100644 --- a/crates/red_knot_server/src/server/api/requests.rs +++ b/crates/red_knot_server/src/server/api/requests.rs @@ -1,8 +1,10 @@ +mod completion; mod diagnostic; mod goto_type_definition; mod hover; mod inlay_hints; +pub(super) use completion::CompletionRequestHandler; pub(super) use diagnostic::DocumentDiagnosticRequestHandler; pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler; pub(super) use hover::HoverRequestHandler; diff --git a/crates/red_knot_server/src/server/api/requests/completion.rs b/crates/red_knot_server/src/server/api/requests/completion.rs new file mode 100644 index 0000000000..93a1c9d8cc --- /dev/null +++ b/crates/red_knot_server/src/server/api/requests/completion.rs @@ -0,0 +1,58 @@ +use std::borrow::Cow; + +use lsp_types::request::Completion; +use lsp_types::{CompletionItem, CompletionParams, CompletionResponse, Url}; +use red_knot_ide::completion; +use red_knot_project::ProjectDatabase; +use ruff_db::source::{line_index, source_text}; + +use crate::document::PositionExt; +use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; +use crate::server::client::Notifier; +use crate::DocumentSnapshot; + +pub(crate) struct CompletionRequestHandler; + +impl RequestHandler for CompletionRequestHandler { + type RequestType = Completion; +} + +impl BackgroundDocumentRequestHandler for CompletionRequestHandler { + fn document_url(params: &CompletionParams) -> Cow { + Cow::Borrowed(¶ms.text_document_position.text_document.uri) + } + + fn run_with_snapshot( + snapshot: DocumentSnapshot, + db: ProjectDatabase, + _notifier: Notifier, + params: CompletionParams, + ) -> crate::server::Result> { + let Some(file) = snapshot.file(&db) else { + tracing::debug!("Failed to resolve file for {:?}", params); + return Ok(None); + }; + + let source = source_text(&db, file); + let line_index = line_index(&db, file); + let offset = params.text_document_position.position.to_text_size( + &source, + &line_index, + snapshot.encoding(), + ); + let completions = completion(&db, file, offset); + if completions.is_empty() { + return Ok(None); + } + + let items: Vec = completions + .into_iter() + .map(|comp| CompletionItem { + label: comp.label, + ..Default::default() + }) + .collect(); + let response = CompletionResponse::Array(items); + Ok(Some(response)) + } +} diff --git a/crates/red_knot_server/src/server/schedule/task.rs b/crates/red_knot_server/src/server/schedule/task.rs index b9ae6f4a59..d269c1edb5 100644 --- a/crates/red_knot_server/src/server/schedule/task.rs +++ b/crates/red_knot_server/src/server/schedule/task.rs @@ -21,7 +21,6 @@ pub(in crate::server) enum BackgroundSchedule { Fmt, /// The task should be run on the general high-priority background /// thread. Reserved for actions caused by the user typing (e.g.syntax highlighting). - #[expect(dead_code)] LatencySensitive, /// The task should be run on a regular-priority background thread. /// The default for any request that isn't in the critical path of the user typing.