From 90f39dac730043d139251bddda825a4839b15f06 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Fri, 18 Jul 2025 17:27:09 -0700 Subject: [PATCH] [REPL] Handle empty completion, keywords better When the context is empty, (like ""), return only names local to the module (fixes #58931). If the cursor is on something that "looks like" an identifier, like a boolean or one of the keywords, treat it as if it was one for completion purposes. Typing a keyword and hitting tab no longer returns the completions for the empty input (fixes #58309, #58832). --- stdlib/REPL/src/REPLCompletions.jl | 11 ++++++----- stdlib/REPL/test/replcompletions.jl | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 7de1246566b48..e05d0435d81d8 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -186,7 +186,7 @@ function complete_symbol!(suggestions::Vector{Completion}, complete_modules_only::Bool=false, shift::Bool=false) local mod, t, val - complete_internal_only = false + complete_internal_only = isempty(name) if prefix !== nothing res = repl_eval_ex(prefix, context_module) res === nothing && return Completion[] @@ -1095,18 +1095,19 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif # Symbol completion # TODO: Should completions replace the identifier at the cursor? + looks_like_ident = Base.isidentifier(@view string[intersect(char_range(cur), 1:pos)]) if cur.parent !== nothing && kind(cur.parent) == K"var" # Replace the entire var"foo", but search using only "foo". r = intersect(char_range(cur.parent), 1:pos) r2 = char_range(children_nt(cur.parent)[1]) s = string[intersect(r2, 1:pos)] - elseif kind(cur) in KSet"Identifier @" - r = intersect(char_range(cur), 1:pos) - s = string[r] elseif kind(cur) == K"MacroName" # Include the `@` r = intersect(prevind(string, cur.position):char_last(cur), 1:pos) s = string[r] + elseif looks_like_ident || kind(cur) in KSet"Bool Identifier @" + r = intersect(char_range(cur), 1:pos) + s = string[r] else r = nextind(string, pos):pos s = "" @@ -1114,7 +1115,7 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif complete_modules_only = false prefix = node_prefix(cur, context_module) - comp_keywords = prefix === nothing + comp_keywords = prefix === nothing && !isempty(s) # Complete loadable module names: # import Mod TAB diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 43bea08e285f1..2225417ddcec6 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -2720,3 +2720,25 @@ let s = "foo58296(findfi" @test "findfirst" in c @test r == 10:15 end + +# #58931 - only show local names when completing the empty string +let s = "" + c, r = test_complete_foo(s) + @test "test" in c + @test !("rand" in c) +end + +# #58309, #58832 - don't show every name when completing after a full keyword +let s = "true" # bool is a little different (Base.isidentifier special case) + c, r = test_complete(s) + @test "trues" in c + @test "true" in c + @test !("rand" in c) +end + +let s = "for" + c, r = test_complete(s) + @test "for" in c + @test "foreach" in c + @test !("rand" in c) +end