Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions pyrefly/lib/state/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3136,11 +3136,17 @@ impl<'a> Transaction<'a> {
supports_completion_item_details,
);
}
// If autoimport completions were skipped due to character threshold,
// mark the results as incomplete so clients keep asking for completions.
// This ensures autoimport completions will be checked once the threshold is reached,
// even if local completions are currently available.
if identifier.as_str().len() < MIN_CHARACTERS_TYPED_AUTOIMPORT {
// Mark results as incomplete in the following cases so clients keep asking
// for completions as the user types more:
// 1. If identifier is below MIN_CHARACTERS_TYPED_AUTOIMPORT threshold,
// autoimport completions are skipped and will be checked once threshold
// is reached.
// 2. If local completions exist and blocked autoimport completions,
// the local completions might not match as the user continues typing,
// and autoimport completions should then be shown.
if identifier.as_str().len() < MIN_CHARACTERS_TYPED_AUTOIMPORT
|| has_local_completions
{
is_incomplete = true;
}
self.add_builtins_autoimport_completions(handle, Some(&identifier), &mut result);
Expand Down
75 changes: 75 additions & 0 deletions pyrefly/lib/test/lsp/lsp_interaction/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,3 +602,78 @@ fn test_completion_complete_with_local_completions() {

interaction.shutdown().unwrap();
}

#[test]
fn test_completion_incomplete_with_local_completions_blocking_autoimport() {
let root = get_test_files_root();
let mut interaction = LspInteraction::new();
interaction.set_root(root.path().join("autoimport_common_prefix"));
interaction
.initialize(InitializeSettings::default())
.unwrap();

// Open b.py which has UsersController, and a.py which has UsersManager
interaction.client.did_open("b.py");
interaction.client.did_open("a.py");

// Type "Users" (5 characters, above MIN_CHARACTERS_TYPED_AUTOIMPORT = 3)
// in b.py. Local completion UsersController exists, so autoimport is skipped.
// But is_incomplete should still be true because the local completion might
// not match as the user continues typing (e.g., "UsersM" should show UsersManager).
interaction
.client
.did_change("b.py", "class UsersController:\n pass\n\nUsers");

interaction
.client
.completion("b.py", 3, 5)
.expect_completion_response_with(|list| {
// Should have local completion UsersController
let has_users_controller = list
.items
.iter()
.any(|item| item.label == "UsersController");
// is_incomplete should be true so client asks again when typing more
has_users_controller && list.is_incomplete
})
.unwrap();

interaction.shutdown().unwrap();
}

#[test]
fn test_completion_autoimport_shown_when_local_no_longer_matches() {
let root = get_test_files_root();
let mut interaction = LspInteraction::new();
interaction.set_root(root.path().join("autoimport_common_prefix"));
interaction
.initialize(InitializeSettings::default())
.unwrap();

// Open b.py which has UsersController, and a.py which has UsersManager
interaction.client.did_open("b.py");
interaction.client.did_open("a.py");

// Type "UsersM" - this should NOT match local "UsersController" (no 'M' in it)
// but SHOULD match autoimport "UsersManager" from a.py
interaction
.client
.did_change("b.py", "class UsersController:\n pass\n\nUsersM");

interaction
.client
.completion("b.py", 3, 6)
.expect_completion_response_with(|list| {
// Should have autoimport completion UsersManager
let has_users_manager = list.items.iter().any(|item| item.label == "UsersManager");
// Should NOT have UsersController (doesn't match "UsersM")
let has_users_controller = list
.items
.iter()
.any(|item| item.label == "UsersController");
has_users_manager && !has_users_controller
})
.unwrap();

interaction.shutdown().unwrap();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.


class UsersManager:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.


class UsersController:
pass