Skip to content

Commit 859475f

Browse files
authored
[ty] add docstrings to completions based on type (#20008)
This is a fairly simple but effective way to add docstrings to like 95% of completions from initial experimentation. Fixes astral-sh/ty#1036 Although ironically this approach *does not* work specifically for `print` and I haven't looked into why.
1 parent 7b75aee commit 859475f

File tree

5 files changed

+64
-9
lines changed

5 files changed

+64
-9
lines changed

crates/ty_ide/src/completion.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
88
use ty_python_semantic::{Completion, NameKind, SemanticModel};
99

1010
use crate::Db;
11+
use crate::docstring::Docstring;
1112
use crate::find_node::covering_node;
13+
use crate::goto::DefinitionsOrTargets;
1214

13-
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'_>> {
15+
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedCompletion<'_>> {
1416
let parsed = parsed_module(db, file).load(db);
1517

1618
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {
@@ -40,6 +42,27 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<Completion<'
4042
completions.sort_by(compare_suggestions);
4143
completions.dedup_by(|c1, c2| c1.name == c2.name);
4244
completions
45+
.into_iter()
46+
.map(|completion| {
47+
let definition = DefinitionsOrTargets::from_ty(db, completion.ty);
48+
let documentation = definition.and_then(|def| def.docstring(db));
49+
DetailedCompletion {
50+
inner: completion,
51+
documentation,
52+
}
53+
})
54+
.collect()
55+
}
56+
57+
pub struct DetailedCompletion<'db> {
58+
pub inner: Completion<'db>,
59+
pub documentation: Option<Docstring>,
60+
}
61+
impl<'db> std::ops::Deref for DetailedCompletion<'db> {
62+
type Target = Completion<'db>;
63+
fn deref(&self) -> &Self::Target {
64+
&self.inner
65+
}
4366
}
4467

4568
/// The kind of tokens identified under the cursor.
@@ -478,9 +501,8 @@ fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
478501
mod tests {
479502
use insta::assert_snapshot;
480503
use ruff_python_parser::{Mode, ParseOptions, TokenKind, Tokens};
481-
use ty_python_semantic::Completion;
482504

483-
use crate::completion;
505+
use crate::completion::{DetailedCompletion, completion};
484506
use crate::tests::{CursorTest, cursor_test};
485507

486508
use super::token_suffix_by_kinds;
@@ -3022,14 +3044,14 @@ from os.<CURSOR>
30223044
)
30233045
}
30243046

3025-
fn completions_if(&self, predicate: impl Fn(&Completion) -> bool) -> String {
3047+
fn completions_if(&self, predicate: impl Fn(&DetailedCompletion) -> bool) -> String {
30263048
self.completions_if_snapshot(predicate, |c| c.name.as_str().to_string())
30273049
}
30283050

30293051
fn completions_if_snapshot(
30303052
&self,
3031-
predicate: impl Fn(&Completion) -> bool,
3032-
snapshot: impl Fn(&Completion) -> String,
3053+
predicate: impl Fn(&DetailedCompletion) -> bool,
3054+
snapshot: impl Fn(&DetailedCompletion) -> String,
30333055
) -> String {
30343056
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
30353057
if completions.is_empty() {

crates/ty_ide/src/goto.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,28 @@ pub(crate) enum DefinitionsOrTargets<'db> {
159159
}
160160

161161
impl<'db> DefinitionsOrTargets<'db> {
162+
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
163+
let ty_def = ty.definition(db)?;
164+
let resolved = match ty_def {
165+
ty_python_semantic::types::TypeDefinition::Module(module) => {
166+
ResolvedDefinition::Module(module.file(db)?)
167+
}
168+
ty_python_semantic::types::TypeDefinition::Class(definition) => {
169+
ResolvedDefinition::Definition(definition)
170+
}
171+
ty_python_semantic::types::TypeDefinition::Function(definition) => {
172+
ResolvedDefinition::Definition(definition)
173+
}
174+
ty_python_semantic::types::TypeDefinition::TypeVar(definition) => {
175+
ResolvedDefinition::Definition(definition)
176+
}
177+
ty_python_semantic::types::TypeDefinition::TypeAlias(definition) => {
178+
ResolvedDefinition::Definition(definition)
179+
}
180+
};
181+
Some(DefinitionsOrTargets::Definitions(vec![resolved]))
182+
}
183+
162184
/// Get the "goto-declaration" interpretation of this definition
163185
///
164186
/// In this case it basically returns exactly what was found.

crates/ty_server/src/server/api/requests/completion.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use std::borrow::Cow;
22
use std::time::Instant;
33

44
use lsp_types::request::Completion;
5-
use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Url};
5+
use lsp_types::{
6+
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Documentation, Url,
7+
};
68
use ruff_db::source::{line_index, source_text};
79
use ty_ide::completion;
810
use ty_project::ProjectDatabase;
@@ -64,9 +66,12 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
6466
.map(|(i, comp)| {
6567
let kind = comp.kind(db).map(ty_kind_to_lsp_kind);
6668
CompletionItem {
67-
label: comp.name.into(),
69+
label: comp.inner.name.into(),
6870
kind,
6971
sort_text: Some(format!("{i:-max_index_len$}")),
72+
documentation: comp
73+
.documentation
74+
.map(|docstring| Documentation::String(docstring.render_plaintext())),
7075
..Default::default()
7176
}
7277
})

crates/ty_wasm/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,10 @@ impl Workspace {
421421
.into_iter()
422422
.map(|completion| Completion {
423423
kind: completion.kind(&self.db).map(CompletionKind::from),
424-
name: completion.name.into(),
424+
name: completion.inner.name.into(),
425+
documentation: completion
426+
.documentation
427+
.map(|documentation| documentation.render_plaintext()),
425428
})
426429
.collect())
427430
}
@@ -908,6 +911,8 @@ pub struct Completion {
908911
#[wasm_bindgen(getter_with_clone)]
909912
pub name: String,
910913
pub kind: Option<CompletionKind>,
914+
#[wasm_bindgen(getter_with_clone)]
915+
pub documentation: Option<String>,
911916
}
912917

913918
#[wasm_bindgen]

playground/ty/src/Editor/Editor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class PlaygroundServer
319319
? CompletionItemKind.Variable
320320
: mapCompletionKind(completion.kind),
321321
insertText: completion.name,
322+
documentation: completion.documentation,
322323
// TODO(micha): It's unclear why this field is required for monaco but not VS Code.
323324
// and omitting it works just fine? The LSP doesn't expose this information right now
324325
// which is why we go with undefined for now.

0 commit comments

Comments
 (0)