From 8f7af0e2bbce613746e8d9966cb16475e33c058a Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 30 Oct 2014 22:20:50 -0400 Subject: [PATCH 01/10] Modal prefix history search. Attempt to deal with down arrow better at end of history. --- base/LineEdit.jl | 199 +++++++++++++++++++++++++++++++++++---------- base/REPL.jl | 38 ++++----- base/precompile.jl | 12 +-- 3 files changed, 183 insertions(+), 66 deletions(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index ae777dcf86fb4..b5fa988425330 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -61,7 +61,7 @@ type PromptState indent::Int end -input_string(s::PromptState) = bytestring(pointer(s.input_buffer.data), s.input_buffer.size) +input_string(s::PromptState) = bytestring(s.input_buffer) input_string_newlines(s::PromptState) = count(c->(c == '\n'), input_string(s)) function input_string_newlines_aftercursor(s::PromptState) @@ -609,8 +609,8 @@ history_next(::EmptyHistoryProvider) = ("", false) history_search(::EmptyHistoryProvider, args...) = false add_history(::EmptyHistoryProvider, s) = nothing add_history(s::PromptState) = add_history(mode(s).hist, s) -history_next_prefix(s, hist) = false -history_prev_prefix(s, hist) = false +history_next_prefix(s, hist, prefix) = false +history_prev_prefix(s, hist, prefix) = false function history_prev(s, hist) l, ok = history_prev(mode(s).hist) @@ -639,12 +639,12 @@ refresh_line(s, termbuf) = refresh_multi_line(termbuf, s) default_completion_cb(::IOBuffer) = [] default_enter_cb(_) = true -write_prompt(terminal, s::PromptState) = write_prompt(terminal, s, s.p.prompt) -function write_prompt(terminal, s::PromptState, prompt) - prefix = isa(s.p.prompt_prefix,Function) ? s.p.prompt_prefix() : s.p.prompt_prefix - suffix = isa(s.p.prompt_suffix,Function) ? s.p.prompt_suffix() : s.p.prompt_suffix +write_prompt(terminal, s::PromptState) = write_prompt(terminal, s.p) +function write_prompt(terminal, p::Prompt) + prefix = isa(p.prompt_prefix,Function) ? p.prompt_prefix() : p.prompt_prefix + suffix = isa(p.prompt_suffix,Function) ? p.prompt_suffix() : p.prompt_suffix write(terminal, prefix) - write(terminal, prompt) + write(terminal, p.prompt) write(terminal, Base.text_colors[:normal]) write(terminal, suffix) end @@ -898,9 +898,81 @@ function history_set_backward(s::SearchState, backward) s.backward = backward end -input_string(s::SearchState) = bytestring(pointer(s.query_buffer.data), s.query_buffer.size) +input_string(s::SearchState) = bytestring(s.query_buffer) -refresh_multi_line(termbuf::TerminalBuffer, term, s::Union(SearchState,PromptState)) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) +function reset_state(s::SearchState) + if s.query_buffer.size != 0 + s.query_buffer.size = 0 + s.query_buffer.ptr = 1 + end + if s.response_buffer.size != 0 + s.response_buffer.size = 0 + s.response_buffer.ptr = 1 + end + reset_state(s.histprompt.hp) +end + +type HistoryPrompt{T<:HistoryProvider} <: TextInterface + hp::T + complete + keymap_func::Function + HistoryPrompt(hp) = new(hp, EmptyCompletionProvider()) +end + +HistoryPrompt{T<:HistoryProvider}(hp::T) = HistoryPrompt{T}(hp) +init_state(terminal, p::HistoryPrompt) = SearchState(terminal, p, true, IOBuffer(), IOBuffer()) + +type PrefixSearchState + terminal + histprompt + prefix::ByteString + response_buffer::IOBuffer + ias::InputAreaState + #The prompt whose input will be replaced by the matched history + parent + PrefixSearchState(terminal, histprompt, prefix, response_buffer) = + new(terminal, histprompt, prefix, response_buffer, InputAreaState(0,0)) +end + +input_string(s::PrefixSearchState) = bytestring(s.response_buffer) + +# a meta-prompt that presents itself as parent_prompt, but which has an independent keymap +# for prefix searching +type PrefixHistoryPrompt{T<:HistoryProvider} <: TextInterface + hp::T + parent_prompt::Prompt + complete + keymap_func::Function + PrefixHistoryPrompt(hp, parent_prompt) = new(hp, parent_prompt, EmptyCompletionProvider()) +end + +PrefixHistoryPrompt{T<:HistoryProvider}(hp::T, parent_prompt) = PrefixHistoryPrompt{T}(hp, parent_prompt) +init_state(terminal, p::PrefixHistoryPrompt) = PrefixSearchState(terminal, p, "", IOBuffer()) + +write_prompt(terminal, s::PrefixSearchState) = write_prompt(terminal, s.histprompt.parent_prompt) +prompt_string(s::PrefixSearchState) = s.histprompt.parent_prompt.prompt + +terminal(s::PrefixSearchState) = s.terminal + +function reset_state(s::PrefixSearchState) + if s.response_buffer.size != 0 + s.response_buffer.size = 0 + s.response_buffer.ptr = 1 + end +end + +function transition(s::PrefixSearchState, mode) + s.histprompt.parent_prompt = mode +end + +replace_line(s::PrefixSearchState, l::IOBuffer) = s.response_buffer = l +function replace_line(s::PrefixSearchState, l) + s.response_buffer.ptr = 1 + s.response_buffer.size = 0 + write(s.response_buffer, l) +end + +refresh_multi_line(termbuf::TerminalBuffer, term, s::Union(SearchState,PrefixSearchState,PromptState)) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) buf = IOBuffer() write(buf, pointer(s.query_buffer.data), s.query_buffer.ptr-1) @@ -914,7 +986,10 @@ function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) s.ias = refresh_multi_line(termbuf, s.terminal, buf, s.ias, s.backward ? "(reverse-i-search)`" : "(forward-i-search)`") end -function refresh_multi_line(s::Union(SearchState,PromptState)) +refresh_multi_line(termbuf::TerminalBuffer, s::PrefixSearchState) = s.ias = + refresh_multi_line(termbuf, terminal(s), buffer(s), s.ias, s) + +function refresh_multi_line(s::Union(SearchState,PrefixSearchState,PromptState)) refresh_multi_line(terminal(s), s) end @@ -928,33 +1003,12 @@ function refresh_multi_line(terminal::UnixTerminal, args...; kwargs...) return ret end -function reset_state(s::SearchState) - if s.query_buffer.size != 0 - s.query_buffer.size = 0 - s.query_buffer.ptr = 1 - end - if s.response_buffer.size != 0 - s.response_buffer.size = 0 - s.response_buffer.ptr = 1 - end - reset_state(s.histprompt.hp) -end - -type HistoryPrompt{T<:HistoryProvider} <: TextInterface - hp::T - complete - keymap_func::Function - HistoryPrompt(hp) = new(hp, EmptyCompletionProvider()) -end - -HistoryPrompt{T<:HistoryProvider}(hp::T) = HistoryPrompt{T}(hp) -init_state(terminal, p::HistoryPrompt) = SearchState(terminal, p, true, IOBuffer(), IOBuffer()) - state(s::MIState, p) = s.mode_state[p] state(s::PromptState, p) = (@assert s.p == p; s) mode(s::MIState) = s.current_mode mode(s::PromptState) = s.p mode(s::SearchState) = @assert false +mode(s::PrefixSearchState) = s.histprompt.parent_prompt # Search Mode completions function complete_line(s::SearchState, repeats) @@ -995,6 +1049,21 @@ function enter_search(s::MIState, p::HistoryPrompt, backward::Bool) transition(s, p) end +function enter_prefix_search(s::MIState, p::PrefixHistoryPrompt, backward::Bool) + buf = buffer(s) + + pss = state(s, p) + pss.parent = mode(s) + pss.prefix = bytestring(pointer(buf.data), position(buf)) + copybuf!(pss.response_buffer, buf) + transition(s, p) + if backward + history_prev_prefix(pss, pss.histprompt.hp, pss.prefix) + else + history_next_prefix(pss, pss.histprompt.hp, pss.prefix) + end +end + function setup_search_keymap(hp) p = HistoryPrompt(hp) pkeymap = AnyDict( @@ -1075,8 +1144,8 @@ function setup_search_keymap(hp) (p, skeymap) end -keymap(state, p::HistoryPrompt) = p.keymap_func -keymap_data(state, ::HistoryPrompt) = state +keymap(state, p::Union(HistoryPrompt,PrefixHistoryPrompt)) = p.keymap_func +keymap_data(state, ::Union(HistoryPrompt, PrefixHistoryPrompt)) = state Base.isempty(s::PromptState) = s.input_buffer.size == 0 @@ -1225,33 +1294,80 @@ AnyDict( end edit_insert(s, input) end, - "^T" => (s,o...)->edit_transpose(s), + "^T" => (s,o...)->edit_transpose(s) ) const history_keymap = AnyDict( "^P" => (s,o...)->(history_prev(s, mode(s).hist)), "^N" => (s,o...)->(history_next(s, mode(s).hist)), # Up Arrow - "\e[A" => (s,o...)->(edit_move_up(s) || history_prev_prefix(s, mode(s).hist)), + "\e[A" => (s,o...)->(edit_move_up(s) || history_prev(s, mode(s).hist)), # Down Arrow - "\e[B" => (s,o...)->(edit_move_down(s) || history_next_prefix(s, mode(s).hist)), + "\e[B" => (s,o...)->(edit_move_down(s) || history_next(s, mode(s).hist)), # Page Up "\e[5~" => (s,o...)->(history_prev(s, mode(s).hist)), # Page Down "\e[6~" => (s,o...)->(history_next(s, mode(s).hist)) ) -function deactivate(p::Union(Prompt,HistoryPrompt), s::Union(SearchState,PromptState), termbuf) +const prefix_history_keymap = AnyDict( + # Up Arrow + "\e[A" => (s,data,c)->history_prev_prefix(data, data.histprompt.hp, data.prefix), + # Down Arrow + "\e[B" => (s,data,c)->history_next_prefix(data, data.histprompt.hp, data.prefix), + # # Right Arrow + "\e[C" => (s,data,c)->(accept_result(s, data.histprompt); edit_move_right(s)), + # # Left Arrow + "\e[D" => (s,data,c)->(accept_result(s, data.histprompt); edit_move_left(s)), + '\r' => (s,data,c)->begin + accept_result(s, data.histprompt) + if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1) + commit_line(s) + return :done + else + edit_insert(s, '\n') + end + end, + '\n' => '\r', + "^C" => (s,data,c)->begin + accept_result(s, data.histprompt) + try # raise the debugger if present + ccall(:jl_raise_debugger, Int, ()) + end + move_input_end(s) + refresh_line(s) + print(terminal(s), "^C\n\n") + transition(s, :reset) + refresh_line(s) + end, + # by default I want to return to the parent mode and pass thru the character... how do I do that? + # "*" => (s,data,c)->(accept_result(s, data.histprompt); write(terminal(s).in_stream, c)) + "*" => (s,data,c)->accept_result(s, data.histprompt) +) + +function setup_prefix_keymap(hp, parent_prompt) + p = PrefixHistoryPrompt(hp, parent_prompt) + p.keymap_func = keymap([prefix_history_keymap]) + pkeymap = AnyDict( + # Up Arrow + "\e[A" => (s,o...)->(edit_move_up(s) || enter_prefix_search(s, p, true)), + # Down Arrow + "\e[B" => (s,o...)->(edit_move_down(s) || enter_prefix_search(s, p, false)), + ) + (p, pkeymap) +end + +function deactivate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::Union(PrefixSearchState,SearchState,PromptState), termbuf) clear_input_area(termbuf, s) s end -function activate(p::Union(Prompt,HistoryPrompt), s::Union(SearchState,PromptState), termbuf) +function activate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::Union(PrefixSearchState,SearchState,PromptState), termbuf) s.ias = InputAreaState(0, 0) refresh_line(s, termbuf) end -function activate(p::Union(Prompt,HistoryPrompt), s::MIState, termbuf) +function activate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::MIState, termbuf) @assert p == s.current_mode activate(p, s.mode_state[s.current_mode], termbuf) end @@ -1332,6 +1448,7 @@ end buffer(s::PromptState) = s.input_buffer buffer(s::SearchState) = s.query_buffer +buffer(s::PrefixSearchState) = s.response_buffer keymap(s::PromptState, prompt::Prompt) = prompt.keymap_func keymap_data(s::PromptState, prompt::Prompt) = prompt.keymap_func_data diff --git a/base/REPL.jl b/base/REPL.jl index a0b5d57146813..9bfb4b326650b 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -384,7 +384,7 @@ function add_history(hist::REPLHistoryProvider, s) flush(hist.history_file) end -function history_move(s::LineEdit.MIState, hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx) +function history_move(s::Union(LineEdit.MIState,LineEdit.PrefixSearchState), hist::REPLHistoryProvider, idx::Int, save_idx::Int = hist.cur_idx) max_idx = length(hist.history) + 1 @assert 1 <= hist.cur_idx <= max_idx (1 <= idx <= max_idx) || return :none @@ -469,34 +469,33 @@ function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider, end end -function history_move_prefix(s::LineEdit.MIState, +function history_move_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, + prefix::String, backwards::Bool) - buf = LineEdit.buffer(s) - pos = position(buf) - prefix = bytestring_beforecursor(buf) - allbuf = bytestring(buf) + cur_response = bytestring(LineEdit.buffer(s)) cur_idx = hist.cur_idx # when searching forward, start at last_idx if !backwards && hist.last_idx > 0 cur_idx = hist.last_idx end hist.last_idx = -1 - idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):length(hist.history)) + max_idx = length(hist.history)+1 + idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):max_idx) for idx in idxs - if beginswith(hist.history[idx], prefix) && hist.history[idx] != allbuf + if (idx == max_idx) || (beginswith(hist.history[idx], prefix) && hist.history[idx] != cur_response) history_move(s, hist, idx) - seek(LineEdit.buffer(s), pos) + LineEdit.move_input_end(s) LineEdit.refresh_line(s) return :ok end end Terminals.beep(LineEdit.terminal(s)) end -history_next_prefix(s::LineEdit.MIState, hist::REPLHistoryProvider) = - history_move_prefix(s, hist, false) -history_prev_prefix(s::LineEdit.MIState, hist::REPLHistoryProvider) = - history_move_prefix(s, hist, true) +history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::String) = + history_move_prefix(s, hist, prefix, false) +history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::String) = + history_move_prefix(s, hist, prefix, true) function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer, backwards::Bool=false, skip_current::Bool=false) @@ -695,9 +694,8 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep shell_mode.hist = hp help_mode.hist = hp - hkp, hkeymap = LineEdit.setup_search_keymap(hp) - - hkp.complete = LatexCompletions() + search_prompt, skeymap = LineEdit.setup_search_keymap(hp) + search_prompt.complete = LatexCompletions() # Canonicalize user keymap input if isa(extra_repl_keymap, Dict) @@ -777,7 +775,9 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep end, ) - a = Dict{Any,Any}[hkeymap, repl_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] + prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt) + + a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] prepend!(a, extra_repl_keymap) julia_prompt.keymap_func = LineEdit.keymap(a) @@ -803,12 +803,12 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep end ) - b = Dict{Any,Any}[hkeymap, mode_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] + b = Dict{Any,Any}[skeymap, mode_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] prepend!(b, extra_repl_keymap) shell_mode.keymap_func = help_mode.keymap_func = LineEdit.keymap(b) - ModalInterface([julia_prompt, shell_mode, help_mode,hkp]) + ModalInterface([julia_prompt, shell_mode, help_mode, search_prompt, prefix_prompt]) end function run_frontend(repl::LineEditREPL, backend) diff --git a/base/precompile.jl b/base/precompile.jl index 918bfd704bf47..277adf6669946 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -72,11 +72,11 @@ precompile(Base.LineEdit.init_state, (Base.Terminals.TTYTerminal, Base.LineEdit. precompile(Base.LineEdit.input_string, (Base.LineEdit.PromptState,)) precompile(Base.LineEdit.keymap, (Base.LineEdit.PromptState, Base.LineEdit.Prompt)) precompile(Base.LineEdit.keymap_data, (Base.LineEdit.PromptState, Base.LineEdit.Prompt)) -precompile(Base.LineEdit.keymap_fcn, (Function, Base.LineEdit.MIState, Char)) -precompile(Base.LineEdit.keymap_fcn, (Function, Base.LineEdit.MIState,Char)) +precompile(Base.LineEdit.keymap_fcn, (Function, Base.LineEdit.MIState, ByteString)) +precompile(Base.LineEdit.keymap_fcn, (Function, Base.LineEdit.MIState, ByteString)) precompile(Base.LineEdit.match_input, (Dict{Char,Any},Base.LineEdit.MIState)) -precompile(Base.LineEdit.match_input, (Function, Base.LineEdit.MIState, Array{Char, 1})) -precompile(Base.LineEdit.match_input, (Function, Base.LineEdit.MIState, Array{Char,1})) +precompile(Base.LineEdit.match_input, (Dict{Char, Any}, Base.LineEdit.MIState, Base.Terminals.TTYTerminal)) +precompile(Base.LineEdit.match_input, (Function, Base.LineEdit.MIState, Base.Terminals.TTYTerminal, Array{Char,1})) precompile(Base.LineEdit.mode, (Base.LineEdit.MIState,)) precompile(Base.LineEdit.move_line_end, (Base.LineEdit.MIState,)) precompile(Base.LineEdit.on_enter, (Base.LineEdit.MIState,)) @@ -104,8 +104,8 @@ precompile(Base.LineEdit.transition, (Base.LineEdit.MIState, Base.LineEdit.Promp precompile(Base.LineEdit.transition, (Base.LineEdit.MIState, Symbol)) precompile(Base.LineEdit.update_key_repeats, (Base.LineEdit.MIState, Array{Char, 1})) precompile(Base.LineEdit.update_key_repeats, (Base.LineEdit.MIState, Array{Char,1})) -precompile(Base.LineEdit.write_prompt, (Base.Terminals.TTYTerminal, Base.LineEdit.PromptState, ASCIIString)) -precompile(Base.LineEdit.write_prompt, (Base.Terminals.TerminalBuffer, Base.LineEdit.PromptState, ASCIIString)) +precompile(Base.LineEdit.write_prompt, (Base.Terminals.TTYTerminal, Base.LineEdit.Prompt)) +precompile(Base.LineEdit.write_prompt, (Base.Terminals.TerminalBuffer, Base.LineEdit.PromptState)) precompile(Base.Multimedia.TextDisplay, (Base.TTY,)) precompile(Base.Multimedia.display, (Int,)) precompile(Base.ProcessGroup, (Int, Array{Any,1}, Array{Any,1})) From 551fdfe8669af5ae23b98691328c56ed2fa7066a Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Sun, 2 Nov 2014 15:24:50 -0500 Subject: [PATCH 02/10] Refine prefix matching condition to consider mode changes as different. --- base/REPL.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/REPL.jl b/base/REPL.jl index 9bfb4b326650b..411306ffd397a 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -483,7 +483,7 @@ function history_move_prefix(s::LineEdit.PrefixSearchState, max_idx = length(hist.history)+1 idxs = backwards ? ((cur_idx-1):-1:1) : ((cur_idx+1):max_idx) for idx in idxs - if (idx == max_idx) || (beginswith(hist.history[idx], prefix) && hist.history[idx] != cur_response) + if (idx == max_idx) || (beginswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || hist.modes[idx] != LineEdit.mode(s))) history_move(s, hist, idx) LineEdit.move_input_end(s) LineEdit.refresh_line(s) From 97f2a5d34aa67a265125fe21ec84d1d913c4ad7f Mon Sep 17 00:00:00 2001 From: "Blake R. Johnson" Date: Mon, 3 Nov 2014 12:08:04 -0500 Subject: [PATCH 03/10] Pass thru keypresses to parent mode. Update lineedit test for new usage. --- base/LineEdit.jl | 70 +++++++++++++++++------------------------------- test/lineedit.jl | 2 +- 2 files changed, 25 insertions(+), 47 deletions(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index b5fa988425330..26d11bdc4c193 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -725,18 +725,18 @@ function normalize_keymap(keymap::Dict) ret end -match_input(k::Function, s, cs) = (update_key_repeats(s, cs); return keymap_fcn(k, s, last(cs))) -match_input(k::Void, s, cs) = (s,p) -> return :ok -function match_input(keymap::Dict, s, cs=Char[]) - c = read(terminal(s), Char) +match_input(k::Function, s, term, cs) = (update_key_repeats(s, cs); return keymap_fcn(k, s, convert(ByteString, cs))) +match_input(k::Void, s, term, cs) = (s,p) -> return :ok +function match_input(keymap::Dict, s, term, cs=Char[]) + c = read(term, Char) push!(cs, c) k = haskey(keymap, c) ? c : '\0' # if we don't match on the key, look for a default action then fallback on 'nothing' to ignore - return match_input(get(keymap, k, nothing), s, cs) + return match_input(get(keymap, k, nothing), s, term, cs) end keymap_fcn(f::Void, s, c) = (s, p) -> return :ok -function keymap_fcn(f::Function, s, c::Char) +function keymap_fcn(f::Function, s, c) return (s, p) -> begin r = f(s, p, c) if isa(r, Symbol) @@ -830,7 +830,7 @@ function keymap{D<:Dict}(keymaps::Array{D}) # keymaps is a vector of prioritized keymaps, with highest priority first dict = map(normalize_keys, keymaps) dict = keymap_prepare(merge(reverse(dict)...)) - return (s,p)->match_input(dict, s)(s,p) + return (term,s,p)->match_input(dict, s, term)(s,p) end const escape_defaults = merge!( @@ -1315,34 +1315,12 @@ const prefix_history_keymap = AnyDict( "\e[A" => (s,data,c)->history_prev_prefix(data, data.histprompt.hp, data.prefix), # Down Arrow "\e[B" => (s,data,c)->history_next_prefix(data, data.histprompt.hp, data.prefix), - # # Right Arrow - "\e[C" => (s,data,c)->(accept_result(s, data.histprompt); edit_move_right(s)), - # # Left Arrow - "\e[D" => (s,data,c)->(accept_result(s, data.histprompt); edit_move_left(s)), - '\r' => (s,data,c)->begin - accept_result(s, data.histprompt) - if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1) - commit_line(s) - return :done - else - edit_insert(s, '\n') - end - end, - '\n' => '\r', - "^C" => (s,data,c)->begin - accept_result(s, data.histprompt) - try # raise the debugger if present - ccall(:jl_raise_debugger, Int, ()) - end - move_input_end(s) - refresh_line(s) - print(terminal(s), "^C\n\n") - transition(s, :reset) - refresh_line(s) - end, - # by default I want to return to the parent mode and pass thru the character... how do I do that? - # "*" => (s,data,c)->(accept_result(s, data.histprompt); write(terminal(s).in_stream, c)) - "*" => (s,data,c)->accept_result(s, data.histprompt) + # by default, pass thru to the parent mode + "*" => (s,data,c)->begin + accept_result(s, data.histprompt); + ps = state(s, mode(s)) + keymap(ps, mode(s))(IOBuffer(c), s, keymap_data(ps, mode(s))) + end ) function setup_prefix_keymap(hp, parent_prompt) @@ -1455,24 +1433,24 @@ keymap_data(s::PromptState, prompt::Prompt) = prompt.keymap_func_data keymap(ms::MIState, m::ModalInterface) = keymap(ms.mode_state[ms.current_mode], ms.current_mode) keymap_data(ms::MIState, m::ModalInterface) = keymap_data(ms.mode_state[ms.current_mode], ms.current_mode) -function prompt!(terminal, prompt, s = init_state(terminal, prompt)) - Base.reseteof(terminal) - raw!(terminal, true) - enable_bracketed_paste(terminal) +function prompt!(term, prompt, s = init_state(term, prompt)) + Base.reseteof(term) + raw!(term, true) + enable_bracketed_paste(term) try - start_reading(terminal) - activate(prompt, s, terminal) + start_reading(term) + activate(prompt, s, term) while true - state = keymap(s, prompt)(s, keymap_data(s, prompt)) + state = keymap(s, prompt)(terminal(s), s, keymap_data(s, prompt)) if state == :abort - stop_reading(terminal) + stop_reading(term) return buffer(s), false, false elseif state == :done - stop_reading(terminal) + stop_reading(term) return buffer(s), true, false elseif state == :suspend @unix_only begin - stop_reading(terminal) + stop_reading(term) return buffer(s), true, true end else @@ -1480,7 +1458,7 @@ function prompt!(terminal, prompt, s = init_state(terminal, prompt)) end end finally - raw!(terminal, false) && disable_bracketed_paste(terminal) + raw!(term, false) && disable_bracketed_paste(term) end end diff --git a/test/lineedit.jl b/test/lineedit.jl index c5cf284f12be4..9ffc608448cef 100644 --- a/test/lineedit.jl +++ b/test/lineedit.jl @@ -27,7 +27,7 @@ function run_test(f,buf) global a_foo, a_bar, b_bar a_foo = a_bar = b_bar = 0 while !eof(buf) - f(buf,nothing) + f(buf,nothing,nothing) end end From 423fbaa3c3c3b4d24d5622c81b086bfe0d58ed53 Mon Sep 17 00:00:00 2001 From: "Blake R. Johnson" Date: Mon, 3 Nov 2014 12:28:50 -0500 Subject: [PATCH 04/10] Cleanup some mess with types in LineEdit.jl --- base/LineEdit.jl | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index 26d11bdc4c193..3910cfe4b5cb5 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -8,6 +8,7 @@ import ..Terminals: raw!, width, height, cmove, getX, import Base: ensureroom, peek, show, AnyDict abstract TextInterface +abstract ModeState export run_interface, Prompt, ModalInterface, transition, reset_state, edit_insert, keymap @@ -26,9 +27,6 @@ type MIState end MIState(i, c, a, m) = MIState(i, c, a, m, "", Char[], 0) -type Mode <: TextInterface -end - type Prompt <: TextInterface prompt first_prompt @@ -53,7 +51,7 @@ immutable InputAreaState curs_row::Int64 end -type PromptState +type PromptState <: ModeState terminal::TextTerminal p::Prompt input_buffer::IOBuffer @@ -868,7 +866,7 @@ function write_response_buffer(s::PromptState, data) refresh_line(s) end -type SearchState +type SearchState <: ModeState terminal histprompt #rsearch (true) or ssearch (false) @@ -922,7 +920,7 @@ end HistoryPrompt{T<:HistoryProvider}(hp::T) = HistoryPrompt{T}(hp) init_state(terminal, p::HistoryPrompt) = SearchState(terminal, p, true, IOBuffer(), IOBuffer()) -type PrefixSearchState +type PrefixSearchState <: ModeState terminal histprompt prefix::ByteString @@ -972,7 +970,10 @@ function replace_line(s::PrefixSearchState, l) write(s.response_buffer, l) end -refresh_multi_line(termbuf::TerminalBuffer, term, s::Union(SearchState,PrefixSearchState,PromptState)) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) +function refresh_multi_line(s::ModeState) + refresh_multi_line(terminal(s), s) +end +refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) buf = IOBuffer() write(buf, pointer(s.query_buffer.data), s.query_buffer.ptr-1) @@ -989,10 +990,6 @@ end refresh_multi_line(termbuf::TerminalBuffer, s::PrefixSearchState) = s.ias = refresh_multi_line(termbuf, terminal(s), buffer(s), s.ias, s) -function refresh_multi_line(s::Union(SearchState,PrefixSearchState,PromptState)) - refresh_multi_line(terminal(s), s) -end - function refresh_multi_line(terminal::UnixTerminal, args...; kwargs...) outbuf = IOBuffer() termbuf = TerminalBuffer(outbuf) @@ -1335,17 +1332,17 @@ function setup_prefix_keymap(hp, parent_prompt) (p, pkeymap) end -function deactivate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::Union(PrefixSearchState,SearchState,PromptState), termbuf) +function deactivate(p::TextInterface, s::ModeState, termbuf) clear_input_area(termbuf, s) s end -function activate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::Union(PrefixSearchState,SearchState,PromptState), termbuf) +function activate(p::TextInterface, s::ModeState, termbuf) s.ias = InputAreaState(0, 0) refresh_line(s, termbuf) end -function activate(p::Union(Prompt,HistoryPrompt,PrefixHistoryPrompt), s::MIState, termbuf) +function activate(p::TextInterface, s::MIState, termbuf) @assert p == s.current_mode activate(p, s.mode_state[s.current_mode], termbuf) end From 5b354e1f845ba6b3256e8094870af5c14d7dcbe8 Mon Sep 17 00:00:00 2001 From: "Blake R. Johnson" Date: Mon, 3 Nov 2014 15:58:03 -0500 Subject: [PATCH 05/10] Use parent prompt indent. --- base/LineEdit.jl | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index 3910cfe4b5cb5..e9dcef78cbdff 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -183,9 +183,10 @@ end prompt_string(s::PromptState) = s.p.prompt prompt_string(s::AbstractString) = s -refresh_multi_line(termbuf::TerminalBuffer, s::PromptState) = s.ias = +refresh_multi_line(s::ModeState) = refresh_multi_line(terminal(s), s) +refresh_multi_line(termbuf::TerminalBuffer, s::ModeState) = s.ias = refresh_multi_line(termbuf, terminal(s), buffer(s), s.ias, s, indent = s.indent) - +refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf, state::InputAreaState, prompt = ""; indent = 0) cols = width(terminal) @@ -297,6 +298,16 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf return InputAreaState(cur_row, curs_row) end +function refresh_multi_line(terminal::UnixTerminal, args...; kwargs...) + outbuf = IOBuffer() + termbuf = TerminalBuffer(outbuf) + ret = refresh_multi_line(termbuf, terminal, args...;kwargs...) + # Output the entire refresh at once + write(terminal, takebuf_array(outbuf)) + flush(terminal) + return ret +end + # Edit functionality is_non_word_char(c) = c in " \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~" @@ -926,10 +937,11 @@ type PrefixSearchState <: ModeState prefix::ByteString response_buffer::IOBuffer ias::InputAreaState + indent::Int #The prompt whose input will be replaced by the matched history parent PrefixSearchState(terminal, histprompt, prefix, response_buffer) = - new(terminal, histprompt, prefix, response_buffer, InputAreaState(0,0)) + new(terminal, histprompt, prefix, response_buffer, InputAreaState(0,0), 0) end input_string(s::PrefixSearchState) = bytestring(s.response_buffer) @@ -970,10 +982,6 @@ function replace_line(s::PrefixSearchState, l) write(s.response_buffer, l) end -function refresh_multi_line(s::ModeState) - refresh_multi_line(terminal(s), s) -end -refresh_multi_line(termbuf::TerminalBuffer, term, s::ModeState) = (@assert term == terminal(s); refresh_multi_line(termbuf,s)) function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) buf = IOBuffer() write(buf, pointer(s.query_buffer.data), s.query_buffer.ptr-1) @@ -987,19 +995,6 @@ function refresh_multi_line(termbuf::TerminalBuffer, s::SearchState) s.ias = refresh_multi_line(termbuf, s.terminal, buf, s.ias, s.backward ? "(reverse-i-search)`" : "(forward-i-search)`") end -refresh_multi_line(termbuf::TerminalBuffer, s::PrefixSearchState) = s.ias = - refresh_multi_line(termbuf, terminal(s), buffer(s), s.ias, s) - -function refresh_multi_line(terminal::UnixTerminal, args...; kwargs...) - outbuf = IOBuffer() - termbuf = TerminalBuffer(outbuf) - ret = refresh_multi_line(termbuf, terminal, args...;kwargs...) - # Output the entire refresh at once - write(terminal, takebuf_array(outbuf)) - flush(terminal) - return ret -end - state(s::MIState, p) = s.mode_state[p] state(s::PromptState, p) = (@assert s.p == p; s) mode(s::MIState) = s.current_mode @@ -1053,6 +1048,7 @@ function enter_prefix_search(s::MIState, p::PrefixHistoryPrompt, backward::Bool) pss.parent = mode(s) pss.prefix = bytestring(pointer(buf.data), position(buf)) copybuf!(pss.response_buffer, buf) + pss.indent = state(s, mode(s)).indent transition(s, p) if backward history_prev_prefix(pss, pss.histprompt.hp, pss.prefix) From e2a5647826693a080015438eacb4747b7f87e91b Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Mon, 3 Nov 2014 21:05:28 -0500 Subject: [PATCH 06/10] Remove one unnecessary layer of anonymous functions. --- base/LineEdit.jl | 31 +++++++++++++++---------------- base/REPL.jl | 10 +++++----- test/lineedit.jl | 16 ++++++++-------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index e9dcef78cbdff..dbd03b87ca7dc 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -35,7 +35,7 @@ type Prompt <: TextInterface prompt_prefix # Same as prefix except after the prompt prompt_suffix - keymap_func + keymap_dict keymap_func_data complete on_enter @@ -734,9 +734,9 @@ function normalize_keymap(keymap::Dict) ret end -match_input(k::Function, s, term, cs) = (update_key_repeats(s, cs); return keymap_fcn(k, s, convert(ByteString, cs))) +match_input(k::Function, s, term, cs) = (update_key_repeats(s, cs); return keymap_fcn(k, s, ByteString(cs))) match_input(k::Void, s, term, cs) = (s,p) -> return :ok -function match_input(keymap::Dict, s, term, cs=Char[]) +function match_input(keymap::Dict, s, term=terminal(s), cs=Char[]) c = read(term, Char) push!(cs, c) k = haskey(keymap, c) ? c : '\0' @@ -838,8 +838,7 @@ end function keymap{D<:Dict}(keymaps::Array{D}) # keymaps is a vector of prioritized keymaps, with highest priority first dict = map(normalize_keys, keymaps) - dict = keymap_prepare(merge(reverse(dict)...)) - return (term,s,p)->match_input(dict, s, term)(s,p) + return keymap_prepare(merge(reverse(dict)...)) end const escape_defaults = merge!( @@ -924,7 +923,7 @@ end type HistoryPrompt{T<:HistoryProvider} <: TextInterface hp::T complete - keymap_func::Function + keymap_dict::Dict{Char,Any} HistoryPrompt(hp) = new(hp, EmptyCompletionProvider()) end @@ -952,7 +951,7 @@ type PrefixHistoryPrompt{T<:HistoryProvider} <: TextInterface hp::T parent_prompt::Prompt complete - keymap_func::Function + keymap_dict::Dict{Char,Any} PrefixHistoryPrompt(hp, parent_prompt) = new(hp, parent_prompt, EmptyCompletionProvider()) end @@ -1129,7 +1128,7 @@ function setup_search_keymap(hp) end, "*" => (s,data,c)->(edit_insert(data.query_buffer, c); update_display_buffer(s, data)) ) - p.keymap_func = keymap([pkeymap, escape_defaults]) + p.keymap_dict = keymap([pkeymap, escape_defaults]) skeymap = AnyDict( "^R" => (s,o...)->(enter_search(s, p, true)), "^S" => (s,o...)->(enter_search(s, p, false)), @@ -1137,7 +1136,7 @@ function setup_search_keymap(hp) (p, skeymap) end -keymap(state, p::Union(HistoryPrompt,PrefixHistoryPrompt)) = p.keymap_func +keymap(state, p::Union(HistoryPrompt,PrefixHistoryPrompt)) = p.keymap_dict keymap_data(state, ::Union(HistoryPrompt, PrefixHistoryPrompt)) = state Base.isempty(s::PromptState) = s.input_buffer.size == 0 @@ -1312,13 +1311,13 @@ const prefix_history_keymap = AnyDict( "*" => (s,data,c)->begin accept_result(s, data.histprompt); ps = state(s, mode(s)) - keymap(ps, mode(s))(IOBuffer(c), s, keymap_data(ps, mode(s))) + match_input(keymap(ps, mode(s)), s, IOBuffer(c))(s, keymap_data(ps, mode(s))) end ) function setup_prefix_keymap(hp, parent_prompt) p = PrefixHistoryPrompt(hp, parent_prompt) - p.keymap_func = keymap([prefix_history_keymap]) + p.keymap_dict = keymap([prefix_history_keymap]) pkeymap = AnyDict( # Up Arrow "\e[A" => (s,o...)->(edit_move_up(s) || enter_prefix_search(s, p, true)), @@ -1374,13 +1373,13 @@ function reset_state(s::MIState) end end -const default_keymap_func = keymap([default_keymap, escape_defaults]) +const default_keymap_dict = keymap([default_keymap, escape_defaults]) function Prompt(prompt; first_prompt = prompt, prompt_prefix = "", prompt_suffix = "", - keymap_func = default_keymap_func, + keymap_dict = default_keymap_dict, keymap_func_data = nothing, complete = EmptyCompletionProvider(), on_enter = default_enter_cb, @@ -1388,7 +1387,7 @@ function Prompt(prompt; hist = EmptyHistoryProvider(), sticky = false) - Prompt(prompt, first_prompt, prompt_prefix, prompt_suffix, keymap_func, keymap_func_data, + Prompt(prompt, first_prompt, prompt_prefix, prompt_suffix, keymap_dict, keymap_func_data, complete, on_enter, on_done, hist, sticky) end @@ -1421,7 +1420,7 @@ buffer(s::PromptState) = s.input_buffer buffer(s::SearchState) = s.query_buffer buffer(s::PrefixSearchState) = s.response_buffer -keymap(s::PromptState, prompt::Prompt) = prompt.keymap_func +keymap(s::PromptState, prompt::Prompt) = prompt.keymap_dict keymap_data(s::PromptState, prompt::Prompt) = prompt.keymap_func_data keymap(ms::MIState, m::ModalInterface) = keymap(ms.mode_state[ms.current_mode], ms.current_mode) keymap_data(ms::MIState, m::ModalInterface) = keymap_data(ms.mode_state[ms.current_mode], ms.current_mode) @@ -1434,7 +1433,7 @@ function prompt!(term, prompt, s = init_state(term, prompt)) start_reading(term) activate(prompt, s, term) while true - state = keymap(s, prompt)(terminal(s), s, keymap_data(s, prompt)) + state = match_input(keymap(s, prompt), s)(s, keymap_data(s, prompt)) if state == :abort stop_reading(term) return buffer(s), false, false diff --git a/base/REPL.jl b/base/REPL.jl index 411306ffd397a..ccd7bc1bcae18 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -471,7 +471,7 @@ end function history_move_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, - prefix::String, + prefix::AbstractString, backwards::Bool) cur_response = bytestring(LineEdit.buffer(s)) cur_idx = hist.cur_idx @@ -492,9 +492,9 @@ function history_move_prefix(s::LineEdit.PrefixSearchState, end Terminals.beep(LineEdit.terminal(s)) end -history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::String) = +history_next_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) = history_move_prefix(s, hist, prefix, false) -history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::String) = +history_prev_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString) = history_move_prefix(s, hist, prefix, true) function history_search(hist::REPLHistoryProvider, query_buffer::IOBuffer, response_buffer::IOBuffer, @@ -780,7 +780,7 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] prepend!(a, extra_repl_keymap) - julia_prompt.keymap_func = LineEdit.keymap(a) + julia_prompt.keymap_dict = LineEdit.keymap(a) const mode_keymap = AnyDict( '\b' => function (s,o...) @@ -806,7 +806,7 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep b = Dict{Any,Any}[skeymap, mode_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] prepend!(b, extra_repl_keymap) - shell_mode.keymap_func = help_mode.keymap_func = LineEdit.keymap(b) + shell_mode.keymap_dict = help_mode.keymap_dict = LineEdit.keymap(b) ModalInterface([julia_prompt, shell_mode, help_mode, search_prompt, prefix_prompt]) end diff --git a/test/lineedit.jl b/test/lineedit.jl index 9ffc608448cef..b23a5fa9b07cf 100644 --- a/test/lineedit.jl +++ b/test/lineedit.jl @@ -21,28 +21,28 @@ const bar_keymap = Dict( 'b' => (o...)->(global b_bar; b_bar += 1) ) -test1_func = LineEdit.keymap([foo_keymap]) +test1_dict = LineEdit.keymap([foo_keymap]) -function run_test(f,buf) +function run_test(d,buf) global a_foo, a_bar, b_bar a_foo = a_bar = b_bar = 0 while !eof(buf) - f(buf,nothing,nothing) + LineEdit.match_input(d, nothing, buf)(nothing,nothing) end end -run_test(test1_func,IOBuffer("aa")) +run_test(test1_dict,IOBuffer("aa")) @test a_foo == 2 -test2_func = LineEdit.keymap([foo2_keymap, foo_keymap]) +test2_dict = LineEdit.keymap([foo2_keymap, foo_keymap]) -run_test(test2_func,IOBuffer("aaabb")) +run_test(test2_dict,IOBuffer("aaabb")) @test a_foo == 3 @test b_foo == 2 -test3_func = LineEdit.keymap([bar_keymap, foo_keymap]) +test3_dict = LineEdit.keymap([bar_keymap, foo_keymap]) -run_test(test3_func,IOBuffer("aab")) +run_test(test3_dict,IOBuffer("aab")) @test a_bar == 2 @test b_bar == 1 From 7c7f491447efd33d2117226f84ec370cb684bf41 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Mon, 3 Nov 2014 23:00:04 -0500 Subject: [PATCH 07/10] Passthru escape sequences. --- base/LineEdit.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index dbd03b87ca7dc..4cd8fe40663de 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -1312,7 +1312,18 @@ const prefix_history_keymap = AnyDict( accept_result(s, data.histprompt); ps = state(s, mode(s)) match_input(keymap(ps, mode(s)), s, IOBuffer(c))(s, keymap_data(ps, mode(s))) - end + end, + # match escape sequences for pass thru + "\e*" => "*", + "\e[*" => "*", + "\e[1~" => "*", + "\e[3~" => "*", + "\e[4~" => "*", + "\e[5~" => "*", + "\e[6~" => "*", + "\e[7~" => "*", + "\e[8~" => "*", + "\e[200~" => "*" ) function setup_prefix_keymap(hp, parent_prompt) From 7814fd265b3b7e53e22ce7b0b9607652019a6af4 Mon Sep 17 00:00:00 2001 From: "Blake R. Johnson" Date: Mon, 17 Nov 2014 10:20:54 -0500 Subject: [PATCH 08/10] Preserve mode when editing history. --- base/LineEdit.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index 4cd8fe40663de..3cb9b8bdd7027 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -971,6 +971,7 @@ function reset_state(s::PrefixSearchState) end function transition(s::PrefixSearchState, mode) + s.parent = mode s.histprompt.parent_prompt = mode end From 0bfc341bd151b98de39ad3df91cf2ad4498f1bba Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 9 Dec 2014 07:46:19 -0500 Subject: [PATCH 09/10] Drop unknown codes at REPL. Better than producing an EOF error. --- base/LineEdit.jl | 3 +++ base/Terminals.jl | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/base/LineEdit.jl b/base/LineEdit.jl index 3cb9b8bdd7027..88972b7f00a0c 100644 --- a/base/LineEdit.jl +++ b/base/LineEdit.jl @@ -737,6 +737,9 @@ end match_input(k::Function, s, term, cs) = (update_key_repeats(s, cs); return keymap_fcn(k, s, ByteString(cs))) match_input(k::Void, s, term, cs) = (s,p) -> return :ok function match_input(keymap::Dict, s, term=terminal(s), cs=Char[]) + # if we run out of characters to match before resolving an action, + # return an empty keymap function + eof(term) && return keymap_fcn(nothing, s, "") c = read(term, Char) push!(cs, c) k = haskey(keymap, c) ? c : '\0' diff --git a/base/Terminals.jl b/base/Terminals.jl index d0a56c7cca152..9ac5db6ef1def 100644 --- a/base/Terminals.jl +++ b/base/Terminals.jl @@ -32,7 +32,8 @@ import Base: stop_reading, write, writemime, - reseteof + reseteof, + eof ## TextTerminal ## @@ -193,6 +194,7 @@ readuntil(t::UnixTerminal, s) = readuntil(t.in_stream, s) read(t::UnixTerminal, ::Type{UInt8}) = read(t.in_stream, UInt8) start_reading(t::UnixTerminal) = start_reading(t.in_stream) stop_reading(t::UnixTerminal) = stop_reading(t.in_stream) +eof(t::UnixTerminal) = eof(t.in_stream) @unix_only hascolor(t::TTYTerminal) = (beginswith(t.term_type, "xterm") || success(`tput setaf 0`)) @windows_only hascolor(t::TTYTerminal) = true From d8c89d23bb20ce60c290ef5555eb89826e27cbd1 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 11 Dec 2014 00:12:05 -0500 Subject: [PATCH 10/10] Keep cursor at prefix position. --- base/REPL.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/base/REPL.jl b/base/REPL.jl index ccd7bc1bcae18..e244d493c3b51 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -485,7 +485,13 @@ function history_move_prefix(s::LineEdit.PrefixSearchState, for idx in idxs if (idx == max_idx) || (beginswith(hist.history[idx], prefix) && (hist.history[idx] != cur_response || hist.modes[idx] != LineEdit.mode(s))) history_move(s, hist, idx) - LineEdit.move_input_end(s) + if length(prefix) == 0 + # on empty prefix search, move cursor to the end + LineEdit.move_input_end(s) + else + # otherwise, keep cursor at the prefix position as a visual cue + seek(LineEdit.buffer(s), length(prefix)) + end LineEdit.refresh_line(s) return :ok end