mirror of https://github.com/astral-sh/ruff
[ty] Add some completion ranking improvements (#20807)
Co-authored-by: Micha Reiser <micha@reiser.io> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
4fc7dd300c
commit
651f7963a7
|
|
@ -721,7 +721,7 @@ jobs:
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
- name: "Run ty completion evaluation"
|
- name: "Run ty completion evaluation"
|
||||||
run: cargo run --release --package ty_completion_eval -- all --threshold 0.1 --tasks /tmp/completion-evaluation-tasks.csv
|
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
|
||||||
- name: "Ensure there are no changes"
|
- name: "Ensure there are no changes"
|
||||||
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
|
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ To run a full evaluation, run the `ty_completion_eval` crate with the
|
||||||
`all` command from the root of this repository:
|
`all` command from the root of this repository:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
cargo run --release --package ty_completion_eval -- all
|
cargo run --profile profiling --package ty_completion_eval -- all
|
||||||
```
|
```
|
||||||
|
|
||||||
The output should look like this:
|
The output should look like this:
|
||||||
|
|
@ -24,7 +24,7 @@ you can ask the evaluation to write CSV data that contains the rank of
|
||||||
the expected answer in each completion request:
|
the expected answer in each completion request:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
cargo r -r -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
cargo r --profile profiling -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||||
```
|
```
|
||||||
|
|
||||||
To debug a _specific_ task and look at the actual results, use the `show-one`
|
To debug a _specific_ task and look at the actual results, use the `show-one`
|
||||||
|
|
@ -133,7 +133,7 @@ CI will also fail if the individual task results have changed.
|
||||||
To make CI pass, you can just re-run the evaluation locally and commit the results:
|
To make CI pass, you can just re-run the evaluation locally and commit the results:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
cargo r -r -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
cargo r --profile profiling -p ty_completion_eval -- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||||
```
|
```
|
||||||
|
|
||||||
CI fails in this case because it would be best to scrutinize the differences here.
|
CI fails in this case because it would be best to scrutinize the differences here.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
name,file,index,rank
|
name,file,index,rank
|
||||||
|
fstring-completions,main.py,0,1
|
||||||
higher-level-symbols-preferred,main.py,0,
|
higher-level-symbols-preferred,main.py,0,
|
||||||
higher-level-symbols-preferred,main.py,1,1
|
higher-level-symbols-preferred,main.py,1,1
|
||||||
import-deprioritizes-dunder,main.py,0,195
|
import-deprioritizes-dunder,main.py,0,1
|
||||||
import-deprioritizes-sunder,main.py,0,195
|
import-deprioritizes-sunder,main.py,0,1
|
||||||
internal-typeshed-hidden,main.py,0,43
|
internal-typeshed-hidden,main.py,0,4
|
||||||
|
none-completion,main.py,0,11
|
||||||
numpy-array,main.py,0,
|
numpy-array,main.py,0,
|
||||||
numpy-array,main.py,1,32
|
numpy-array,main.py,1,1
|
||||||
object-attr-instance-methods,main.py,0,7
|
object-attr-instance-methods,main.py,0,1
|
||||||
object-attr-instance-methods,main.py,1,1
|
object-attr-instance-methods,main.py,1,1
|
||||||
raise-uses-base-exception,main.py,0,42
|
raise-uses-base-exception,main.py,0,2
|
||||||
scope-existing-over-new-import,main.py,0,495
|
scope-existing-over-new-import,main.py,0,474
|
||||||
scope-prioritize-closer,main.py,0,152
|
scope-prioritize-closer,main.py,0,2
|
||||||
scope-simple-long-identifier,main.py,0,140
|
scope-simple-long-identifier,main.py,0,1
|
||||||
ty-extensions-lower-stdlib,main.py,0,142
|
tstring-completions,main.py,0,1
|
||||||
type-var-typing-over-ast,main.py,0,65
|
ty-extensions-lower-stdlib,main.py,0,7
|
||||||
type-var-typing-over-ast,main.py,1,353
|
type-var-typing-over-ast,main.py,0,3
|
||||||
|
type-var-typing-over-ast,main.py,1,270
|
||||||
|
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ use regex::bytes::Regex;
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||||
use ty_ide::Completion;
|
use ty_ide::Completion;
|
||||||
|
use ty_project::metadata::Options;
|
||||||
|
use ty_project::metadata::options::EnvironmentOptions;
|
||||||
|
use ty_project::metadata::value::RelativePathBuf;
|
||||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||||
use ty_python_semantic::ModuleName;
|
use ty_python_semantic::ModuleName;
|
||||||
|
|
||||||
|
|
@ -117,8 +120,8 @@ impl ShowOneCommand {
|
||||||
&& self
|
&& self
|
||||||
.file_name
|
.file_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|name| name == task.cursor_name())
|
.is_none_or(|name| name == task.cursor_name())
|
||||||
&& self.index.is_some_and(|index| index == task.cursor.index)
|
&& self.index.is_none_or(|index| index == task.cursor.index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +281,14 @@ impl Task {
|
||||||
|
|
||||||
let system = OsSystem::new(project_path);
|
let system = OsSystem::new(project_path);
|
||||||
let mut project_metadata = ProjectMetadata::discover(project_path, &system)?;
|
let mut project_metadata = ProjectMetadata::discover(project_path, &system)?;
|
||||||
|
// Explicitly point ty to the .venv to avoid any set VIRTUAL_ENV variable to take precedence.
|
||||||
|
project_metadata.apply_options(Options {
|
||||||
|
environment: Some(EnvironmentOptions {
|
||||||
|
python: Some(RelativePathBuf::cli(".venv")),
|
||||||
|
..EnvironmentOptions::default()
|
||||||
|
}),
|
||||||
|
..Options::default()
|
||||||
|
});
|
||||||
project_metadata.apply_configuration_files(&system)?;
|
project_metadata.apply_configuration_files(&system)?;
|
||||||
let db = ProjectDatabase::new(project_metadata, system)?;
|
let db = ProjectDatabase::new(project_metadata, system)?;
|
||||||
Ok(Task {
|
Ok(Task {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[settings]
|
||||||
|
auto-import = false
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
zqzqzq_identifier = 1
|
||||||
|
|
||||||
|
print(f"{zqzqzq_<CURSOR: zqzqzq_identifier>}")
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
[project]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = []
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[settings]
|
||||||
|
auto-import = false
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
x = Non<CURSOR: None>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
[project]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = []
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
[settings]
|
||||||
|
auto-import = false
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
zqzqzq_identifier = 1
|
||||||
|
|
||||||
|
print(t"{zqzqzq_<CURSOR: zqzqzq_identifier>}")
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
[project]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = []
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
@ -7,7 +7,7 @@ use ruff_diagnostics::Edit;
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_parser::{Token, TokenAt, TokenKind};
|
use ruff_python_parser::{Token, TokenAt, TokenKind, Tokens};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
use ty_python_semantic::{
|
use ty_python_semantic::{
|
||||||
Completion as SemanticCompletion, ModuleName, NameKind, SemanticModel,
|
Completion as SemanticCompletion, ModuleName, NameKind, SemanticModel,
|
||||||
|
|
@ -18,6 +18,7 @@ use crate::docstring::Docstring;
|
||||||
use crate::find_node::covering_node;
|
use crate::find_node::covering_node;
|
||||||
use crate::goto::DefinitionsOrTargets;
|
use crate::goto::DefinitionsOrTargets;
|
||||||
use crate::importer::{ImportRequest, Importer};
|
use crate::importer::{ImportRequest, Importer};
|
||||||
|
use crate::symbols::QueryPattern;
|
||||||
use crate::{Db, all_symbols};
|
use crate::{Db, all_symbols};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -206,6 +207,15 @@ pub fn completion<'db>(
|
||||||
offset: TextSize,
|
offset: TextSize,
|
||||||
) -> Vec<Completion<'db>> {
|
) -> Vec<Completion<'db>> {
|
||||||
let parsed = parsed_module(db, file).load(db);
|
let parsed = parsed_module(db, file).load(db);
|
||||||
|
if is_in_comment(&parsed, offset) || is_in_string(&parsed, offset) {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let typed = find_typed_text(db, file, &parsed, offset);
|
||||||
|
let typed_query = typed
|
||||||
|
.as_deref()
|
||||||
|
.map(QueryPattern::new)
|
||||||
|
.unwrap_or_else(QueryPattern::matches_all_symbols);
|
||||||
|
|
||||||
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {
|
let Some(target_token) = CompletionTargetTokens::find(&parsed, offset) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
|
|
@ -235,12 +245,23 @@ pub fn completion<'db>(
|
||||||
};
|
};
|
||||||
let mut completions: Vec<Completion<'_>> = semantic_completions
|
let mut completions: Vec<Completion<'_>> = semantic_completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|c| typed_query.is_match_symbol_name(c.name.as_str()))
|
||||||
.map(|c| Completion::from_semantic_completion(db, c))
|
.map(|c| Completion::from_semantic_completion(db, c))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
if scoped.is_some() {
|
||||||
|
add_keyword_value_completions(db, &typed_query, &mut completions);
|
||||||
|
}
|
||||||
if settings.auto_import {
|
if settings.auto_import {
|
||||||
if let Some(scoped) = scoped {
|
if let Some(scoped) = scoped {
|
||||||
add_unimported_completions(db, file, &parsed, scoped, &mut completions);
|
add_unimported_completions(
|
||||||
|
db,
|
||||||
|
file,
|
||||||
|
&parsed,
|
||||||
|
scoped,
|
||||||
|
typed.as_deref(),
|
||||||
|
&mut completions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completions.sort_by(compare_suggestions);
|
completions.sort_by(compare_suggestions);
|
||||||
|
|
@ -248,6 +269,37 @@ pub fn completion<'db>(
|
||||||
completions
|
completions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a subset of completions derived from keywords.
|
||||||
|
///
|
||||||
|
/// Note that at present, these should only be added to "scoped"
|
||||||
|
/// completions. i.e., This will include `None`, `True`, `False`, etc.
|
||||||
|
fn add_keyword_value_completions<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
query: &QueryPattern,
|
||||||
|
completions: &mut Vec<Completion<'db>>,
|
||||||
|
) {
|
||||||
|
let keywords = [
|
||||||
|
("None", Type::none(db)),
|
||||||
|
("True", Type::BooleanLiteral(true)),
|
||||||
|
("False", Type::BooleanLiteral(false)),
|
||||||
|
];
|
||||||
|
for (name, ty) in keywords {
|
||||||
|
if !query.is_match_symbol_name(name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
completions.push(Completion {
|
||||||
|
name: ast::name::Name::new(name),
|
||||||
|
insert: None,
|
||||||
|
ty: Some(ty),
|
||||||
|
kind: None,
|
||||||
|
module_name: None,
|
||||||
|
import: None,
|
||||||
|
builtin: true,
|
||||||
|
documentation: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds completions not in scope.
|
/// Adds completions not in scope.
|
||||||
///
|
///
|
||||||
/// `scoped` should be information about the identified scope
|
/// `scoped` should be information about the identified scope
|
||||||
|
|
@ -260,9 +312,10 @@ fn add_unimported_completions<'db>(
|
||||||
file: File,
|
file: File,
|
||||||
parsed: &ParsedModuleRef,
|
parsed: &ParsedModuleRef,
|
||||||
scoped: ScopedTarget<'_>,
|
scoped: ScopedTarget<'_>,
|
||||||
|
typed: Option<&str>,
|
||||||
completions: &mut Vec<Completion<'db>>,
|
completions: &mut Vec<Completion<'db>>,
|
||||||
) {
|
) {
|
||||||
let Some(typed) = scoped.typed else {
|
let Some(typed) = typed else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let source = source_text(db, file);
|
let source = source_text(db, file);
|
||||||
|
|
@ -356,7 +409,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||||
TokenAt::Single(tok) => tok.end(),
|
TokenAt::Single(tok) => tok.end(),
|
||||||
TokenAt::Between(_, tok) => tok.start(),
|
TokenAt::Between(_, tok) => tok.start(),
|
||||||
};
|
};
|
||||||
let before = parsed.tokens().before(offset);
|
let before = tokens_start_before(parsed.tokens(), offset);
|
||||||
Some(
|
Some(
|
||||||
// Our strategy when it comes to `object.attribute` here is
|
// Our strategy when it comes to `object.attribute` here is
|
||||||
// to look for the `.` and then take the token immediately
|
// to look for the `.` and then take the token immediately
|
||||||
|
|
@ -485,21 +538,13 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||||
}
|
}
|
||||||
CompletionTargetTokens::Generic { token } => {
|
CompletionTargetTokens::Generic { token } => {
|
||||||
let node = covering_node(parsed.syntax().into(), token.range()).node();
|
let node = covering_node(parsed.syntax().into(), token.range()).node();
|
||||||
let typed = match node {
|
Some(CompletionTargetAst::Scoped(ScopedTarget { node }))
|
||||||
ast::AnyNodeRef::ExprName(ast::ExprName { id, .. }) => {
|
|
||||||
let name = id.as_str();
|
|
||||||
if name.is_empty() { None } else { Some(name) }
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
Some(CompletionTargetAst::Scoped(ScopedTarget { node, typed }))
|
|
||||||
}
|
}
|
||||||
CompletionTargetTokens::Unknown => {
|
CompletionTargetTokens::Unknown => {
|
||||||
let range = TextRange::empty(offset);
|
let range = TextRange::empty(offset);
|
||||||
let covering_node = covering_node(parsed.syntax().into(), range);
|
let covering_node = covering_node(parsed.syntax().into(), range);
|
||||||
Some(CompletionTargetAst::Scoped(ScopedTarget {
|
Some(CompletionTargetAst::Scoped(ScopedTarget {
|
||||||
node: covering_node.node(),
|
node: covering_node.node(),
|
||||||
typed: None,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -561,11 +606,25 @@ struct ScopedTarget<'t> {
|
||||||
/// The node with the smallest range that fully covers
|
/// The node with the smallest range that fully covers
|
||||||
/// the token under the cursor.
|
/// the token under the cursor.
|
||||||
node: ast::AnyNodeRef<'t>,
|
node: ast::AnyNodeRef<'t>,
|
||||||
/// The text that has been typed so far, if available.
|
}
|
||||||
///
|
|
||||||
/// When not `None`, the typed text is guaranteed to be
|
/// Returns a slice of tokens that all start before or at the given
|
||||||
/// non-empty.
|
/// [`TextSize`] offset.
|
||||||
typed: Option<&'t str>,
|
///
|
||||||
|
/// If the given offset is between two tokens, the returned slice will end just
|
||||||
|
/// before the following token. In other words, if the offset is between the
|
||||||
|
/// end of previous token and start of next token, the returned slice will end
|
||||||
|
/// just before the next token.
|
||||||
|
///
|
||||||
|
/// Unlike `Tokens::before`, this never panics. If `offset` is within a token's
|
||||||
|
/// range (including if it's at the very beginning), then that token will be
|
||||||
|
/// included in the slice returned.
|
||||||
|
fn tokens_start_before(tokens: &Tokens, offset: TextSize) -> &[Token] {
|
||||||
|
let idx = match tokens.binary_search_by(|token| token.start().cmp(&offset)) {
|
||||||
|
Ok(idx) => idx,
|
||||||
|
Err(idx) => idx,
|
||||||
|
};
|
||||||
|
&tokens[..idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a suffix of `tokens` corresponding to the `kinds` given.
|
/// Returns a suffix of `tokens` corresponding to the `kinds` given.
|
||||||
|
|
@ -729,6 +788,57 @@ fn import_tokens(tokens: &[Token]) -> Option<(&Token, &Token)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks for the text typed immediately before the cursor offset
|
||||||
|
/// given.
|
||||||
|
///
|
||||||
|
/// If there isn't any typed text or it could not otherwise be found,
|
||||||
|
/// then `None` is returned.
|
||||||
|
fn find_typed_text(
|
||||||
|
db: &dyn Db,
|
||||||
|
file: File,
|
||||||
|
parsed: &ParsedModuleRef,
|
||||||
|
offset: TextSize,
|
||||||
|
) -> Option<String> {
|
||||||
|
let source = source_text(db, file);
|
||||||
|
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||||
|
let last = tokens.last()?;
|
||||||
|
if !matches!(last.kind(), TokenKind::Name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// This one's weird, but if the cursor is beyond
|
||||||
|
// what is in the closest `Name` token, then it's
|
||||||
|
// likely we can't infer anything about what has
|
||||||
|
// been typed. This likely means there is whitespace
|
||||||
|
// or something that isn't represented in the token
|
||||||
|
// stream. So just give up.
|
||||||
|
if last.end() < offset {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(source[last.range()].to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the given offset within the parsed module is within
|
||||||
|
/// a comment or not.
|
||||||
|
fn is_in_comment(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||||
|
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||||
|
tokens.last().is_some_and(|t| t.kind().is_comment())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true when the cursor at `offset` is positioned within
|
||||||
|
/// a string token (regular, f-string, t-string, etc).
|
||||||
|
///
|
||||||
|
/// Note that this will return `false` when positioned within an
|
||||||
|
/// interpolation block in an f-string or a t-string.
|
||||||
|
fn is_in_string(parsed: &ParsedModuleRef, offset: TextSize) -> bool {
|
||||||
|
let tokens = tokens_start_before(parsed.tokens(), offset);
|
||||||
|
tokens.last().is_some_and(|t| {
|
||||||
|
matches!(
|
||||||
|
t.kind(),
|
||||||
|
TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Order completions lexicographically, with these exceptions:
|
/// Order completions lexicographically, with these exceptions:
|
||||||
///
|
///
|
||||||
/// 1) A `_[^_]` prefix sorts last and
|
/// 1) A `_[^_]` prefix sorts last and
|
||||||
|
|
@ -1055,7 +1165,7 @@ g<CURSOR>
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @"foo");
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found after filtering out completions>");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1493,10 +1603,8 @@ class Foo:
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||||
Foo
|
|
||||||
bar
|
bar
|
||||||
frob
|
frob
|
||||||
quux
|
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1510,11 +1618,7 @@ class Foo:
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
assert_snapshot!(test.completions_without_builtins(), @"bar");
|
||||||
Foo
|
|
||||||
bar
|
|
||||||
quux
|
|
||||||
");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -1687,29 +1791,8 @@ quux.b<CURSOR>
|
||||||
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
assert_snapshot!(test.completions_without_builtins_with_types(), @r"
|
||||||
bar :: Unknown | Literal[2]
|
bar :: Unknown | Literal[2]
|
||||||
baz :: Unknown | Literal[3]
|
baz :: Unknown | Literal[3]
|
||||||
foo :: Unknown | Literal[1]
|
|
||||||
__annotations__ :: dict[str, Any]
|
|
||||||
__class__ :: type[Quux]
|
|
||||||
__delattr__ :: bound method Quux.__delattr__(name: str, /) -> None
|
|
||||||
__dict__ :: dict[str, Any]
|
|
||||||
__dir__ :: bound method Quux.__dir__() -> Iterable[str]
|
|
||||||
__doc__ :: str | None
|
|
||||||
__eq__ :: bound method Quux.__eq__(value: object, /) -> bool
|
|
||||||
__format__ :: bound method Quux.__format__(format_spec: str, /) -> str
|
|
||||||
__getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
|
__getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
|
||||||
__getstate__ :: bound method Quux.__getstate__() -> object
|
|
||||||
__hash__ :: bound method Quux.__hash__() -> int
|
|
||||||
__init__ :: bound method Quux.__init__() -> Unknown
|
|
||||||
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
|
||||||
__module__ :: str
|
|
||||||
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
|
|
||||||
__new__ :: bound method Quux.__new__() -> Quux
|
|
||||||
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
|
|
||||||
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
|
|
||||||
__repr__ :: bound method Quux.__repr__() -> str
|
|
||||||
__setattr__ :: bound method Quux.__setattr__(name: str, value: Any, /) -> None
|
|
||||||
__sizeof__ :: bound method Quux.__sizeof__() -> int
|
|
||||||
__str__ :: bound method Quux.__str__() -> str
|
|
||||||
__subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
|
__subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
@ -2059,10 +2142,7 @@ bar(o<CURSOR>
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
assert_snapshot!(test.completions_without_builtins(), @"foo");
|
||||||
bar
|
|
||||||
foo
|
|
||||||
");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2097,8 +2177,6 @@ class C:
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||||
C
|
|
||||||
bar
|
|
||||||
foo
|
foo
|
||||||
self
|
self
|
||||||
");
|
");
|
||||||
|
|
@ -2133,8 +2211,6 @@ class C:
|
||||||
// that is only a method that can be called on
|
// that is only a method that can be called on
|
||||||
// `self`.
|
// `self`.
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
assert_snapshot!(test.completions_without_builtins(), @r"
|
||||||
C
|
|
||||||
bar
|
|
||||||
foo
|
foo
|
||||||
self
|
self
|
||||||
");
|
");
|
||||||
|
|
@ -2179,7 +2255,7 @@ hidden_<CURSOR>
|
||||||
|
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
test.completions_without_builtins(),
|
test.completions_without_builtins(),
|
||||||
@"<No completions found after filtering out completions>",
|
@"<No completions found>",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2199,7 +2275,10 @@ if sys.platform == \"not-my-current-platform\":
|
||||||
// TODO: ideally, `only_available_in_this_branch` should be available here, but we
|
// TODO: ideally, `only_available_in_this_branch` should be available here, but we
|
||||||
// currently make no effort to provide a good IDE experience within sections that
|
// currently make no effort to provide a good IDE experience within sections that
|
||||||
// are unreachable
|
// are unreachable
|
||||||
assert_snapshot!(test.completions_without_builtins(), @"sys");
|
assert_snapshot!(
|
||||||
|
test.completions_without_builtins(),
|
||||||
|
@"<No completions found after filtering out completions>",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2785,17 +2864,7 @@ f = Foo()
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: This should not have any completions suggested for it.
|
assert_snapshot!(test.completions_without_builtins(), @r"<No completions found>");
|
||||||
// We do correctly avoid giving `object.attr` completions here,
|
|
||||||
// but we instead fall back to scope based completions. Since
|
|
||||||
// we're inside a string, we should avoid giving completions at
|
|
||||||
// all.
|
|
||||||
assert_snapshot!(test.completions_without_builtins(), @r"
|
|
||||||
Foo
|
|
||||||
bar
|
|
||||||
f
|
|
||||||
foo
|
|
||||||
");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2818,6 +2887,26 @@ f"{f.<CURSOR>
|
||||||
test.assert_completions_include("method");
|
test.assert_completions_include("method");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_dot_attr3() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
foo = 1
|
||||||
|
bar = 2
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
def method(self): ...
|
||||||
|
|
||||||
|
f = Foo()
|
||||||
|
|
||||||
|
# T-string, this is an attribute access
|
||||||
|
t"{f.<CURSOR>
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
test.assert_completions_include("method");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_panic_for_attribute_table_that_contains_subscript() {
|
fn no_panic_for_attribute_table_that_contains_subscript() {
|
||||||
let test = cursor_test(
|
let test = cursor_test(
|
||||||
|
|
@ -3315,6 +3404,498 @@ from os.<CURSOR>
|
||||||
assert_eq!(completion.kind(&test.db), Some(CompletionKind::Struct));
|
assert_eq!(completion.kind(&test.db), Some(CompletionKind::Struct));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_comment() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
# zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"Foo.zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_incomplete_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print('zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print('Foo.zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_incomplete_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print('zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print('Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"\"\"zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"\"\"Foo.zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_incomplete_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"\"\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(\"\"\"Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print('''zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print('''Foo.zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_string_incomplete_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print('''zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print('''Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"{Foo} and Foo.zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_incomplete_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'{Foo} and Foo.zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_incomplete_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"\"\"zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"\"\"{Foo} and Foo.zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_incomplete_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"\"\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f\"\"\"{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'''zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'''{Foo} and Foo.zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_fstring_incomplete_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'''zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(f'''{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"{Foo} and Foo.zqzq<CURSOR>\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_incomplete_double_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'{Foo} and Foo.zqzq<CURSOR>')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_incomplete_single_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"\"\"zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"\"\"{Foo} and Foo.zqzq<CURSOR>\"\"\")
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_incomplete_double_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"\"\"zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t\"\"\"{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'''zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'''{Foo} and Foo.zqzq<CURSOR>''')
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completions_in_tstring_incomplete_single_triple_quote() {
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'''zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
|
||||||
|
let test = cursor_test(
|
||||||
|
"\
|
||||||
|
class Foo:
|
||||||
|
zqzqzq = 1
|
||||||
|
print(t'''{Foo} and Foo.zqzq<CURSOR>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
assert_snapshot!(test.completions_without_builtins(), @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: The methods below are getting somewhat ridiculous.
|
// NOTE: The methods below are getting somewhat ridiculous.
|
||||||
// We should refactor this by converting to using a builder
|
// We should refactor this by converting to using a builder
|
||||||
// to set different modes. ---AG
|
// to set different modes. ---AG
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,26 @@ impl QueryPattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_match(&self, symbol: &SymbolInfo<'_>) -> bool {
|
/// Create a new query pattern that matches all symbols.
|
||||||
|
pub fn matches_all_symbols() -> QueryPattern {
|
||||||
|
QueryPattern {
|
||||||
|
re: None,
|
||||||
|
original: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_match_symbol(&self, symbol: &SymbolInfo<'_>) -> bool {
|
||||||
self.is_match_symbol_name(&symbol.name)
|
self.is_match_symbol_name(&symbol.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_match_symbol_name(&self, symbol_name: &str) -> bool {
|
pub fn is_match_symbol_name(&self, symbol_name: &str) -> bool {
|
||||||
if let Some(ref re) = self.re {
|
if let Some(ref re) = self.re {
|
||||||
re.is_match(symbol_name)
|
re.is_match(symbol_name)
|
||||||
} else {
|
} else {
|
||||||
// This is a degenerate case. The only way
|
// This is a degenerate case. The only way
|
||||||
// we should get here is if the query string
|
// we should get here is if the query string
|
||||||
// was thousands (or more) characters long.
|
// was thousands (or more) characters long.
|
||||||
|
// ... or, if "typed" text could not be found.
|
||||||
symbol_name.contains(&self.original)
|
symbol_name.contains(&self.original)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +117,8 @@ impl FlatSymbols {
|
||||||
|
|
||||||
/// Returns a sequence of symbols that matches the given query.
|
/// Returns a sequence of symbols that matches the given query.
|
||||||
pub fn search(&self, query: &QueryPattern) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
|
pub fn search(&self, query: &QueryPattern) -> impl Iterator<Item = (SymbolId, SymbolInfo<'_>)> {
|
||||||
self.iter().filter(|(_, symbol)| query.is_match(symbol))
|
self.iter()
|
||||||
|
.filter(|(_, symbol)| query.is_match_symbol(symbol))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this flat sequence of symbols into a hierarchy of symbols.
|
/// Turns this flat sequence of symbols into a hierarchy of symbols.
|
||||||
|
|
|
||||||
|
|
@ -6253,7 +6253,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type `NoneType` / `None`
|
/// The type `NoneType` / `None`
|
||||||
pub(crate) fn none(db: &'db dyn Db) -> Type<'db> {
|
pub fn none(db: &'db dyn Db) -> Type<'db> {
|
||||||
KnownClass::NoneType.to_instance(db)
|
KnownClass::NoneType.to_instance(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ use std::time::Instant;
|
||||||
|
|
||||||
use lsp_types::request::Completion;
|
use lsp_types::request::Completion;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionParams,
|
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList,
|
||||||
CompletionResponse, Documentation, TextEdit, Url,
|
CompletionParams, CompletionResponse, Documentation, TextEdit, Url,
|
||||||
};
|
};
|
||||||
use ruff_db::source::{line_index, source_text};
|
use ruff_db::source::{line_index, source_text};
|
||||||
use ruff_source_file::OneIndexed;
|
use ruff_source_file::OneIndexed;
|
||||||
|
|
@ -100,7 +100,10 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let len = items.len();
|
let len = items.len();
|
||||||
let response = CompletionResponse::Array(items);
|
let response = CompletionResponse::List(CompletionList {
|
||||||
|
is_incomplete: true,
|
||||||
|
items,
|
||||||
|
});
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Completions request returned {len} suggestions in {elapsed:?}",
|
"Completions request returned {len} suggestions in {elapsed:?}",
|
||||||
elapsed = Instant::now().duration_since(start)
|
elapsed = Instant::now().duration_since(start)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue