Skip to content

Commit

Permalink
This attempts to improve inference in REPL. There are several pieces:
Browse files Browse the repository at this point in the history
- type-stable interfaces have beend added for abstract types
  `REPLCompletions.Completion` and `TerminalMenus.AbstractMenu`,
- by defining the `Options` struct prior to creating the `LineEdit` module,
  we gain the ability to improve many inference results
- several methods assume the presence of fields that are not guaranteed
  for all subtypes. Restrict to subtypes that have the requisite fields.
- because REPL is compiled with low optimization settings, to prevent loss
  of type information it's helpful to declare more types for input
  arguments on internal methods.
- in a couple of places, avoiding Unions makes later steps easier
- numerous places still need manual type annotations added
  • Loading branch information
timholy committed Aug 17, 2020
1 parent d47f7d0 commit 0de90e9
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 152 deletions.
77 changes: 39 additions & 38 deletions stdlib/REPL/src/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module LineEdit

import ..REPL
using REPL: AbstractREPL
using REPL: AbstractREPL, Options

using ..Terminals
import ..Terminals: raw!, width, height, cmove, getX,
Expand Down Expand Up @@ -91,9 +91,9 @@ options(s::PromptState) =
if isdefined(s.p, :repl) && isdefined(s.p.repl, :options)
# we can't test isa(s.p.repl, LineEditREPL) as LineEditREPL is defined
# in the REPL module
s.p.repl.options
s.p.repl.options::Options
else
REPL.GlobalOptions
REPL.GlobalOptions::Options
end

function setmark(s::MIState, guess_region_active::Bool=true)
Expand Down Expand Up @@ -149,7 +149,7 @@ struct EmptyHistoryProvider <: HistoryProvider end

reset_state(::EmptyHistoryProvider) = nothing

complete_line(c::EmptyCompletionProvider, s) = [], true, true
complete_line(c::EmptyCompletionProvider, s) = String[], "", true

terminal(s::IO) = s
terminal(s::PromptState) = s.terminal
Expand All @@ -162,27 +162,28 @@ function beep(s::PromptState, duration::Real=options(s).beep_duration,
use_current::Bool=options(s).beep_use_current)
isinteractive() || return # some tests fail on some platforms
s.beeping = min(s.beeping + duration, maxduration)
@async begin
trylock(s.refresh_lock) || return
try
orig_prefix = s.p.prompt_prefix
colors = Base.copymutable(colors)
use_current && push!(colors, prompt_string(orig_prefix))
i = 0
while s.beeping > 0.0
prefix = colors[mod1(i+=1, end)]
s.p.prompt_prefix = prefix
let colors = Base.copymutable(colors)
@async begin
trylock(s.refresh_lock) || return
try
orig_prefix = s.p.prompt_prefix
use_current && push!(colors, prompt_string(orig_prefix))
i = 0
while s.beeping > 0.0
prefix = colors[mod1(i+=1, end)]
s.p.prompt_prefix = prefix
refresh_multi_line(s, beeping=true)
sleep(blink)
s.beeping -= blink
end
s.p.prompt_prefix = orig_prefix
refresh_multi_line(s, beeping=true)
sleep(blink)
s.beeping -= blink
s.beeping = 0.0
catch e
Base.showerror(stdout, e, catch_backtrace())
finally
unlock(s.refresh_lock)
end
s.p.prompt_prefix = orig_prefix
refresh_multi_line(s, beeping=true)
s.beeping = 0.0
catch e
Base.showerror(stdout, e, catch_backtrace())
finally
unlock(s.refresh_lock)
end
end
nothing
Expand Down Expand Up @@ -290,7 +291,7 @@ function common_prefix(completions)
end

# Show available completions
function show_completions(s::PromptState, completions)
function show_completions(s::PromptState, completions::Vector{String})
colmax = maximum(map(length, completions))
num_cols = max(div(width(terminal(s)), colmax+2), 1)
entries_per_col, r = divrem(length(completions), num_cols)
Expand Down Expand Up @@ -325,8 +326,8 @@ function complete_line(s::MIState)
end
end

function complete_line(s::PromptState, repeats)
completions, partial, should_complete = complete_line(s.p.complete, s)
function complete_line(s::PromptState, repeats::Int)
completions, partial, should_complete = complete_line(s.p.complete, s)::Tuple{Vector{String},String,Bool}
isempty(completions) && return false
if !should_complete
# should_complete is false for cases where we only want to show
Expand Down Expand Up @@ -382,7 +383,7 @@ refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState; kw...) = (@asser

function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf::IOBuffer,
state::InputAreaState, prompt = "";
indent = 0, region_active = false)
indent::Int = 0, region_active::Bool = false)
_clear_input_area(termbuf, state)

cols = width(terminal)
Expand All @@ -395,7 +396,7 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf
regstart, regstop = region(buf)
written = 0
# Write out the prompt string
lindent = write_prompt(termbuf, prompt, hascolor(terminal))
lindent = write_prompt(termbuf, prompt, hascolor(terminal))::Int
# Count the '\n' at the end of the line if the terminal emulator does (specific to DOS cmd prompt)
miscountnl = @static Sys.iswindows() ? (isa(Terminals.pipe_reader(terminal), Base.TTY) && !Base.ispty(Terminals.pipe_reader(terminal))) : false

