From f4fc810f193e1abb889a79d1f300780d8e152071 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Wed, 19 Feb 2025 13:13:42 -0800 Subject: [PATCH 1/3] REPL completions: Enter import mode only when cursor beyond "import" Fixes #55842 --- stdlib/REPL/src/REPLCompletions.jl | 13 ++++++++----- stdlib/REPL/test/replcompletions.jl | 8 ++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index e70eb8dd97927..8fee05c399417 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -939,13 +939,16 @@ const superscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(superscripts)) * "]+\\z") # Aux function to detect whether we're right after a using or import keyword -function get_import_mode(s::String) +function get_import_mode(s::String, pos::Int) # allow all of these to start with leading whitespace and macros like @eval and @eval( # ^\s*(?:@\w+\s*(?:\(\s*)?)? + # Do not enter import mode unless cursor beyond import keyword + beyond_kw(m) = pos >= m.offsets[1] + length(m[1]) + # match simple cases like `using |` and `import |` mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s) - if mod_import_match_simple !== nothing + if mod_import_match_simple !== nothing && beyond_kw(mod_import_match_simple) if mod_import_match_simple[1] == "using" return :using_module else @@ -954,7 +957,7 @@ function get_import_mode(s::String) end # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |` mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s) - if mod_import_match !== nothing + if mod_import_match !== nothing && beyond_kw(mod_import_match) if mod_import_match.captures[1] == "using" return :using_module else @@ -963,7 +966,7 @@ function get_import_mode(s::String) end # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|` name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s) - if name_import_match !== nothing + if name_import_match !== nothing && beyond_kw(name_import_match) if name_import_match[1] == "using" return :using_name else @@ -1462,7 +1465,7 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif separatorpos = something(findprev(isequal('.'), string, pos), 0) namepos = max(startpos, separatorpos+1) name = string[namepos:pos] - import_mode = get_import_mode(string) + import_mode = get_import_mode(string, pos) if import_mode === :using_module || import_mode === :import_module # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`: # Let's look only for packages and modules we can reach from here diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 59e994f88945b..4f3d4db1de7d3 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -180,6 +180,8 @@ end test_complete(s) = map_completion_text(@inferred(completions(s, lastindex(s)))) test_scomplete(s) = map_completion_text(@inferred(shell_completions(s, lastindex(s)))) +# | is reserved in test_complete_pos +test_complete_pos(s) = map_completion_text(@inferred(completions(replace(s, '|' => ""), findfirst('|', s)-1))) test_complete_context(s, m=@__MODULE__; shift::Bool=true) = map_completion_text(@inferred(completions(s,lastindex(s), m, shift))) test_complete_foo(s) = test_complete_context(s, Main.CompletionFoo) @@ -2484,3 +2486,9 @@ let (c, r, res) = test_complete_context("global xxx::Number = Base.", Main) @test res @test "pi" ∈ c end + +# #57473 +let (c, r) = test_complete_pos("@tim| using Date") + @test "@time" in c + @test r == 1:4 +end From 5f757c5ebea0094aa23c934999e3848a76d9bd9a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 26 Feb 2025 11:24:53 -0800 Subject: [PATCH 2/3] Update stdlib/REPL/src/REPLCompletions.jl --- stdlib/REPL/src/REPLCompletions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 8fee05c399417..bd734cd52b895 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -944,7 +944,7 @@ function get_import_mode(s::String, pos::Int) # ^\s*(?:@\w+\s*(?:\(\s*)?)? # Do not enter import mode unless cursor beyond import keyword - beyond_kw(m) = pos >= m.offsets[1] + length(m[1]) + beyond_kw(m) = pos >= m.offsets[1] + sizeof(m[1]) # match simple cases like `using |` and `import |` mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s) From 6c5947438cf6b012b3c9d3e29da64d9f29917284 Mon Sep 17 00:00:00 2001 From: Sam Schweigel Date: Wed, 26 Feb 2025 12:18:39 -0800 Subject: [PATCH 3/3] REPL completion: make import mode more forgiving Don't require a using/import to be on its own line to enter import mode; instead look for partial import expressions and require the cursor be in the range where modules/imported names would go. Fixes #56389 (including the fix for #55842) --- stdlib/REPL/src/REPLCompletions.jl | 44 +++++++++-------------------- stdlib/REPL/test/replcompletions.jl | 14 +++++++++ 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index bd734cd52b895..d634241086aef 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -940,40 +940,22 @@ const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? " # Aux function to detect whether we're right after a using or import keyword function get_import_mode(s::String, pos::Int) - # allow all of these to start with leading whitespace and macros like @eval and @eval( - # ^\s*(?:@\w+\s*(?:\(\s*)?)? - - # Do not enter import mode unless cursor beyond import keyword - beyond_kw(m) = pos >= m.offsets[1] + sizeof(m[1]) - - # match simple cases like `using |` and `import |` - mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s) - if mod_import_match_simple !== nothing && beyond_kw(mod_import_match_simple) - if mod_import_match_simple[1] == "using" - return :using_module - else - return :import_module - end - end - # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |` - mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s) - if mod_import_match !== nothing && beyond_kw(mod_import_match) - if mod_import_match.captures[1] == "using" - return :using_module - else - return :import_module + # Capture group 1 will be returned, group 2 is where the cursor should be. + function match_pos(re) + for m in eachmatch(re, s, overlap=true) + m !== nothing || continue + pos in range(m.offsets[2], length=sizeof(m[2])) || continue + return m[1] end end + + # match module import statements like `using |`, `import |`, `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |` + m = match_pos(r"\b(using|import)(\s+(?:[\w\.]+(?:\s*,\s*[\w\.]+)*(:?\s*,)?\s*)?)") + m !== nothing && return m == "using" ? :using_module : :import_module + # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|` - name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s) - if name_import_match !== nothing && beyond_kw(name_import_match) - if name_import_match[1] == "using" - return :using_name - else - return :import_name - end - end - return nothing + m = match_pos(r"\b(using|import)\s+(?:[\w\.]+(?:\s*,\s*[\w\.]+)*)\s*(:\s*(?:[\w@!\s,]+)*)") + m !== nothing && return m == "using" ? :using_name : :import_name end function close_path_completion(dir, path, str, pos) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 4f3d4db1de7d3..11154323d704a 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -2492,3 +2492,17 @@ let (c, r) = test_complete_pos("@tim| using Date") @test "@time" in c @test r == 1:4 end + +# #56389 +let s = "begin\n using Linear" + c, r = test_complete(s) + @test "LinearAlgebra" in c + @test r == 15:20 + @test s[r] == "Linear" +end +let s = "using .CompletionFoo: bar, type_" + c, r = test_complete(s) + @test "type_test" in c + @test r == 28:32 + @test s[r] == "type_" +end