Skip to content

Commit b7ce694

Browse files
committed
red_knot_server: add auto-completion MVP
This PR does the wiring necessary to respond to completion requests from LSP clients. As far as the actual completion results go, they are nearly about the dumbest and simplest thing we can do: we simply return a de-duplicated list of all identifiers from the current module.
1 parent 163d526 commit b7ce694

File tree

7 files changed

+109
-1
lines changed

7 files changed

+109
-1
lines changed

crates/red_knot_ide/src/completion.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use ruff_db::files::File;
2+
use ruff_db::parsed::parsed_module;
3+
use ruff_python_ast::visitor::source_order::SourceOrderVisitor;
4+
use ruff_python_ast::{AnyNodeRef, Identifier};
5+
use ruff_text_size::TextSize;
6+
7+
use crate::Db;
8+
9+
pub struct Completion {
10+
pub label: String,
11+
}
12+
13+
pub fn completion(db: &dyn Db, file: File, _offset: TextSize) -> Vec<Completion> {
14+
let parsed = parsed_module(db.upcast(), file);
15+
identifiers(parsed.syntax().into())
16+
.into_iter()
17+
.map(|label| Completion { label })
18+
.collect()
19+
}
20+
21+
fn identifiers(node: AnyNodeRef) -> Vec<String> {
22+
struct Visitor {
23+
identifiers: Vec<String>,
24+
}
25+
26+
impl<'a> SourceOrderVisitor<'a> for Visitor {
27+
fn visit_identifier(&mut self, id: &'a Identifier) {
28+
self.identifiers.push(id.id.as_str().to_string());
29+
}
30+
}
31+
32+
let mut visitor = Visitor {
33+
identifiers: vec![],
34+
};
35+
node.visit_source_order(&mut visitor);
36+
visitor.identifiers.sort();
37+
visitor.identifiers.dedup();
38+
visitor.identifiers
39+
}

crates/red_knot_ide/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
mod completion;
12
mod db;
23
mod find_node;
34
mod goto;
45
mod hover;
56
mod inlay_hints;
67
mod markup;
78

9+
pub use completion::completion;
810
pub use db::Db;
911
pub use goto::goto_type_definition;
1012
pub use hover::hover;

crates/red_knot_server/src/server.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ impl Server {
227227
inlay_hint_provider: Some(lsp_types::OneOf::Right(
228228
InlayHintServerCapabilities::Options(InlayHintOptions::default()),
229229
)),
230+
completion_provider: Some(lsp_types::CompletionOptions {
231+
..Default::default()
232+
}),
230233
..Default::default()
231234
}
232235
}

crates/red_knot_server/src/server/api.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ pub(super) fn request<'a>(req: server::Request) -> Task<'a> {
3636
request::InlayHintRequestHandler::METHOD => background_request_task::<
3737
request::InlayHintRequestHandler,
3838
>(req, BackgroundSchedule::Worker),
39+
request::CompletionRequestHandler::METHOD => background_request_task::<
40+
request::CompletionRequestHandler,
41+
>(
42+
req, BackgroundSchedule::LatencySensitive
43+
),
3944

4045
method => {
4146
tracing::warn!("Received request {method} which does not have a handler");

crates/red_knot_server/src/server/api/requests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
mod completion;
12
mod diagnostic;
23
mod goto_type_definition;
34
mod hover;
45
mod inlay_hints;
56

7+
pub(super) use completion::CompletionRequestHandler;
68
pub(super) use diagnostic::DocumentDiagnosticRequestHandler;
79
pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler;
810
pub(super) use hover::HoverRequestHandler;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::borrow::Cow;
2+
3+
use lsp_types::request::Completion;
4+
use lsp_types::{CompletionItem, CompletionParams, CompletionResponse, Url};
5+
use red_knot_ide::completion;
6+
use red_knot_project::ProjectDatabase;
7+
use ruff_db::source::{line_index, source_text};
8+
9+
use crate::document::PositionExt;
10+
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
11+
use crate::server::client::Notifier;
12+
use crate::DocumentSnapshot;
13+
14+
pub(crate) struct CompletionRequestHandler;
15+
16+
impl RequestHandler for CompletionRequestHandler {
17+
type RequestType = Completion;
18+
}
19+
20+
impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
21+
fn document_url(params: &CompletionParams) -> Cow<Url> {
22+
Cow::Borrowed(&params.text_document_position.text_document.uri)
23+
}
24+
25+
fn run_with_snapshot(
26+
snapshot: DocumentSnapshot,
27+
db: ProjectDatabase,
28+
_notifier: Notifier,
29+
params: CompletionParams,
30+
) -> crate::server::Result<Option<CompletionResponse>> {
31+
let Some(file) = snapshot.file(&db) else {
32+
tracing::debug!("Failed to resolve file for {:?}", params);
33+
return Ok(None);
34+
};
35+
36+
let source = source_text(&db, file);
37+
let line_index = line_index(&db, file);
38+
let offset = params.text_document_position.position.to_text_size(
39+
&source,
40+
&line_index,
41+
snapshot.encoding(),
42+
);
43+
let completions = completion(&db, file, offset);
44+
if completions.is_empty() {
45+
return Ok(None);
46+
}
47+
48+
let items: Vec<CompletionItem> = completions
49+
.into_iter()
50+
.map(|comp| CompletionItem {
51+
label: comp.label,
52+
..Default::default()
53+
})
54+
.collect();
55+
let response = CompletionResponse::Array(items);
56+
Ok(Some(response))
57+
}
58+
}

crates/red_knot_server/src/server/schedule/task.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ pub(in crate::server) enum BackgroundSchedule {
2121
Fmt,
2222
/// The task should be run on the general high-priority background
2323
/// thread. Reserved for actions caused by the user typing (e.g.syntax highlighting).
24-
#[expect(dead_code)]
2524
LatencySensitive,
2625
/// The task should be run on a regular-priority background thread.
2726
/// The default for any request that isn't in the critical path of the user typing.

0 commit comments

Comments
 (0)