Skip to content

Commit 4e67e6f

Browse files
kinto0facebook-github-bot
authored andcommitted
implement basic completions for module attributes
Summary: this diff implements basic completions for the `y` in `from x import y`. we do this by reusing the `import_at` api implemented in the last diff. unfortunately, there are a few gaps: - `from x import <>` does not work - `from x imp<>` unfortunately works (because ruff's parser makes that an ImportFrom node with module = `x` and names = ['imp']) I will fix these in follow-up diffs, but I believe the current implementation is better than nothing for now Reviewed By: SamChou19815 Differential Revision: D75477356 fbshipit-source-id: 5fabe76f474a842f2d5e8319b50c7cc567de455d
1 parent c015036 commit 4e67e6f

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

pyrefly/lib/state/lsp.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,28 @@ impl<'a> Transaction<'a> {
551551
handle: &Handle,
552552
position: TextSize,
553553
) -> Option<Vec<CompletionItem>> {
554-
if self.identifier_at(handle, position).is_some() {
554+
if let Some(import) = self.import_at(handle, position) {
555+
return match import {
556+
ImportIdentifier::Name(module_name) => {
557+
let handle = self.import_handle(handle, module_name, None).ok()?;
558+
let exports = self.get_exports(&handle);
559+
let completions = exports
560+
.keys()
561+
.map(|name| CompletionItem {
562+
label: name.to_string(),
563+
// todo(kylei): completion kind for exports
564+
kind: Some(CompletionItemKind::VARIABLE),
565+
..Default::default()
566+
})
567+
.collect();
568+
Some(completions)
569+
}
570+
ImportIdentifier::Module(_module_name) => {
571+
// TODO(kylei): completion for module names
572+
None
573+
}
574+
};
575+
} else if self.identifier_at(handle, position).is_some() {
555576
let bindings = self.get_bindings(handle)?;
556577
let module_info = self.get_module_info(handle)?;
557578
let names = bindings

pyrefly/lib/test/lsp/completion.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,94 @@ Completion Results:
179179
report.trim(),
180180
);
181181
}
182+
183+
// TODO(kylei): ruff's ast gives us names = ["imp"] for `from foo imp`
184+
#[test]
185+
fn from_import_imp_test() {
186+
let foo_code = r#"
187+
imperial_guard = "cool"
188+
"#;
189+
let main_code = r#"
190+
from foo imp
191+
# ^
192+
"#;
193+
let report = get_batched_lsp_operations_report_allow_error(
194+
&[("main", main_code), ("foo", foo_code)],
195+
get_test_report,
196+
);
197+
assert_eq!(
198+
r#"
199+
# main.py
200+
2 | from foo imp
201+
^
202+
Completion Results:
203+
- (Variable) imperial_guard
204+
205+
206+
# foo.py
207+
"#
208+
.trim(),
209+
report.trim(),
210+
);
211+
}
212+
// TODO(kylei): ruff's ast gives us names = [] for `from foo import <>`
213+
#[test]
214+
fn from_import_empty_test() {
215+
let foo_code = r#"
216+
imperial_guard = "cool"
217+
"#;
218+
let main_code = r#"
219+
from foo import
220+
# ^
221+
"#;
222+
let report = get_batched_lsp_operations_report_allow_error(
223+
&[("main", main_code), ("foo", foo_code)],
224+
get_test_report,
225+
);
226+
assert_eq!(
227+
r#"
228+
# main.py
229+
2 | from foo import
230+
^
231+
Completion Results:
232+
233+
234+
# foo.py
235+
"#
236+
.trim(),
237+
report.trim(),
238+
);
239+
}
240+
241+
#[test]
242+
fn from_import_basic() {
243+
let foo_code = r#"
244+
imperial_guard = "cool"
245+
"#;
246+
let main_code = r#"
247+
from foo import imperial
248+
# ^ ^
249+
"#;
250+
let report = get_batched_lsp_operations_report_allow_error(
251+
&[("main", main_code), ("foo", foo_code)],
252+
get_test_report,
253+
);
254+
assert_eq!(
255+
r#"
256+
# main.py
257+
2 | from foo import imperial
258+
^
259+
Completion Results:
260+
261+
2 | from foo import imperial
262+
^
263+
Completion Results:
264+
- (Variable) imperial_guard
265+
266+
267+
# foo.py
268+
"#
269+
.trim(),
270+
report.trim(),
271+
);
272+
}

0 commit comments

Comments
 (0)