Skip to content

Commit aaa906c

Browse files
committed
REPL: Allow completions to replace arbitrary regions of text
Adds another permitted return type for complete_line, where the second element of the tuple is a Region (a Pair{Int, Int}) describing the region of text to be replaced. This is useful for making completions work consistently when the closing delimiter may or may not be present: the cursor can be made to "jump" out of the delimiters regardless of whether it is there already. "exam| =TAB=> "example.jl"| "exam|" =TAB=> "example.jl"|
1 parent 28d3bd5 commit aaa906c

File tree

2 files changed

+43
-39
lines changed

2 files changed

+43
-39
lines changed

stdlib/REPL/src/LineEdit.jl

+31-29
Original file line numberDiff line numberDiff line change
@@ -391,16 +391,21 @@ function complete_line(s::MIState)
391391
end
392392
end
393393

394+
# Old complete_line return type: Vector{String}, String, Bool
395+
# New complete_line return type: NamedCompletion{String}, String, Bool
396+
# OR NamedCompletion{String}, Region, Bool
397+
#
394398
# due to close coupling of the Pkg ReplExt `complete_line` can still return a vector of strings,
395399
# so we convert those in this helper
396-
function complete_line_named(args...; kwargs...)::Tuple{Vector{NamedCompletion},String,Bool}
397-
result = complete_line(args...; kwargs...)::Union{Tuple{Vector{NamedCompletion},String,Bool},Tuple{Vector{String},String,Bool}}
398-
if result isa Tuple{Vector{NamedCompletion},String,Bool}
399-
return result
400-
else
401-
completions, partial, should_complete = result
402-
return map(NamedCompletion, completions), partial, should_complete
403-
end
400+
function complete_line_named(c, s, args...; kwargs...)::Tuple{Vector{NamedCompletion},Region,Bool}
401+
r1, r2, should_complete = complete_line(c, s, args...; kwargs...)::Union{
402+
Tuple{Vector{String}, String, Bool},
403+
Tuple{Vector{NamedCompletion}, String, Bool},
404+
Tuple{Vector{NamedCompletion}, Region, Bool},
405+
}
406+
completions = (r1 isa Vector{String} ? map(NamedCompletion, r1) : r1)
407+
r = (r2 isa String ? (position(s)-sizeof(r2) => position(s)) : r2)
408+
completions, r, should_complete
404409
end
405410

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

431436
# only allow one task to generate hints at a time and check around lock
432437
# if the user has pressed a key since the hint was requested, to skip old completions
433438
next_key_pressed() && return
434439
@lock s.hint_generation_lock begin
435440
next_key_pressed() && return
436-
named_completions, partial, should_complete = try
441+
named_completions, reg, should_complete = try
437442
complete_line_named(st.p.complete, st, s.active_module; hint = true)
438443
catch
439444
lock_clear_hint()
@@ -448,21 +453,19 @@ function check_show_hint(s::MIState)
448453
return
449454
end
450455
# Don't complete for single chars, given e.g. `x` completes to `xor`
451-
if length(partial) > 1 && should_complete
456+
if reg.second - reg.first > 1 && should_complete
452457
singlecompletion = length(completions) == 1
453458
p = singlecompletion ? completions[1] : common_prefix(completions)
454459
if singlecompletion || p in completions # i.e. complete `@time` even though `@time_imports` etc. exists
455-
# The completion `p` and the input `partial` may not share the same initial
460+
# The completion `p` and the region `reg` may not share the same initial
456461
# characters, for instance when completing to subscripts or superscripts.
457462
# So, in general, make sure that the hint starts at the correct position by
458463
# incrementing its starting position by as many characters as the input.
459-
startind = 1 # index of p from which to start providing the hint
460-
maxind = ncodeunits(p)
461-
for _ in partial
462-
startind = nextind(p, startind)
463-
startind > maxind && break
464-
end
464+
maxind = lastindex(p)
465+
startind = sizeof(content(s, reg))
465466
if startind maxind # completion on a complete name returns itself so check that there's something to hint
467+
# index of p from which to start providing the hint
468+
startind = nextind(p, startind)
466469
hint = p[startind:end]
467470
next_key_pressed() && return
468471
@lock s.line_modify_lock begin
@@ -491,25 +494,24 @@ function clear_hint(s::ModeState)
491494
end
492495

