[ty] Provide completions at the end of the file

This commit is contained in:
David Peter 2025-10-17 22:32:48 +02:00
parent 8ca2b5555d
commit 0d159b2f38
1 changed files with 30 additions and 2 deletions

View File

@ -409,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 = tokens_start_before(parsed.tokens(), offset); let before = strip_eof_newline(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
@ -627,6 +627,19 @@ fn tokens_start_before(tokens: &Tokens, offset: TextSize) -> &[Token] {
&tokens[..idx] &tokens[..idx]
} }
/// Strip trailing Newline token that the parser automatically adds at the
/// end of the file.
fn strip_eof_newline(tokens: &[Token]) -> &[Token] {
if tokens
.last()
.is_some_and(|t| t.kind() == TokenKind::Newline)
{
&tokens[..tokens.len() - 1]
} else {
tokens
}
}
/// Returns a suffix of `tokens` corresponding to the `kinds` given. /// Returns a suffix of `tokens` corresponding to the `kinds` given.
/// ///
/// If a suffix of `tokens` with the given `kinds` could not be found, /// If a suffix of `tokens` with the given `kinds` could not be found,
@ -800,7 +813,7 @@ fn find_typed_text(
offset: TextSize, offset: TextSize,
) -> Option<String> { ) -> Option<String> {
let source = source_text(db, file); let source = source_text(db, file);
let tokens = tokens_start_before(parsed.tokens(), offset); let tokens = strip_eof_newline(tokens_start_before(parsed.tokens(), offset));
let last = tokens.last()?; let last = tokens.last()?;
if !matches!(last.kind(), TokenKind::Name) { if !matches!(last.kind(), TokenKind::Name) {
return None; return None;
@ -3945,6 +3958,21 @@ def f[T](x: T):
test.assert_completions_include("__repr__"); test.assert_completions_include("__repr__");
} }
#[test]
fn attribute_at_eof_without_trailing_newline() {
// Regression test for https://github.com/astral-sh/ty/issues/1392
// This test ensures completions work when the cursor is at the end
// of the file with no trailing newline.
let test = cursor_test("def f(msg: str):\n msg.<CURSOR>");
test.assert_completions_include("upper");
test.assert_completions_include("capitalize");
let test = cursor_test("def f(msg: str):\n msg.u<CURSOR>");
test.assert_completions_include("upper");
test.assert_completions_do_not_include("capitalize");
}
// 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