Expand Down Expand Up @@ -480,14 +481,14 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf
return InputAreaState(cur_row, curs_row)
end

function highlight_region(lwrite::AbstractString, regstart::Int, regstop::Int, written::Int, slength::Int)
function highlight_region(lwrite::Union{String,SubString{String}}, regstart::Int, regstop::Int, written::Int, slength::Int)
if written <= regstop <= written+slength
i = thisind(lwrite, regstop-written)
lwrite = lwrite[1:i] * Base.disable_text_style[:reverse] * lwrite[nextind(lwrite, i):end]
lwrite = lwrite[1:i] * Base.disable_text_style[:reverse] * lwrite[nextind(lwrite, i)::Int:end]
end
if written <= regstart <= written+slength
i = thisind(lwrite, regstart-written)
lwrite = lwrite[1:i] * Base.text_colors[:reverse] * lwrite[nextind(lwrite, i):end]
lwrite = lwrite[1:i] * Base.text_colors[:reverse] * lwrite[nextind(lwrite, i)::Int:end]
end
return lwrite
end
Expand Down Expand Up @@ -686,7 +687,7 @@ end
# splice! for IOBuffer: convert from close-open region to index, update the size,
# and keep the cursor position and mark stable with the text
# returns the removed portion as a String
function edit_splice!(s, r::Region=region(s), ins::AbstractString = ""; rigid_mark::Bool=true)
function edit_splice!(s::Union{IOBuffer,MIState,PromptState}, r::Region=region(s), ins::String = ""; rigid_mark::Bool=true)
A, B = first(r), last(r)
A >= B && isempty(ins) && return String(ins)
buf = buffer(s)
Expand Down Expand Up @@ -809,8 +810,8 @@ _notspace(c) = c != _space
beginofline(buf, pos=position(buf)) = something(findprev(isequal(_newline), buf.data, pos), 0)

function endofline(buf, pos=position(buf))
eol = findnext(isequal(_newline), buf.data[pos+1:buf.size], 1)
eol === nothing ? buf.size : pos + eol - 1
eol = findnext(isequal(_newline), buf.data, pos+1)
eol === nothing ? buf.size : eol
end

function edit_backspace(buf::IOBuffer, align::Bool=false, adjust::Bool=false)
Expand Down Expand Up @@ -1305,7 +1306,7 @@ end
# returns the width of the written prompt
function write_prompt(terminal, s::Union{AbstractString,Function}, color::Bool)
@static Sys.iswindows() && _reset_console_mode()
promptstr = prompt_string(s)
promptstr = prompt_string(s)::String
write(terminal, promptstr)
return textwidth(promptstr)
end
Expand Down Expand Up @@ -1731,8 +1732,8 @@ end
function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal,
s::Union{PromptState,PrefixSearchState}; beeping=false)
beeping || cancel_beep(s)
ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s,
indent = s.indent,
ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias::InputAreaState, s;
indent = s.indent::Int,
region_active = is_region_active(s))
s.ias = ias
return ias
Expand Down Expand Up @@ -2069,7 +2070,7 @@ end

# return true iff the content of the buffer is modified
# return false when only the position changed
function edit_insert_tab(buf::IOBuffer, jump_spaces=false, delete_trailing=jump_spaces)
function edit_insert_tab(buf::IOBuffer, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces)
i = position(buf)
if jump_spaces && i < buf.size && buf.data[i+1] == _space
spaces = something(findnext(_notspace, buf.data[i+1:buf.size], 1), 0)
Expand Down
87 changes: 15 additions & 72 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ import Base:
==,
catch_stack

_displaysize(io) = displaysize(io)::Tuple{Int,Int}

include("Terminals.jl")
using .Terminals

abstract type AbstractREPL end

include("options.jl")

include("LineEdit.jl")
using .LineEdit
import ..LineEdit:
Expand Down Expand Up @@ -374,64 +377,6 @@ function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
nothing
end

## User Options

mutable struct Options
hascolor::Bool
extra_keymap::Union{Dict,Vector{<:Dict}}
# controls the presumed tab width of code pasted into the REPL.
# Must satisfy `0 < tabwidth <= 16`.
tabwidth::Int
# Maximum number of entries in the kill ring queue.
# Beyond this number, oldest entries are discarded first.
kill_ring_max::Int
region_animation_duration::Float64
beep_duration::Float64
beep_blink::Float64
beep_maxduration::Float64
beep_colors::Vector{String}
beep_use_current::Bool
backspace_align::Bool
backspace_adjust::Bool
confirm_exit::Bool # ^D must be repeated to confirm exit
auto_indent::Bool # indent a newline like line above
auto_indent_tmp_off::Bool # switch auto_indent temporarily off if copy&paste
auto_indent_bracketed_paste::Bool # set to true if terminal knows paste mode
# cancel auto-indent when next character is entered within this time frame :
auto_indent_time_threshold::Float64
# default IOContext settings at the REPL
iocontext::Dict{Symbol,Any}
end

