[ty] Make auto-import completions opt-in via an experimental option

Instead of waiting to land auto-import until it is "ready
for users," it'd be nicer to get incremental progress merged
to `main`. By making it an experimental opt-in, we avoid making
the default completion experience worse but permit developers
and motivated users to try it.
This commit is contained in:
Andrew Gallant 2025-08-29 14:44:56 -04:00 committed by Andrew Gallant
parent 8e52027a88
commit 0a0eaf5a9b
7 changed files with 63 additions and 16 deletions

View file

@ -12,7 +12,17 @@ use crate::find_node::covering_node;
use crate::goto::DefinitionsOrTargets;
use crate::{Db, all_symbols};
pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedCompletion<'_>> {
#[derive(Clone, Debug, Default)]
pub struct CompletionSettings {
pub auto_import: bool,
}
pub fn completion<'db>(
db: &'db dyn Db,
settings: &CompletionSettings,
file: File,
offset: TextSize,
) -> Vec<DetailedCompletion<'db>> {
let parsed = parsed_module(db, file).load(db);
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {
@ -39,6 +49,7 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedComp
}
CompletionTargetAst::Scoped { node } => {
let mut completions = model.scoped_completions(node);
if settings.auto_import {
for symbol in all_symbols(db, "") {
completions.push(Completion {
name: ast::name::Name::new(&symbol.symbol.name),
@ -47,6 +58,7 @@ pub fn completion(db: &dyn Db, file: File, offset: TextSize) -> Vec<DetailedComp
builtin: false,
});
}
}
completions
}
};
@ -518,7 +530,7 @@ mod tests {
use crate::completion::{DetailedCompletion, completion};
use crate::tests::{CursorTest, cursor_test};
use super::token_suffix_by_kinds;
use super::{CompletionSettings, token_suffix_by_kinds};
#[test]
fn token_suffixes_match() {
@ -3073,7 +3085,8 @@ from os.<CURSOR>
predicate: impl Fn(&DetailedCompletion) -> bool,
snapshot: impl Fn(&DetailedCompletion) -> String,
) -> String {
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
let settings = CompletionSettings::default();
let completions = completion(&self.db, &settings, self.cursor.file, self.cursor.offset);
if completions.is_empty() {
return "<No completions found>".to_string();
}
@ -3096,7 +3109,8 @@ from os.<CURSOR>
#[track_caller]
fn assert_completions_include(&self, expected: &str) {
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
let settings = CompletionSettings::default();
let completions = completion(&self.db, &settings, self.cursor.file, self.cursor.offset);
assert!(
completions
@ -3108,7 +3122,8 @@ from os.<CURSOR>
#[track_caller]
fn assert_completions_do_not_include(&self, unexpected: &str) {
let completions = completion(&self.db, self.cursor.file, self.cursor.offset);
let settings = CompletionSettings::default();
let completions = completion(&self.db, &settings, self.cursor.file, self.cursor.offset);
assert!(
completions

View file

@ -26,7 +26,7 @@ mod symbols;
mod workspace_symbols;
pub use all_symbols::{AllSymbolInfo, all_symbols};
pub use completion::completion;
pub use completion::{CompletionSettings, completion};
pub use doc_highlights::document_highlights;
pub use document_symbols::document_symbols;
pub use goto::{goto_declaration, goto_definition, goto_type_definition};

View file

@ -7,7 +7,7 @@ use lsp_types::{
};
use ruff_db::source::{line_index, source_text};
use ruff_source_file::OneIndexed;
use ty_ide::completion;
use ty_ide::{CompletionSettings, completion};
use ty_project::ProjectDatabase;
use ty_python_semantic::CompletionKind;
@ -55,7 +55,10 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
&line_index,
snapshot.encoding(),
);
let completions = completion(db, file, offset);
let settings = CompletionSettings {
auto_import: snapshot.global_settings().is_auto_import_enabled(),
};
let completions = completion(db, &settings, file, offset);
if completions.is_empty() {
return Ok(None);
}

View file

@ -826,6 +826,7 @@ impl Session {
.map_err(DocumentQueryError::InvalidUrl);
DocumentSnapshot {
resolved_client_capabilities: self.resolved_client_capabilities,
global_settings: self.global_settings.clone(),
workspace_settings: key
.as_ref()
.ok()
@ -1000,6 +1001,7 @@ impl Drop for MutIndexGuard<'_> {
#[derive(Debug)]
pub(crate) struct DocumentSnapshot {
resolved_client_capabilities: ResolvedClientCapabilities,
global_settings: Arc<GlobalSettings>,
workspace_settings: Arc<WorkspaceSettings>,
position_encoding: PositionEncoding,
document_query_result: Result<DocumentQuery, DocumentQueryError>,
@ -1016,6 +1018,11 @@ impl DocumentSnapshot {
self.position_encoding
}
/// Returns the client settings for all workspaces.
pub(crate) fn global_settings(&self) -> &GlobalSettings {
&self.global_settings
}
/// Returns the client settings for the workspace that this document belongs to.
pub(crate) fn workspace_settings(&self) -> &WorkspaceSettings {
&self.workspace_settings

View file

@ -122,6 +122,12 @@ impl ClientOptions {
self
}
#[must_use]
pub fn with_experimental_auto_import(mut self, enabled: bool) -> Self {
self.global.experimental.get_or_insert_default().auto_import = Some(enabled);
self
}
#[must_use]
pub fn with_unknown(mut self, unknown: HashMap<String, Value>) -> Self {
self.unknown = unknown;
@ -149,6 +155,7 @@ impl GlobalOptions {
.experimental
.map(|experimental| ExperimentalSettings {
rename: experimental.rename.unwrap_or(true),
auto_import: experimental.auto_import.unwrap_or(false),
})
.unwrap_or_default();
@ -293,6 +300,12 @@ impl Combine for DiagnosticMode {
pub(crate) struct Experimental {
/// Whether to enable the experimental symbol rename feature.
pub(crate) rename: Option<bool>,
/// Whether to enable the experimental "auto-import" feature.
///
/// At time of writing (2025-08-29), this feature is still
/// under active development. It may not work right or may be
/// incomplete.
pub(crate) auto_import: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]

View file

@ -14,6 +14,10 @@ impl GlobalSettings {
pub(crate) fn is_rename_enabled(&self) -> bool {
self.experimental.rename
}
pub(crate) fn is_auto_import_enabled(&self) -> bool {
self.experimental.auto_import
}
}
impl GlobalSettings {
@ -25,6 +29,7 @@ impl GlobalSettings {
#[derive(Clone, Default, Debug, PartialEq)]
pub(crate) struct ExperimentalSettings {
pub(super) rename: bool,
pub(super) auto_import: bool,
}
/// Resolved client settings for a specific workspace.

View file

@ -415,7 +415,11 @@ impl Workspace {
let offset = position.to_text_size(&source, &index, self.position_encoding)?;
let completions = ty_ide::completion(&self.db, file_id.file, offset);
// NOTE: At time of writing, 2025-08-29, auto-import isn't
// ready to be enabled by default yet. Once it is, we should
// either just enable it or provide a way to configure it.
let settings = ty_ide::CompletionSettings { auto_import: false };
let completions = ty_ide::completion(&self.db, &settings, file_id.file, offset);
Ok(completions
.into_iter()