From 69da8249d2ecad7eab6e52e4b76bc88c4bfc2d14 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 18 Aug 2020 13:05:47 -0500 Subject: [PATCH] REPL: add argument typing and improve implementations (part of #37081) Since entire modules are marked `@nospecialize`, we need to declare argument types anywhere we want good inference. This also improves numerous implementations to ensure inferrability. For the completion methods, notice this changes typeof(ret[2]) from UnitRange{Int64} to UnitRange{Int}. Internally, the methods are using Int rather than Int64, so consistently supporting Int64 would require more extensive changes. --- stdlib/REPL/src/LineEdit.jl | 223 +++++++++--------- stdlib/REPL/src/REPL.jl | 53 +++-- stdlib/REPL/src/REPLCompletions.jl | 81 +++---- stdlib/REPL/src/TerminalMenus/AbstractMenu.jl | 10 +- stdlib/REPL/src/TerminalMenus/util.jl | 6 +- stdlib/REPL/src/Terminals.jl | 6 +- stdlib/REPL/src/docview.jl | 45 ++-- stdlib/REPL/test/replcompletions.jl | 18 +- 8 files changed, 225 insertions(+), 217 deletions(-) diff --git a/stdlib/REPL/src/LineEdit.jl b/stdlib/REPL/src/LineEdit.jl index abb2ce37b942d..1dd83efb3ea77 100644 --- a/stdlib/REPL/src/LineEdit.jl +++ b/stdlib/REPL/src/LineEdit.jl @@ -21,6 +21,8 @@ export run_interface, Prompt, ModalInterface, transition, reset_state, edit_inse @nospecialize # use only declared type signatures +const StringLike = Union{Char,String,SubString{String}} + # interface for TextInterface function Base.getproperty(ti::TextInterface, name::Symbol) if name === :hp @@ -73,6 +75,7 @@ end MIState(i, c, a, m) = MIState(i, c, a, m, String[], 0, Char[], 0, :none, :none) const BufferLike = Union{MIState,ModeState,IOBuffer} +const State = Union{MIState,ModeState} function show(io::IO, s::MIState) print(io, "MI State (", mode(s), " active)") @@ -119,18 +122,18 @@ function setmark(s::MIState, guess_region_active::Bool=true) end # the default mark is 0 -getmark(s) = max(0, buffer(s).mark) +getmark(s::BufferLike) = max(0, buffer(s).mark) const Region = Pair{Int,Int} -_region(s) = getmark(s) => position(s) -region(s) = Pair(extrema(_region(s))...) +_region(s::BufferLike) = getmark(s) => position(s) +region(s::BufferLike) = Pair(extrema(_region(s))...) -bufend(s) = buffer(s).size +bufend(s::BufferLike) = buffer(s).size axes(reg::Region) = first(reg)+1:last(reg) -content(s, reg::Region = 0=>bufend(s)) = String(buffer(s).data[axes(reg)]) +content(s::BufferLike, reg::Region = 0=>bufend(s)) = String(buffer(s).data[axes(reg)]) function activate_region(s::PromptState, state::Symbol) @assert state in (:mark, :shift, :off) @@ -163,7 +166,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 @@ -176,27 +179,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 @@ -245,7 +249,7 @@ const COMMAND_GROUPS = :copy => [:edit_copy_region], :misc => [:complete_line, :setmark, :edit_undo!, :edit_redo!]) -const COMMAND_GROUP = Dict(command=>group for (group, commands) in COMMAND_GROUPS for command in commands) +const COMMAND_GROUP = Dict{Symbol,Symbol}(command=>group for (group, commands) in COMMAND_GROUPS for command in commands) command_group(command::Symbol) = get(COMMAND_GROUP, command, :nogroup) command_group(command::Function) = command_group(nameof(command)) @@ -287,7 +291,7 @@ end set_action!(s, command::Symbol) = nothing -function common_prefix(completions) +function common_prefix(completions::Vector{String}) ret = "" c1 = completions[1] isempty(c1) && return ret @@ -305,7 +309,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) @@ -340,8 +344,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 @@ -367,9 +371,9 @@ function complete_line(s::PromptState, repeats) return true end -clear_input_area(terminal, s) = (_clear_input_area(terminal, s.ias); s.ias = InputAreaState(0, 0)) -clear_input_area(s) = clear_input_area(s.terminal, s) -function _clear_input_area(terminal, state::InputAreaState) +clear_input_area(terminal::AbstractTerminal, s::ModeState) = (_clear_input_area(terminal, s.ias); s.ias = InputAreaState(0, 0)) +clear_input_area(s::ModeState) = clear_input_area(s.terminal, s) +function _clear_input_area(terminal::AbstractTerminal, state::InputAreaState) # Go to the last line if state.curs_row < state.num_rows cmove_down(terminal, state.num_rows - state.curs_row) @@ -397,7 +401,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) @@ -410,7 +414,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 @@ -495,7 +499,7 @@ 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] @@ -519,7 +523,7 @@ end # Edit functionality -is_non_word_char(c) = c in """ \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~""" +is_non_word_char(c::Char) = c in """ \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~""" function reset_key_repeats(f::Function, s::MIState) key_repeats_sav = s.key_repeats @@ -573,7 +577,7 @@ end edit_move_left(s::PromptState) = edit_move_left(s.input_buffer) ? refresh_line(s) : false -function edit_move_word_left(s) +function edit_move_word_left(s::PromptState) if position(s) > 0 char_move_word_left(s.input_buffer) return refresh_line(s) @@ -581,12 +585,12 @@ function edit_move_word_left(s) return nothing end -char_move_right(s) = char_move_right(buffer(s)) +char_move_right(s::MIState) = char_move_right(buffer(s)) function char_move_right(buf::IOBuffer) return !eof(buf) && read(buf, Char) end -function char_move_word_right(buf::IOBuffer, is_delimiter=is_non_word_char) +function char_move_word_right(buf::IOBuffer, is_delimiter::Function=is_non_word_char) while !eof(buf) && is_delimiter(char_move_right(buf)) end while !eof(buf) @@ -598,7 +602,7 @@ function char_move_word_right(buf::IOBuffer, is_delimiter=is_non_word_char) end end -function char_move_word_left(buf::IOBuffer, is_delimiter=is_non_word_char) +function char_move_word_left(buf::IOBuffer, is_delimiter::Function=is_non_word_char) while position(buf) > 0 && is_delimiter(char_move_left(buf)) end while position(buf) > 0 @@ -610,8 +614,8 @@ function char_move_word_left(buf::IOBuffer, is_delimiter=is_non_word_char) end end -char_move_word_right(s) = char_move_word_right(buffer(s)) -char_move_word_left(s) = char_move_word_left(buffer(s)) +char_move_word_right(s::Union{MIState,ModeState}) = char_move_word_right(buffer(s)) +char_move_word_left(s::Union{MIState,ModeState}) = char_move_word_left(buffer(s)) function edit_move_right(buf::IOBuffer) if !eof(buf) @@ -630,7 +634,7 @@ function edit_move_right(buf::IOBuffer) end edit_move_right(s::PromptState) = edit_move_right(s.input_buffer) ? refresh_line(s) : false -function edit_move_word_right(s) +function edit_move_word_right(s::PromptState) if !eof(s.input_buffer) char_move_word_right(s) return refresh_line(s) @@ -659,7 +663,7 @@ function edit_move_up(buf::IOBuffer) end return true end -function edit_move_up(s) +function edit_move_up(s::MIState) set_action!(s, :edit_move_up) changed = edit_move_up(buffer(s)) changed && refresh_line(s) @@ -684,7 +688,7 @@ function edit_move_down(buf::IOBuffer) end return true end -function edit_move_down(s) +function edit_move_down(s::MIState) set_action!(s, :edit_move_down) changed = edit_move_down(buffer(s)) changed && refresh_line(s) @@ -701,7 +705,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::BufferLike, 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) @@ -727,9 +731,9 @@ function edit_splice!(s, r::Region=region(s), ins::AbstractString = ""; rigid_ma return String(ret) end -edit_splice!(s, ins::AbstractString) = edit_splice!(s, region(s), ins) +edit_splice!(s::MIState, ins::AbstractString) = edit_splice!(s, region(s), ins) -function edit_insert(s::PromptState, c) +function edit_insert(s::PromptState, c::StringLike) push_undo(s) buf = s.input_buffer @@ -756,7 +760,7 @@ function edit_insert(s::PromptState, c) str = string(c) edit_insert(buf, str) offset = s.ias.curs_row == 1 || s.indent < 0 ? - sizeof(prompt_string(s.p.prompt)) : s.indent + sizeof(prompt_string(s.p.prompt)::String) : s.indent if !('\n' in str) && eof(buf) && ((position(buf) - beginofline(buf) + # size of current line offset + sizeof(str) - 1) < width(terminal(s))) @@ -768,7 +772,7 @@ function edit_insert(s::PromptState, c) end end -function edit_insert(buf::IOBuffer, c) +function edit_insert(buf::IOBuffer, c::StringLike) if eof(buf) return write(buf, c) else @@ -806,7 +810,7 @@ end # adjust: also delete spaces on the right of the cursor to try to keep aligned what is # on the right function edit_backspace(s::PromptState, align::Bool=options(s).backspace_align, - adjust=options(s).backspace_adjust) + adjust::Bool=options(s).backspace_adjust) push_undo(s) if edit_backspace(buffer(s), align, adjust) return refresh_line(s) @@ -821,9 +825,9 @@ const _space = UInt8(' ') _notspace(c) = c != _space -beginofline(buf, pos=position(buf)) = something(findprev(isequal(_newline), buf.data, pos), 0) +beginofline(buf::IOBuffer, pos::Int=position(buf)) = something(findprev(isequal(_newline), buf.data, pos), 0) -function endofline(buf, pos=position(buf)) +function endofline(buf::IOBuffer, pos::Int=position(buf)) eol = findnext(isequal(_newline), buf.data[pos+1:buf.size], 1) eol === nothing ? buf.size : pos + eol - 1 end @@ -855,7 +859,7 @@ function edit_backspace(buf::IOBuffer, align::Bool=false, adjust::Bool=false) return true end -function edit_delete(s) +function edit_delete(s::MIState) set_action!(s, :edit_delete) push_undo(s) if edit_delete(buffer(s)) @@ -917,7 +921,7 @@ function edit_delete_next_word(buf::IOBuffer) return edit_splice!(buf, pos0 => pos1) end -function edit_delete_next_word(s) +function edit_delete_next_word(s::MIState) set_action!(s, :edit_delete_next_word) push_undo(s) if push_kill!(s, edit_delete_next_word(buffer(s))) @@ -940,7 +944,7 @@ function edit_yank(s::MIState) return refresh_line(s) end -function edit_yank_pop(s::MIState, require_previous_yank=true) +function edit_yank_pop(s::MIState, require_previous_yank::Bool=true) set_action!(s, :edit_yank_pop) repeat = s.last_action ∈ (:edit_yank, :edit_yank_pop) if require_previous_yank && !repeat || isempty(s.kill_ring) @@ -954,7 +958,7 @@ function edit_yank_pop(s::MIState, require_previous_yank=true) end end -function push_kill!(s::MIState, killed::String, concat = s.key_repeats > 0; rev=false) +function push_kill!(s::MIState, killed::String, concat::Bool = s.key_repeats > 0; rev::Bool=false) isempty(killed) && return false if concat && !isempty(s.kill_ring) s.kill_ring[end] = rev ? @@ -991,8 +995,8 @@ function edit_kill_line(s::MIState, backwards::Bool=false) end end -edit_kill_line_forwards(s) = edit_kill_line(s, false) -edit_kill_line_backwards(s) = edit_kill_line(s, true) +edit_kill_line_forwards(s::MIState) = edit_kill_line(s, false) +edit_kill_line_backwards(s::MIState) = edit_kill_line(s, true) function edit_copy_region(s::MIState) set_action!(s, :edit_copy_region) @@ -1034,13 +1038,13 @@ function edit_transpose_chars(buf::IOBuffer) return true end -function edit_transpose_words(s) +function edit_transpose_words(s::MIState) set_action!(s, :edit_transpose_words) push_undo(s) return edit_transpose_words(buffer(s)) ? refresh_line(s) : pop_undo(s) end -function edit_transpose_words(buf::IOBuffer, mode=:emacs) +function edit_transpose_words(buf::IOBuffer, mode::Symbol=:emacs) mode in [:readline, :emacs] || throw(ArgumentError("`mode` must be `:readline` or `:emacs`")) pos = position(buf) @@ -1092,7 +1096,7 @@ function edit_transpose_lines_down!(buf::IOBuffer, reg::Region) end # return the region if active, or the current position as a Region otherwise -region_if_active(s)::Region = is_region_active(s) ? region(s) : position(s)=>position(s) +region_if_active(s::MIState)::Region = is_region_active(s) ? region(s) : position(s)=>position(s) function edit_transpose_lines_up!(s::MIState) set_action!(s, :edit_transpose_lines_up!) @@ -1113,20 +1117,20 @@ function edit_transpose_lines_down!(s::MIState) end end -function edit_upper_case(s) +function edit_upper_case(s::BufferLike) set_action!(s, :edit_upper_case) return edit_replace_word_right(s, uppercase) end -function edit_lower_case(s) +function edit_lower_case(s::BufferLike) set_action!(s, :edit_lower_case) return edit_replace_word_right(s, lowercase) end -function edit_title_case(s) +function edit_title_case(s::BufferLike) set_action!(s, :edit_title_case) return edit_replace_word_right(s, titlecase) end -function edit_replace_word_right(s, replace::Function) +function edit_replace_word_right(s::Union{MIState,ModeState}, replace::Function) push_undo(s) return edit_replace_word_right(buffer(s), replace) ? refresh_line(s) : pop_undo(s) end @@ -1162,7 +1166,7 @@ function replace_line(s::PromptState, l::IOBuffer) nothing end -function replace_line(s::PromptState, l, keep_undo=false) +function replace_line(s::PromptState, l::Union{String,SubString{String}}, keep_undo::Bool=false) keep_undo || empty_undo(s) s.input_buffer.ptr = 1 s.input_buffer.size = 0 @@ -1188,7 +1192,7 @@ end # return the indices in buffer(s) of the beginning of each lines # having a non-empty intersection with region(s) -function get_lines_in_region(s)::Vector{Int} +function get_lines_in_region(s::BufferLike) buf = buffer(s) b, e = region(buf) bol = Int[beginofline(buf, b)] # begin of lines @@ -1203,7 +1207,7 @@ end # compute the number of spaces from b till the next non-space on the right # (which can also be "end of line" or "end of buffer") -function leadingspaces(buf::IOBuffer, b::Int)::Int +function leadingspaces(buf::IOBuffer, b::Int) ls = something(findnext(_notspace, buf.data, b+1), 0)-1 ls == -1 && (ls = buf.size) ls -= b @@ -1212,7 +1216,7 @@ end # indent by abs(num) characters, on the right if num >= 0, on the left otherwise # if multiline is true, indent all the lines in the region as a block. -function edit_indent(buf::IOBuffer, num::Int, multiline::Bool)::Bool +function edit_indent(buf::IOBuffer, num::Int, multiline::Bool) bol = multiline ? get_lines_in_region(buf) : Int[beginofline(buf)] if num < 0 # count leading spaces on the lines, which are an upper bound @@ -1245,7 +1249,7 @@ add_history(s::PromptState) = add_history(mode(s).hist, s) history_next_prefix(s, hist, prefix) = false history_prev_prefix(s, hist, prefix) = false -function history_prev(s, hist) +function history_prev(s::ModeState, hist) l, ok = history_prev(mode(s).hist) if ok replace_line(s, l) @@ -1256,7 +1260,7 @@ function history_prev(s, hist) end nothing end -function history_next(s, hist) +function history_next(s::ModeState, hist) l, ok = history_next(mode(s).hist) if ok replace_line(s, l) @@ -1268,15 +1272,15 @@ function history_next(s, hist) nothing end -refresh_line(s) = refresh_multi_line(s) -refresh_line(s, termbuf) = refresh_multi_line(termbuf, s) +refresh_line(s::BufferLike) = refresh_multi_line(s) +refresh_line(s::BufferLike, termbuf::AbstractTerminal) = refresh_multi_line(termbuf, s) default_completion_cb(::IOBuffer) = [] default_enter_cb(_) = true -write_prompt(terminal, s::PromptState, color::Bool) = write_prompt(terminal, s.p, color) +write_prompt(terminal::AbstractTerminal, s::PromptState, color::Bool) = write_prompt(terminal, s.p, color) -function write_prompt(terminal, p::Prompt, color::Bool) +function write_prompt(terminal::AbstractTerminal, p::Prompt, color::Bool) prefix = prompt_string(p.prompt_prefix) suffix = prompt_string(p.prompt_suffix) write(terminal, prefix) @@ -1320,7 +1324,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 @@ -1331,7 +1335,7 @@ const wildcard = '\U10f7ff' # "Private Use" Char normalize_key(key::AbstractChar) = string(key) normalize_key(key::Union{Int,UInt8}) = normalize_key(Char(key)) -function normalize_key(key::AbstractString) +function normalize_key(key::Union{String,SubString{String}}) wildcard in key && error("Matching '\U10f7ff' not supported.") buf = IOBuffer() i = firstindex(key) @@ -1435,7 +1439,7 @@ function match_input(k::Dict, s, term=terminal(s), cs=Char[], keymap = k) end update_key_repeats(s, keystroke) = nothing -function update_key_repeats(s::MIState, keystroke) +function update_key_repeats(s::MIState, keystroke::Vector{Char}) s.key_repeats = s.previous_key == keystroke ? s.key_repeats + 1 : 0 s.previous_key = keystroke return @@ -1674,7 +1678,7 @@ init_state(terminal, p::HistoryPrompt) = SearchState(terminal, p, true, IOBuffer terminal(s::SearchState) = s.terminal -function update_display_buffer(s::SearchState, data) +function update_display_buffer(s::SearchState, data::SearchState) s.failed = !history_search(data.histprompt.hp, data.query_buffer, data.response_buffer, data.backward, false) s.failed && beep(s) refresh_line(s) @@ -1688,7 +1692,7 @@ function history_next_result(s::MIState, data::SearchState) nothing end -function history_set_backward(s::SearchState, backward) +function history_set_backward(s::SearchState, backward::Bool) s.backward = backward nothing end @@ -1772,9 +1776,9 @@ function show(io::IO, s::PrefixSearchState) end function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, - s::Union{PromptState,PrefixSearchState}; beeping=false) + s::Union{PromptState,PrefixSearchState}; beeping::Bool=false) beeping || cancel_beep(s) - ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s, + ias = refresh_multi_line(termbuf, terminal, buffer(s), s.ias, s; indent = s.indent, region_active = is_region_active(s)) s.ias = ias @@ -1797,7 +1801,7 @@ function reset_state(s::PrefixSearchState) nothing end -function transition(f::Function, s::PrefixSearchState, mode) +function transition(f::Function, s::PrefixSearchState, mode::Prompt) if isdefined(s, :mi) transition(s.mi, mode) end @@ -1812,7 +1816,7 @@ function transition(f::Function, s::PrefixSearchState, mode) end replace_line(s::PrefixSearchState, l::IOBuffer) = (s.response_buffer = l; nothing) -function replace_line(s::PrefixSearchState, l) +function replace_line(s::PrefixSearchState, l::Union{String,SubString{String}}) s.response_buffer.ptr = 1 s.response_buffer.size = 0 write(s.response_buffer, l) @@ -1836,12 +1840,13 @@ function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) return ias end -state(s::MIState, p=mode(s)) = s.mode_state[p] -state(s::PromptState, p=mode(s)) = (@assert s.p == p; s) -mode(s::MIState) = s.current_mode -mode(s::PromptState) = s.p +state(s::MIState, p::TextInterface=mode(s)) = s.mode_state[p] +state(s::PromptState, p::Prompt=mode(s)) = (@assert s.p == p; s) + +mode(s::MIState) = s.current_mode # ::TextInterface, and might be a Prompt +mode(s::PromptState) = s.p # ::Prompt mode(s::SearchState) = @assert false -mode(s::PrefixSearchState) = s.histprompt.parent_prompt +mode(s::PrefixSearchState) = s.histprompt.parent_prompt # ::Prompt # Search Mode completions function complete_line(s::SearchState, repeats) @@ -1857,7 +1862,7 @@ function complete_line(s::SearchState, repeats) end accept_result_newmode(hp::HistoryProvider) = nothing -function accept_result(s, p) # p must be either a HistoryPrompt or PrefixHistoryPrompt, probably +function accept_result(s::MIState, p::TextInterface) parent = something(accept_result_newmode(p.hp), state(s, p).parent) transition(s, parent) do replace_line(state(s, parent), state(s, p).response_buffer) @@ -2006,9 +2011,9 @@ Base.isempty(s::PromptState) = s.input_buffer.size == 0 on_enter(s::PromptState) = s.p.on_enter(s) -move_input_start(s) = (seek(buffer(s), 0); nothing) +move_input_start(s::BufferLike) = (seek(buffer(s), 0); nothing) move_input_end(buf::IOBuffer) = (seekend(buf); nothing) -move_input_end(s) = (move_input_end(buffer(s)); nothing) +move_input_end(s::Union{MIState,ModeState}) = (move_input_end(buffer(s)); nothing) function move_line_start(s::MIState) set_action!(s, :move_line_start) @@ -2061,7 +2066,7 @@ function get_last_word(buf::IOBuffer) word end -function commit_line(s) +function commit_line(s::MIState) cancel_beep(s) move_input_end(s) refresh_line(s) @@ -2072,7 +2077,7 @@ function commit_line(s) nothing end -function bracketed_paste(s; tabwidth::Int=options(s).tabwidth::Int) +function bracketed_paste(s::MIState; tabwidth::Int=options(s).tabwidth) options(s).auto_indent_bracketed_paste = true ps = state(s, mode(s))::PromptState input = readuntil(ps.terminal, "\e[201~") @@ -2084,7 +2089,7 @@ function bracketed_paste(s; tabwidth::Int=options(s).tabwidth::Int) return replace(input, '\t' => " "^tabwidth) end -function tab_should_complete(s) +function tab_should_complete(s::MIState) # Yes, we are ignoring the possibility # the we could be in the middle of a multi-byte # sequence, here but that's ok, since any @@ -2102,7 +2107,7 @@ end # jump_spaces: if cursor is on a ' ', move it to the first non-' ' char on the right # if `delete_trailing`, ignore trailing ' ' by deleting them -function edit_tab(s::MIState, jump_spaces=false, delete_trailing=jump_spaces) +function edit_tab(s::MIState, jump_spaces::Bool=false, delete_trailing::Bool=jump_spaces) tab_should_complete(s) && return complete_line(s) set_action!(s, :edit_insert_tab) push_undo(s) @@ -2112,7 +2117,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) @@ -2130,7 +2135,7 @@ function edit_insert_tab(buf::IOBuffer, jump_spaces=false, delete_trailing=jump_ return true end -function edit_abort(s, confirm::Bool=options(s).confirm_exit; key="^D") +function edit_abort(s::MIState, confirm::Bool=options(s).confirm_exit; key="^D") set_action!(s, :edit_abort) if !confirm || s.last_action === :edit_abort println(terminal(s)) @@ -2309,7 +2314,7 @@ const prefix_history_keymap = merge!( AnyDict("\e[$(c)l" => "*" for c in 1:20) ) -function setup_prefix_keymap(hp, parent_prompt) +function setup_prefix_keymap(hp::HistoryProvider, parent_prompt::Prompt) p = PrefixHistoryPrompt(hp, parent_prompt) p.keymap_dict = keymap([prefix_history_keymap]) pkeymap = AnyDict( @@ -2323,28 +2328,28 @@ function setup_prefix_keymap(hp, parent_prompt) return (p, pkeymap) end -function deactivate(p::TextInterface, s::ModeState, termbuf, term::TextTerminal) +function deactivate(p::TextInterface, s::ModeState, termbuf::AbstractTerminal, term::TextTerminal) clear_input_area(termbuf, s) return s end -function activate(p::TextInterface, s::ModeState, termbuf, term::TextTerminal) +function activate(p::TextInterface, s::ModeState, termbuf::AbstractTerminal, term::TextTerminal) s.ias = InputAreaState(0, 0) refresh_line(s, termbuf) nothing end -function activate(p::TextInterface, s::MIState, termbuf, term::TextTerminal) +function activate(p::TextInterface, s::MIState, termbuf::AbstractTerminal, term::TextTerminal) @assert p == mode(s) activate(p, state(s), termbuf, term) nothing end -activate(m::ModalInterface, s::MIState, termbuf, term::TextTerminal) = +activate(m::ModalInterface, s::MIState, termbuf::AbstractTerminal, term::TextTerminal) = activate(mode(s), s, termbuf, term) -commit_changes(t::UnixTerminal, termbuf) = (write(t, take!(termbuf.out_stream)); nothing) +commit_changes(t::UnixTerminal, termbuf::TerminalBuffer) = (write(t, take!(termbuf.out_stream)); nothing) -function transition(f::Function, s::MIState, newmode) +function transition(f::Function, s::MIState, newmode::Union{TextInterface,Symbol}) cancel_beep(s) if newmode === :abort s.aborted = true @@ -2366,7 +2371,7 @@ function transition(f::Function, s::MIState, newmode) commit_changes(t, termbuf) nothing end -transition(s::MIState, mode) = transition((args...)->nothing, s, mode) +transition(s::MIState, mode::Union{TextInterface,Symbol}) = transition((args...)->nothing, s, mode) function reset_state(s::PromptState) if s.input_buffer.size != 0 @@ -2446,7 +2451,7 @@ end empty_undo(s) = nothing -function push_undo(s::PromptState, advance=true) +function push_undo(s::PromptState, advance::Bool=true) resize!(s.undo_buffers, s.undo_idx) s.undo_buffers[end] = copy(s.input_buffer) advance && (s.undo_idx += 1) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index e9ed85829f560..cea7d4fd6986b 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -32,6 +32,7 @@ import Base: ==, catch_stack +_displaysize(io::IO) = displaysize(io)::Tuple{Int,Int} include("Terminals.jl") using .Terminals @@ -56,7 +57,9 @@ import ..LineEdit: history_search, accept_result, terminal, - MIState + MIState, + PromptState, + TextInterface include("REPLCompletions.jl") using .REPLCompletions @@ -207,7 +210,7 @@ end function display(d::REPLDisplay, mime::MIME"text/plain", x) linfos = Tuple{String,Int}[] - io = IOContext(outstream(d.repl), :limit => true, :module => Main, :last_shown_line_infos => linfos) + io = IOContext(outstream(d.repl), :limit => true, :module => Main::Module, :last_shown_line_infos => linfos) get(io, :color, false) && write(io, answer_color(d.repl)) if isdefined(d.repl, :options) && isdefined(d.repl.options, :iocontext) # this can override the :limit property set initially @@ -225,14 +228,14 @@ display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x) function print_response(repl::AbstractREPL, @nospecialize(response), show_value::Bool, have_color::Bool) repl.waserror = response[2] linfos = Tuple{String,Int}[] - io = IOContext(outstream(repl), :module => Main, :last_shown_line_infos => linfos) + io = IOContext(outstream(repl), :module => Main::Module, :last_shown_line_infos => linfos) print_response(io, response, show_value, have_color, specialdisplay(repl)) if !isempty(linfos) repl.last_shown_line_infos = linfos end return nothing end -function print_response(errio::IO, @nospecialize(response), show_value::Bool, have_color::Bool, specialdisplay=nothing) +function print_response(errio::IO, @nospecialize(response), show_value::Bool, have_color::Bool, specialdisplay::Union{AbstractDisplay,Nothing}=nothing) Base.sigatomic_begin() val, iserr = response while true @@ -433,14 +436,14 @@ struct LatexCompletions <: CompletionProvider end beforecursor(buf::IOBuffer) = String(buf.data[1:buf.ptr-1]) -function complete_line(c::REPLCompletionProvider, s) +function complete_line(c::REPLCompletionProvider, s::PromptState) 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 end -function complete_line(c::ShellCompletionProvider, s) +function complete_line(c::ShellCompletionProvider, s::PromptState) # First parse everything up to the current position partial = beforecursor(s.input_buffer) full = LineEdit.input_string(s) @@ -448,7 +451,7 @@ function complete_line(c::ShellCompletionProvider, s) return unique!(map(completion_text, ret)), partial[range], should_complete end -function complete_line(c::LatexCompletions, s) +function complete_line(c::LatexCompletions, s::PromptState) partial = beforecursor(LineEdit.buffer(s)) full = LineEdit.input_string(s) ret, range, should_complete = bslash_completions(full, lastindex(partial))[2] @@ -480,7 +483,7 @@ munged_history_message(path::String) = """ Invalid history file ($path) format: An editor may have converted tabs to spaces at line """ -function hist_getline(file) +function hist_getline(file::IO) while !eof(file) line = readline(file, keep=true) isempty(line) && return line @@ -489,7 +492,7 @@ function hist_getline(file) return "" end -function hist_from_file(hp, file, path) +function hist_from_file(hp::REPLHistoryProvider, file::IO, path::String) hp.history_file = file seek(file, 0) countlines = 0 @@ -533,7 +536,7 @@ function hist_from_file(hp, file, path) return hp end -function mode_idx(hist::REPLHistoryProvider, mode) +function mode_idx(hist::REPLHistoryProvider, mode::TextInterface) c = :julia for (k,v) in hist.mode_mapping isequal(v, mode) && (c = k) @@ -541,7 +544,7 @@ function mode_idx(hist::REPLHistoryProvider, mode) return c end -function add_history(hist::REPLHistoryProvider, s) +function add_history(hist::REPLHistoryProvider, s::PromptState) str = rstrip(String(take!(copy(s.input_buffer)))) isempty(strip(str)) && return mode = mode_idx(hist, LineEdit.mode(s)) @@ -661,7 +664,7 @@ 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 @@ -669,7 +672,7 @@ function history_move_prefix(s::LineEdit.PrefixSearchState, 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) @@ -714,13 +717,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 = backwards ? b : a 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 @@ -729,7 +729,8 @@ 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 @@ -737,10 +738,10 @@ function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, respo 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) : findfirst(searchdata, h) if match !== nothing && h != response_str && haskey(hist.mode_mapping, hist.modes[idx]) truncate(response_buffer, 0) write(response_buffer, h) @@ -778,12 +779,12 @@ function eval_with_backend(ast, backend::REPLBackendRef) return take!(backend.response_channel) # (val, iserr) end -function respond(f, repl, main; pass_empty = false, suppress_on_semicolon = true) - return function do_respond(s, buf, ok) +function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon::Bool = true) + return function do_respond(s::MIState, buf, ok::Bool) if !ok return transition(s, :abort) end - line = String(take!(buf)) + line = String(take!(buf)::Vector{UInt8}) if !isempty(line) || pass_empty reset(repl) local response @@ -1150,7 +1151,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 @@ -1204,7 +1205,7 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef) d = REPLDisplay(repl) dopushdisplay = !in(d,Base.Multimedia.displays) dopushdisplay && pushdisplay(d) - while !eof(repl.stream) + while !eof(repl.stream)::Bool if have_color print(repl.stream,repl.prompt_color) end diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 4c77c805259ca..dd8d27c96194f 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -96,7 +96,7 @@ _completion_text(c::DictCompletion) = c.key completion_text(c) = _completion_text(c)::String -const Completions = Tuple{Vector{Completion}, UnitRange{Int64}, Bool} +const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool} function completes_global(x, name) return startswith(x, name) && !('#' in x) @@ -124,7 +124,7 @@ function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, end # REPL Symbol Completions -function complete_symbol(sym, ffunc, context_module=Main)::Vector{Completion} +function complete_symbol(sym::String, ffunc, context_module::Module=Main) mod = context_module name = sym @@ -162,14 +162,14 @@ function complete_symbol(sym, ffunc, context_module=Main)::Vector{Completion} # as excluding Main.Main.Main, etc., because that's most likely not what # the user wants p = let mod=mod, modname=nameof(mod) - s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)) + s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool) end # Looking for a binding in a module if mod == context_module # Also look in modules we got through `using` - mods = ccall(:jl_module_usings, Any, (Any,), context_module) + mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector for m in mods - append!(suggestions, filtered_mod_names(p, m, name)) + append!(suggestions, filtered_mod_names(p, m::Module, name)) end append!(suggestions, filtered_mod_names(p, mod, name, true, true)) else @@ -212,7 +212,7 @@ const sorted_keywords = [ "primitive type", "quote", "return", "struct", "true", "try", "using", "while"] -function complete_keyword(s::Union{String,SubString{String}})::Vector{Completion} +function complete_keyword(s::Union{String,SubString{String}}) r = searchsorted(sorted_keywords, s) i = first(r) n = length(sorted_keywords) @@ -220,10 +220,10 @@ function complete_keyword(s::Union{String,SubString{String}})::Vector{Completion r = first(r):i i += 1 end - map(KeywordCompletion, sorted_keywords[r]) + Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]] end -function complete_path(path::AbstractString, pos; use_envpath=false, shell_escape=false)::Completions +function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false) if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) # if the path is just "~", don't consider the expanded username as a prefix if path == "~" @@ -241,10 +241,10 @@ function complete_path(path::AbstractString, pos; use_envpath=false, shell_escap elseif isdir(dir) files = readdir(dir) else - return PathCompletion[], 0:-1, false + return Completion[], 0:-1, false end catch - return PathCompletion[], 0:-1, false + return Completion[], 0:-1, false end matches = Set{String}() @@ -298,7 +298,7 @@ function complete_path(path::AbstractString, pos; use_envpath=false, shell_escap end end - matchList = PathCompletion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches] + matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches] startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix) # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`, # hence we need to add one to get the first index. This is also correct when considering @@ -306,9 +306,9 @@ function complete_path(path::AbstractString, pos; use_envpath=false, shell_escap return matchList, startpos:pos, !isempty(matchList) end -function complete_expanduser(path::AbstractString, r)::Completions +function complete_expanduser(path::AbstractString, r) expanded = expanduser(path) - return [PathCompletion(expanded)], r, path != expanded + return Completion[PathCompletion(expanded)], r, path != expanded end # Determines whether method_complete should be tried. It should only be done if @@ -366,7 +366,7 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') end braces != 1 && return 0:-1, -1 method_name_end = reverseind(s, i) - startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0)) + startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int return (startind:lastindex(s), method_name_end) end @@ -466,16 +466,16 @@ function get_type(sym, fn::Module) end # Method completion on function call expression that look like :(max(1)) -function complete_methods(ex_org::Expr, context_module=Main)::Vector{Completion} +function complete_methods(ex_org::Expr, context_module::Module=Main) args_ex = Any[] - func, found = get_value(ex_org.args[1], context_module) + func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool} !found && return Completion[] funargs = ex_org.args[2:end] # handle broadcasting, but only handle number of arguments instead of # argument types if ex_org.head === :. && ex_org.args[2] isa Expr - for _ in ex_org.args[2].args + for _ in (ex_org.args[2]::Expr).args push!(args_ex, Any) end else @@ -493,7 +493,7 @@ function complete_methods(ex_org::Expr, context_module=Main)::Vector{Completion} ms = method.sig # Check if the method's type signature intersects the input types - if typeintersect(Base.rewrap_unionall(Tuple{Base.unwrap_unionall(ms).parameters[1 : min(na, end)]...}, ms), t_in) != Union{} + if typeintersect(Base.rewrap_unionall(Tuple{(Base.unwrap_unionall(ms)::DataType).parameters[1 : min(na, end)]...}, ms), t_in) !== Union{} push!(out, MethodCompletion(func, t_in, method)) end end @@ -523,7 +523,7 @@ function afterusing(string::String, startpos::Int) return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end]) end -function bslash_completions(string, pos)::Tuple{Bool, Completions} +function bslash_completions(string::String, pos::Int) slashpos = something(findprev(isequal('\\'), string, pos), 0) if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos && !(1 < slashpos && (string[prevind(string, slashpos)]=='\\'))) @@ -531,26 +531,25 @@ function bslash_completions(string, pos)::Tuple{Bool, Completions} s = string[slashpos:pos] latex = get(latex_symbols, s, "") if !isempty(latex) # complete an exact match - return (true, ([BslashCompletion(latex)], slashpos:pos, true)) + return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true)) end emoji = get(emoji_symbols, s, "") if !isempty(emoji) - return (true, ([BslashCompletion(emoji)], slashpos:pos, true)) + return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true)) end # return possible matches; these cannot be mixed with regular # Julian completions as only latex / emoji symbols contain the leading \ if startswith(s, "\\:") # emoji - emoji_names = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols)) - return (true, (map(BslashCompletion, sort!(collect(emoji_names))), slashpos:pos, true)) + namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols)) else # latex - latex_names = Iterators.filter(k -> startswith(k, s), keys(latex_symbols)) - return (true, (map(BslashCompletion, sort!(collect(latex_names))), slashpos:pos, true)) + namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols)) end + return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true)) end return (false, (Completion[], 0:-1, false)) end -function dict_identifier_key(str, tag, context_module = Main) +function dict_identifier_key(str::String, tag::Symbol, context_module::Module = Main) if tag === :string str_close = str*"\"" elseif tag === :cmd @@ -568,14 +567,14 @@ function dict_identifier_key(str, tag, context_module = Main) isdefined(obj, sym) || return (nothing, nothing, nothing) obj = getfield(obj, sym) end - (isa(obj, AbstractDict) && length(obj) < 1_000_000) || return (nothing, nothing, nothing) + (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing) begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [ lastindex(str)+1) - return (obj, str[begin_of_key:end], begin_of_key) + return (obj::AbstractDict, str[begin_of_key:end], begin_of_key) end # This needs to be a separate non-inlined function, see #19441 -@noinline function find_dict_matches(identifier, partial_key) +@noinline function find_dict_matches(identifier::AbstractDict, partial_key) matches = String[] for key in keys(identifier) rkey = repr(key) @@ -584,7 +583,7 @@ end return matches end -function project_deps_get_completion_candidates(pkgstarts::String, project_file::String)::Vector{Completion} +function project_deps_get_completion_candidates(pkgstarts::String, project_file::String) loading_candidates = String[] open(project_file) do io state = :top @@ -607,7 +606,7 @@ function project_deps_get_completion_candidates(pkgstarts::String, project_file: return Completion[PackageCompletion(name) for name in loading_candidates] end -function completions(string, pos, context_module=Main)::Completions +function completions(string::String, pos::Int, context_module::Module=Main) # First parse everything up to the current position partial = string[1:pos] inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false)) @@ -617,7 +616,7 @@ function completions(string, pos, context_module=Main)::Completions if identifier !== nothing matches = find_dict_matches(identifier, partial_key) length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']') - length(matches)>0 && return [DictCompletion(identifier, match) for match in sort!(matches)], loc:pos, true + length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true end # otherwise... @@ -648,7 +647,7 @@ function completions(string, pos, context_module=Main)::Completions ok && return ret # Make sure that only bslash_completions is working on strings - inc_tag==:string && return String[], 0:-1, false + inc_tag==:string && return Completion[], 0:-1, false if inc_tag === :other && should_method_complete(partial) frange, method_name_end = find_start_brace(partial) # strip preceding ! operator @@ -736,7 +735,7 @@ function completions(string, pos, context_module=Main)::Completions frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end) startpos = first(frange) i = prevind(string, startpos) - elseif c in ["\'\"\`"...] + elseif c in ('\'', '\"', '\`') s = "$c$c"*string[startpos:pos] break else @@ -749,26 +748,28 @@ function completions(string, pos, context_module=Main)::Completions return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true end -function shell_completions(string, pos)::Completions +function shell_completions(string, pos) # First parse everything up to the current position scs = string[1:pos] local args, last_parse try - args, last_parse = Base.shell_parse(scs, true) + args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}} catch return Completion[], 0:-1, false end + ex = args.args[end]::Expr # Now look at the last thing we parsed - isempty(args.args[end].args) && return Completion[], 0:-1, false - arg = args.args[end].args[end] - if all(s -> isa(s, AbstractString), args.args[end].args) + isempty(ex.args) && return Completion[], 0:-1, false + arg = ex.args[end] + if all(s -> isa(s, AbstractString), ex.args) + arg = arg::AbstractString # Treat this as a path # As Base.shell_parse throws away trailing spaces (unless they are escaped), # we need to special case here. # If the last char was a space, but shell_parse ignored it search on "". ignore_last_word = arg != " " && scs[end] == ' ' - prefix = ignore_last_word ? "" : join(args.args[end].args) + prefix = ignore_last_word ? "" : join(ex.args) # Also try looking into the env path if the user wants to complete the first argument use_envpath = !ignore_last_word && length(args.args) < 2 diff --git a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl index 3b21733087917..c6fc1989b1c39 100644 --- a/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl +++ b/stdlib/REPL/src/TerminalMenus/AbstractMenu.jl @@ -307,10 +307,10 @@ overwriting of the previous display. On older versions of Julia, this was called `printMenu` and it lacked the `state` argument/return value. This older function is supported on all Julia 1.x versions but will be dropped in Julia 2.0. """ -function printmenu(out, m::AbstractMenu, cursoridx::Int; oldstate=nothing, init::Bool=false) +function printmenu(out::IO, m::AbstractMenu, cursoridx::Int; oldstate=nothing, init::Bool=false) # TODO Julia 2.0?: get rid of `init` and just use `oldstate` buf = IOBuffer() - lastoption = numoptions(m) + lastoption = numoptions(m)::Int ncleared = oldstate === nothing ? m.pagesize-1 : oldstate if init @@ -330,11 +330,11 @@ function printmenu(out, m::AbstractMenu, cursoridx::Int; oldstate=nothing, init: downscrollable = i == lastline && i != lastoption if upscrollable && downscrollable - print(buf, updown_arrow(m)) + print(buf, updown_arrow(m)::Union{Char,String}) elseif upscrollable - print(buf, up_arrow(m)) + print(buf, up_arrow(m)::Union{Char,String}) elseif downscrollable - print(buf, down_arrow(m)) + print(buf, down_arrow(m)::Union{Char,String}) else print(buf, ' ') end diff --git a/stdlib/REPL/src/TerminalMenus/util.jl b/stdlib/REPL/src/TerminalMenus/util.jl index 68d98dd57c199..8ad9ec0e4100d 100644 --- a/stdlib/REPL/src/TerminalMenus/util.jl +++ b/stdlib/REPL/src/TerminalMenus/util.jl @@ -12,13 +12,13 @@ PAGE_UP, PAGE_DOWN) -readbyte(stream::IO=stdin) = Char(read(stream,1)[1]) +readbyte(stream::IO=stdin) = read(stream, Char) # Read the next key from stdin. It is also able to read several bytes for # escaped keys such as the arrow keys, home/end keys, etc. # Escaped keys are returned using the `Key` enum. -readkey(stream::IO=stdin) = UInt32(_readkey(stream)) -function _readkey(stream::IO=stdin) +readkey(stream::Base.LibuvStream=stdin) = UInt32(_readkey(stream)) +function _readkey(stream::Base.LibuvStream=stdin) c = readbyte(stream) # Escape characters diff --git a/stdlib/REPL/src/Terminals.jl b/stdlib/REPL/src/Terminals.jl index 28a6dd828bfa8..dac19406b3fc1 100644 --- a/stdlib/REPL/src/Terminals.jl +++ b/stdlib/REPL/src/Terminals.jl @@ -76,8 +76,8 @@ cmove_col(t::TextTerminal, c) = cmove(c, getY(t)) hascolor(::TextTerminal) = false # Utility Functions -width(t::TextTerminal) = displaysize(t)[2] -height(t::TextTerminal) = displaysize(t)[1] +width(t::TextTerminal) = (displaysize(t)::Tuple{Int,Int})[2] +height(t::TextTerminal) = (displaysize(t)::Tuple{Int,Int})[1] # For terminals with buffers flush(t::TextTerminal) = nothing @@ -160,6 +160,6 @@ Base.haskey(t::TTYTerminal, key) = haskey(pipe_writer(t), key) Base.getindex(t::TTYTerminal, key) = getindex(pipe_writer(t), key) Base.get(t::TTYTerminal, key, default) = get(pipe_writer(t), key, default) -Base.peek(t::TTYTerminal, ::Type{T}) where {T} = peek(t.in_stream, T) +Base.peek(t::TTYTerminal, ::Type{T}) where {T} = peek(t.in_stream, T)::T end # module diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 8246f4380087a..3593ad3c089e7 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -51,7 +51,7 @@ function _helpmode(io::IO, line::AbstractString) # keyword such as `function` would throw a parse error due to the missing `end`. assym elseif isexpr(x, (:using, :import)) - x.head + (x::Expr).head else # Retrieving docs for macros requires us to make a distinction between the text # `@macroname` and `@macroname()`. These both parse the same, but are used by @@ -67,7 +67,7 @@ end _helpmode(line::AbstractString) = _helpmode(stdout, line) # Print vertical lines along each docstring if there are multiple docs -function insert_hlines(io::IO, docs) +function insert_hlines(io::IO, docs::Markdown.MD) if !isa(docs, Markdown.MD) || !haskey(docs.meta, :results) || isempty(docs.meta[:results]) return docs end @@ -250,13 +250,13 @@ function summarize(binding::Binding, sig) return md end -function summarize(io::IO, λ::Function, binding) +function summarize(io::IO, λ::Function, binding::Binding) kind = startswith(string(binding.var), '@') ? "macro" : "`Function`" println(io, "`", binding, "` is a ", kind, ".") println(io, "```\n", methods(λ), "\n```") end -function summarize(io::IO, T::DataType, binding) +function summarize(io::IO, T::DataType, binding::Binding) println(io, "# Summary") println(io, "```") println(io, @@ -292,11 +292,11 @@ function summarize(io::IO, T::DataType, binding) end end -function summarize(io::IO, m::Module, binding) +function summarize(io::IO, m::Module, binding::Binding) println(io, "No docstring found for module `", m, "`.\n") end -function summarize(io::IO, @nospecialize(T), binding) +function summarize(io::IO, @nospecialize(T), binding::Binding) T = typeof(T) println(io, "`", binding, "` is of type `", T, "`.\n") summarize(io, T, binding) @@ -304,10 +304,10 @@ end # repl search and completions for help -function repl_search(io::IO, s) +function repl_search(io::IO, s::Union{Symbol,String}) pre = "search:" print(io, pre) - printmatches(io, s, doc_completions(s), cols = displaysize(io)[2] - length(pre)) + printmatches(io, s, doc_completions(s), cols = _displaysize(io)[2] - length(pre)) println(io, "\n") end repl_search(s) = repl_search(stdout, s) @@ -358,7 +358,7 @@ function repl_latex(io::IO, s::String) end repl_latex(s::String) = repl_latex(stdout, s) -macro repl(ex, brief=false) repl(ex; brief=brief) end +macro repl(ex, brief::Bool=false) repl(ex; brief=brief) end macro repl(io, ex, brief) repl(io, ex; brief=brief) end function repl(io::IO, s::Symbol; brief::Bool=true) @@ -378,10 +378,11 @@ repl(io::IO, str::AbstractString; brief::Bool=true) = :(apropos($io, $str)) repl(io::IO, other; brief::Bool=true) = esc(:(@doc $other)) #repl(io::IO, other) = lookup_doc(other) # TODO -repl(x; brief=true) = repl(stdout, x; brief=brief) +repl(x; brief::Bool=true) = repl(stdout, x; brief=brief) -function _repl(x, brief=true) +function _repl(x, brief::Bool=true) if isexpr(x, :call) + x = x::Expr # determine the types of the values kwargs = nothing pargs = Any[] @@ -482,7 +483,7 @@ fielddoc(object, field::Symbol) = fielddoc(aliasof(object, typeof(object)), fiel # Fuzzy Search Algorithm -function matchinds(needle, haystack; acronym = false) +function matchinds(needle, haystack; acronym::Bool = false) chars = collect(needle) is = Int[] lastc = '\0' @@ -519,8 +520,8 @@ function fuzzyscore(needle, haystack) return score end -function fuzzysort(search, candidates) - scores = map(cand -> (fuzzyscore(search, cand), -levenshtein(search, cand)), candidates) +function fuzzysort(search::String, candidates::Vector{String}) + scores = map(cand -> (fuzzyscore(search, cand), -Float64(levenshtein(search, cand))), candidates) candidates[sortperm(scores)] |> reverse end @@ -544,8 +545,8 @@ function levenshtein(s1, s2) return d[m+1, n+1] end -function levsort(search, candidates) - scores = map(cand -> (levenshtein(search, cand), -fuzzyscore(search, cand)), candidates) +function levsort(search::String, candidates::Vector{String}) + scores = map(cand -> (Float64(levenshtein(search, cand)), -fuzzyscore(search, cand)), candidates) candidates = candidates[sortperm(scores)] i = 0 for outer i = 1:length(candidates) @@ -569,7 +570,7 @@ end printmatch(args...) = printfuzzy(stdout, args...) -function printmatches(io::IO, word, matches; cols = displaysize(io)[2]) +function printmatches(io::IO, word, matches; cols::Int = _displaysize(io)[2]) total = 0 for match in matches total + length(match) + 1 > cols && break @@ -580,9 +581,9 @@ function printmatches(io::IO, word, matches; cols = displaysize(io)[2]) end end -printmatches(args...; cols = displaysize(stdout)[2]) = printmatches(stdout, args..., cols = cols) +printmatches(args...; cols::Int = _displaysize(stdout)[2]) = printmatches(stdout, args..., cols = cols) -function print_joined_cols(io::IO, ss, delim = "", last = delim; cols = displaysize(io)[2]) +function print_joined_cols(io::IO, ss::Vector{String}, delim = "", last = delim; cols::Int = _displaysize(io)[2]) i = 0 total = 0 for outer i = 1:length(ss) @@ -592,13 +593,13 @@ function print_joined_cols(io::IO, ss, delim = "", last = delim; cols = displays join(io, ss[1:i], delim, last) end -print_joined_cols(args...; cols = displaysize(stdout)[2]) = print_joined_cols(stdout, args...; cols=cols) +print_joined_cols(args...; cols::Int = _displaysize(stdout)[2]) = print_joined_cols(stdout, args...; cols=cols) -function print_correction(io, word) +function print_correction(io::IO, word::String) cors = levsort(word, accessible(Main)) pre = "Perhaps you meant " print(io, pre) - print_joined_cols(io, cors, ", ", " or "; cols = displaysize(io)[2] - length(pre)) + print_joined_cols(io, cors, ", ", " or "; cols = _displaysize(io)[2] - length(pre)) println(io) return end diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 6044040e22962..a0d614edce5c1 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -96,10 +96,10 @@ function map_completion_text(completions) return map(completion_text, c), r, res end -test_complete(s) = map_completion_text(completions(s,lastindex(s))) -test_scomplete(s) = map_completion_text(shell_completions(s,lastindex(s))) -test_bslashcomplete(s) = map_completion_text(bslash_completions(s,lastindex(s))[2]) -test_complete_context(s) = map_completion_text(completions(s,lastindex(s),Main.CompletionFoo)) +test_complete(s) = map_completion_text(@inferred(completions(s,lastindex(s)))) +test_scomplete(s) = map_completion_text(@inferred(shell_completions(s,lastindex(s)))) +test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s,lastindex(s)))[2]) +test_complete_context(s) = map_completion_text(@inferred(completions(s,lastindex(s),Main.CompletionFoo))) module M32377 end test_complete_32377(s) = map_completion_text(completions(s,lastindex(s), M32377)) @@ -135,7 +135,7 @@ end let s = "Main.CompletionFoo." c, r = test_complete(s) @test "bar" in c - @test r === UnitRange{Int64}(20:19) + @test r === 20:19 @test s[r] == "" end @@ -596,7 +596,7 @@ end let c, r, res c, r, res = test_scomplete("\$a") @test c == String[] - @test r === UnitRange{Int64}(0:-1) + @test r === 0:-1 @test res === false end @@ -646,7 +646,7 @@ let s, c, r s = "/tmp/" c,r = test_scomplete(s) @test !("tmp/" in c) - @test r === UnitRange{Int64}(6:5) + @test r === 6:5 @test s[r] == "" end @@ -663,7 +663,7 @@ let s, c, r file = joinpath(path, "repl completions") s = "/tmp " c,r = test_scomplete(s) - @test r === UnitRange{Int64}(6:5) + @test r === 6:5 end # Test completing paths with an escaped trailing space @@ -977,7 +977,7 @@ end let s = "" c, r = test_complete_context(s) @test "bar" in c - @test r === UnitRange{Int64}(1:0) + @test r === 1:0 @test s[r] == "" end