Options(;
hascolor = true,
extra_keymap = AnyDict[],
tabwidth = 8,
kill_ring_max = 100,
region_animation_duration = 0.2,
beep_duration = 0.2, beep_blink = 0.2, beep_maxduration = 1.0,
beep_colors = ["\e[90m"], # gray (text_colors not yet available)
beep_use_current = true,
backspace_align = true, backspace_adjust = backspace_align,
confirm_exit = false,
auto_indent = true,
auto_indent_tmp_off = false,
auto_indent_bracketed_paste = false,
auto_indent_time_threshold = 0.005,
iocontext = Dict{Symbol,Any}()) =
Options(hascolor, extra_keymap, tabwidth,
kill_ring_max, region_animation_duration,
beep_duration, beep_blink, beep_maxduration,
beep_colors, beep_use_current,
backspace_align, backspace_adjust, confirm_exit,
auto_indent, auto_indent_tmp_off, auto_indent_bracketed_paste,
auto_indent_time_threshold,
iocontext)

# for use by REPLs not having an options field
const GlobalOptions = Options()


## LineEditREPL ##

mutable struct LineEditREPL <: AbstractREPL
Expand Down Expand Up @@ -489,22 +434,22 @@ function complete_line(c::REPLCompletionProvider, s)
partial = beforecursor(s.input_buffer)
full = LineEdit.input_string(s)
ret, range, should_complete = completions(full, lastindex(partial))
return unique!(map(completion_text, ret)), partial[range], should_complete
return unique!(map(completion_text, ret)::Vector{String}), partial[range], should_complete
end

function complete_line(c::ShellCompletionProvider, s)
# 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))
return unique!(map(completion_text, ret)), partial[range], should_complete
return unique!(map(completion_text, ret)::Vector{String}), partial[range], should_complete
end

function complete_line(c::LatexCompletions, s)
partial = beforecursor(LineEdit.buffer(s))
full = LineEdit.input_string(s)
ret, range, should_complete = bslash_completions(full, lastindex(partial))[2]
return unique!(map(completion_text, ret)), partial[range], should_complete
return unique!(map(completion_text, ret)::Vector{String}), partial[range], should_complete
end

with_methodtable_hint(f, repl) = f(outstream(repl))
Expand Down Expand Up @@ -729,15 +674,15 @@ function history_move_prefix(s::LineEdit.PrefixSearchState,
hist::REPLHistoryProvider,
prefix::AbstractString,
backwards::Bool,
cur_idx = hist.cur_idx)
cur_idx::Int = hist.cur_idx)
cur_response = String(take!(copy(LineEdit.buffer(s))))
# when searching forward, start at last_idx
if !backwards && hist.last_idx > 0
cur_idx = hist.last_idx
end
hist.last_idx = -1
max_idx = length(hist.history)+1
idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):max_idx)
idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):1:max_idx)
for idx in idxs
if (idx == max_idx) || (startswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || hist.modes[idx] != LineEdit.mode(s)))
m = history_move(s, hist, idx)
Expand Down Expand Up @@ -782,13 +727,10 @@ function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, respo
b = b ncodeunits(response_str) ? prevind(response_str, b) : b-1
b = min(lastindex(response_str), b) # ensure that b is valid

searchfunc1, searchfunc2, searchstart, skipfunc = backwards ?
(findlast, findprev, b, prevind) :
(findfirst, findnext, a, nextind)

searchstart = -1
if searchdata == response_str[a:b]
if skip_current
searchstart = skipfunc(response_str, searchstart)
searchstart = backwards ? prevind(response_str, b) : nextind(response_str, a)
else
return true
end
Expand All @@ -797,18 +739,19 @@ function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, respo
# Start searching
# First the current response buffer
if 1 <= searchstart <= lastindex(response_str)
match = searchfunc2(searchdata, response_str, searchstart)
match = backwards ? findprev(searchdata, response_str, searchstart) :
findnext(searchdata, response_str, searchstart)
if match !== nothing
seek(response_buffer, first(match) - 1)
return true
end
end

# Now search all the other buffers
idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):length(hist.history))
idxs = backwards ? ((hist.cur_idx-1):-1:1) : ((hist.cur_idx+1):1:length(hist.history))
for idx in idxs
h = hist.history[idx]
match = searchfunc1(searchdata, h)
match = backwards ? findlast(searchdata, h) : findnext(searchdata, h)
if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx])
truncate(response_buffer, 0)
write(response_buffer, h)
Expand Down Expand Up @@ -1218,7 +1161,7 @@ input_color(r::StreamREPL) = r.input_color
# heuristic function to decide if the presence of a semicolon
# at the end of the expression was intended for suppressing output
function ends_with_semicolon(line::AbstractString)
match = findlast(isequal(';'), line)
match = findlast(isequal(';'), line)::Union{Nothing,Int}
if match !== nothing
# state for comment parser, assuming that the `;` isn't in a string or comment
# so input like ";#" will still thwart this to give the wrong (anti-conservative) answer
Expand Down

0 comments on commit 0de90e9

Please sign in to comment.