Skip to content

JuliaSyntax parser-based REPL completions overhaul #57767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b93d838
REPL: Allow completions to replace arbitrary regions of text
xal-0 Mar 13, 2025
c539ec4
REPL: JuliaSyntax completions overhaul
xal-0 Mar 13, 2025
304aefa
REPL: Add tests for #55420, #55429, #55842, #57307, #57624
xal-0 Mar 14, 2025
498aec3
Add test for #57772
xal-0 Mar 14, 2025
5449069
REPL: Add additional tests from #55518 discussion
xal-0 Mar 14, 2025
a54d2c4
REPL: Avoid triggering method completion when cursor on function name
xal-0 Mar 15, 2025
3fad260
Update stdlib/REPL/test/replcompletions.jl
xal-0 Mar 17, 2025
0b291a9
REPL: Catch IOError, ArgumentError when calling ispath
xal-0 Mar 19, 2025
aef610e
Update stdlib/REPL/test/replcompletions.jl
xal-0 Mar 19, 2025
50788f8
Merge branch 'master' into juliasyntax-repl
xal-0 Mar 19, 2025
54b8f04
Merge branch 'master' into juliasyntax-repl
xal-0 Mar 24, 2025
9980020
REPL: Escape path completions after joining dir and path
xal-0 Mar 26, 2025
69db0a5
Merge remote-tracking branch 'upstream/master' into juliasyntax-repl
xal-0 Mar 26, 2025
d48fd5e
REPL: new backslash escape hack for Windows Pkg completions
xal-0 Mar 26, 2025
8cd20d4
Update stdlib/REPL/src/REPLCompletions.jl
xal-0 Mar 31, 2025
8dee0ff
Use '/' as directory separator for shell completions
xal-0 Apr 2, 2025
e2eef34
Merge remote-tracking branch 'upstream/master' into juliasyntax-repl
xal-0 Apr 2, 2025
14dde09
Restore previous Pkg.jl path completion
xal-0 Apr 7, 2025
8ee2978
Merge remote-tracking branch 'upstream/master' into juliasyntax-repl
xal-0 Apr 7, 2025
5839241
Allow REPL completions inside string interpolation
xal-0 Apr 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -391,16 +391,21 @@ function complete_line(s::MIState)
end
end

# Old complete_line return type: Vector{String}, String, Bool
# New complete_line return type: NamedCompletion{String}, String, Bool
# OR NamedCompletion{String}, Region, Bool
#
# due to close coupling of the Pkg ReplExt `complete_line` can still return a vector of strings,
# so we convert those in this helper
function complete_line_named(args...; kwargs...)::Tuple{Vector{NamedCompletion},String,Bool}
result = complete_line(args...; kwargs...)::Union{Tuple{Vector{NamedCompletion},String,Bool},Tuple{Vector{String},String,Bool}}
if result isa Tuple{Vector{NamedCompletion},String,Bool}
return result
else
completions, partial, should_complete = result
return map(NamedCompletion, completions), partial, should_complete
end
function complete_line_named(c, s, args...; kwargs...)::Tuple{Vector{NamedCompletion},Region,Bool}
r1, r2, should_complete = complete_line(c, s, args...; kwargs...)::Union{
Tuple{Vector{String}, String, Bool},
Tuple{Vector{NamedCompletion}, String, Bool},
Tuple{Vector{NamedCompletion}, Region, Bool},
}
completions = (r1 isa Vector{String} ? map(NamedCompletion, r1) : r1)
r = (r2 isa String ? (position(s)-sizeof(r2) => position(s)) : r2)
completions, r, should_complete
end

# checks for a hint and shows it if appropriate.
Expand All @@ -426,14 +431,14 @@ function check_show_hint(s::MIState)
return
end
t_completion = Threads.@spawn :default begin
named_completions, partial, should_complete = nothing, nothing, nothing
named_completions, reg, should_complete = nothing, nothing, nothing