493496
function complete_line(s::PromptState, repeats::Int, mod::Module; hint::Bool=false)
494-
completions, partial, should_complete = complete_line_named(s.p.complete, s, mod; hint)
497+
completions, reg, should_complete = complete_line_named(s.p.complete, s, mod; hint)
495498
isempty(completions) && return false
496499
if !should_complete
497500
# should_complete is false for cases where we only want to show
498501
# a list of possible completions but not complete, e.g. foo(\t
499502
show_completions(s, completions)
500503
elseif length(completions) == 1
501504
# Replace word by completion
502-
prev_pos = position(s)
503505
push_undo(s)
504-
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
506+
edit_splice!(s, reg, completions[1].completion)
505507
else
506508
p = common_prefix(completions)
509+
partial = content(s, reg.first => min(bufend(s), reg.first + sizeof(p)))
507510
if !isempty(p) && p != partial
508511
# All possible completions share the same prefix, so we might as
509-
# well complete that
510-
prev_pos = position(s)
512+
# well complete that.
511513
push_undo(s)
512-
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, p)
514+
edit_splice!(s, reg, p)
513515
elseif repeats > 0
514516
show_completions(s, completions)
515517
end
@@ -830,12 +832,12 @@ function edit_move_right(m::MIState)
830832
refresh_line(s)
831833
return true
832834
else
833-
completions, partial, should_complete = complete_line(s.p.complete, s, m.active_module)
834-
if should_complete && eof(buf) && length(completions) == 1 && length(partial) > 1
835+
completions, reg, should_complete = complete_line(s.p.complete, s, m.active_module)
836+
if should_complete && eof(buf) && length(completions) == 1 && reg.second - reg.first > 1
835837
# Replace word by completion
836838
prev_pos = position(s)
837839
push_undo(s)
838-
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
840+
edit_splice!(s, (prev_pos - reg.second + reg.first) => prev_pos, completions[1].completion)
839841
refresh_line(state(s))
840842
return true
841843
else
@@ -2255,12 +2257,12 @@ setmodifiers!(c) = nothing
22552257

22562258
# Search Mode completions
22572259
function complete_line(s::SearchState, repeats, mod::Module; hint::Bool=false)
2258-
completions, partial, should_complete = complete_line(s.histprompt.complete, s, mod; hint)
2260+
completions, reg, should_complete = complete_line(s.histprompt.complete, s, mod; hint)
22592261
# For now only allow exact completions in search mode
22602262
if length(completions) == 1
22612263
prev_pos = position(s)
22622264
push_undo(s)
2263-
edit_splice!(s, (prev_pos - sizeof(partial)) => prev_pos, completions[1].completion)
2265+
edit_splice!(s, (prev_pos - reg.second - reg.first) => prev_pos, completions[1].completion)
22642266
return true
22652267
end
22662268
return false

stdlib/REPL/src/REPL.jl

+12-10
Original file line numberDiff line numberDiff line change
@@ -782,27 +782,29 @@ end
782782

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

785+
# Convert inclusive-inclusive 1-based char indexing to inclusive-exclusuive byte Region.
786+
to_region(s, r) = first(r)-1 => (length(r) > 0 ? nextind(s, last(r))-1 : first(r)-1)
787+
785788
function complete_line(c::REPLCompletionProvider, s::PromptState, mod::Module; hint::Bool=false)
786-
partial = beforecursor(s.input_buffer)
787789
full = LineEdit.input_string(s)
788-
ret, range, should_complete = completions(full, lastindex(partial), mod, c.modifiers.shift, hint)
790+
ret, range, should_complete = completions(full, thisind(full, position(s)), mod, c.modifiers.shift, hint)
791+
range = to_region(full, range)
789792
c.modifiers = LineEdit.Modifiers()
790-
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), partial[range], should_complete
793+
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
791794
end
792795

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

801803
function complete_line(c::LatexCompletions, s; hint::Bool=false)
802-
partial = beforecursor(LineEdit.buffer(s))
803804
full = LineEdit.input_string(s)::String
804-
ret, range, should_complete = bslash_completions(full, lastindex(partial), hint)[2]
805-
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), partial[range], should_complete
805+
ret, range, should_complete = bslash_completions(full, thisind(full, position(s)), hint)[2]
806+
range = to_region(full, range)
807+
return unique!(LineEdit.NamedCompletion[named_completion(x) for x in ret]), range, should_complete
806808
end
807809

808810
with_repl_linfo(f, repl) = f(outstream(repl))

0 commit comments

Comments
 (0)