# only allow one task to generate hints at a time and check around lock
# if the user has pressed a key since the hint was requested, to skip old completions
next_key_pressed() && return
@lock s.hint_generation_lock begin
next_key_pressed() && return
named_completions, partial, should_complete = try
named_completions, reg, should_complete = try
complete_line_named(st.p.complete, st, s.active_module; hint = true)
catch
lock_clear_hint()
Expand All @@ -448,21 +453,19 @@ function check_show_hint(s::MIState)
return
end
# Don't complete for single chars, given e.g. `x` completes to `xor`
if length(partial) > 1 && should_complete
if reg.second - reg.first > 1 && should_complete
singlecompletion = length(completions) == 1
p = singlecompletion ? completions[1] : common_prefix(completions)
if singlecompletion || p in completions # i.e. complete `@time` even though `@time_imports` etc. exists
# The completion `p` and the input `partial` may not share the same initial
# The completion `p` and the region `reg` may not share the same initial
# characters, for instance when completing to subscripts or superscripts.
# So, in general, make sure that the hint starts at the correct position by
# incrementing its starting position by as many characters as the input.
startind = 1 # index of p from which to start providing the hint
maxind = ncodeunits(p)
for _ in partial
startind = nextind(p, startind)
startind > maxind && break
end
maxind = lastindex(p)
startind = sizeof(content(s, reg))
if startind ≤ maxind # completion on a complete name returns itself so check that there's something to hint
# index of p from which to start providing the hint
startind = nextind(p, startind)
hint = p[startind:end]
next_key_pressed() && return
@lock s.line_modify_lock begin
Expand Down Expand Up @@ -491,25 +494,24 @@ function clear_hint(s::ModeState)
end

function complete_line(s::PromptState, repeats::Int, mod::Module; hint::Bool=false)
completions, partial, should_complete = complete_line_named(s.p.complete, s, mod; hint)
completions, reg, should_complete = complete_line_named(s.p.complete, s, mod; hint)
isempty(completions) && return false
if !should_complete
# should_complete is false for cases where we only want to show
# a list of possible completions but not complete, e.g. foo(\t
show_completions(s, completions)
elseif length(completions) == 1
# Replace word by completion
prev_pos = position(s)
push_undo(s)
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
edit_splice!(s, reg, completions[1].completion)
else
p = common_prefix(completions)
partial = content(s, reg.first => min(bufend(s), reg.first + sizeof(p)))
if !isempty(p) && p != partial
# All possible completions share the same prefix, so we might as
# well complete that
prev_pos = position(s)
# well complete that.
push_undo(s)
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, p)
edit_splice!(s, reg, p)
elseif repeats > 0
show_completions(s, completions)
end
Expand Down Expand Up @@ -830,12 +832,12 @@ function edit_move_right(m::MIState)
refresh_line(s)
return true
else
completions, partial, should_complete = complete_line(s.p.complete, s, m.active_module)
if should_complete && eof(buf) && length(completions) == 1 && length(partial) > 1
completions, reg, should_complete = complete_line(s.p.complete, s, m.active_module)
if should_complete && eof(buf) && length(completions) == 1 && reg.second - reg.first > 1
# Replace word by completion
prev_pos = position(s)
push_undo(s)
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
edit_splice!(s, (prev_pos - reg.second + reg.first) => prev_pos, completions[1].completion)
refresh_line(state(s))
return true
else
Expand Down Expand Up @@ -2255,12 +2257,12 @@ setmodifiers!(c) = nothing

# Search Mode completions
function complete_line(s::SearchState, repeats, mod::Module; hint::Bool=false)
completions, partial, should_complete = complete_line(s.histprompt.complete, s, mod; hint)
completions, reg, should_complete = complete_line(s.histprompt.complete, s, mod; hint)
# For now only allow exact completions in search mode
if length(completions) == 1
prev_pos = position(s)
push_undo(s)
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
edit_splice!(s, (prev_pos - reg.second - reg.first) => prev_pos, completions[1].completion)
return true
end
return false
Expand Down
23 changes: 13 additions & 10 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import .LineEdit:
PromptState,
mode_idx

include("SyntaxUtil.jl")
include("REPLCompletions.jl")
using .REPLCompletions

Expand Down Expand Up @@ -799,27 +800,29 @@ end

beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1])

# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusive byte Region.
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)

function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
partial = beforecursor(s.input_buffer)
full = LineEdit.input_string(s)
ret, range, should_complete = completions(full, lastindex(partial), mod, c.modifiers.shift, hint)
ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
range = to_region(full, range)
c.modifiers = LineEdit.Modifiers()
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), partial[range], should_complete
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
end

function complete_line(c::ShellCompletionProvider, s::PromptState; hint::Bool=false)
# First parse everything up to the current position
partial = beforecursor(s.input_buffer)
full = LineEdit.input_string(s)
ret, range, should_complete = shell_completions(full, lastindex(partial), hint)
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), partial[range], should_complete
ret, range, should_complete = shell_completions(full, thisind(full, position(s)), hint)
range = to_region(full, range)
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
end

function complete_line(c::LatexCompletions, s; hint::Bool=false)
partial = beforecursor(LineEdit.buffer(s))
full = LineEdit.input_string(s)::String
ret, range, should_complete = bslash_completions(full, lastindex(partial), hint)[2]
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), partial[range], should_complete
ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
range = to_region(full, range)
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
end

with_repl_linfo(f, repl) = f(outstream(repl))
Expand Down
Loading