Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

REPL: enable "redo" after "undo" #23590

Merged
merged 3 commits into from
Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 77 additions & 31 deletions base/repl/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ end
MIState(i, c, a, m) = MIState(i, c, a, m, String[], 0, Char[], 0, :begin)

function show(io::IO, s::MIState)
print(io, "MI State (", s.current_mode, " active)")
print(io, "MI State (", mode(s), " active)")
end

struct InputAreaState
Expand All @@ -68,6 +68,7 @@ mutable struct PromptState <: ModeState
p::Prompt
input_buffer::IOBuffer
undo_buffers::Vector{IOBuffer}
undo_idx::Int
ias::InputAreaState
# indentation of lines which do not include the prompt
# if negative, the width of the prompt is used
Expand Down Expand Up @@ -116,13 +117,13 @@ terminal(s::PromptState) = s.terminal
for f in [:terminal, :on_enter, :add_history, :buffer, :(Base.isempty),
:replace_line, :refresh_multi_line, :input_string, :update_display_buffer,
:empty_undo, :push_undo, :pop_undo]
@eval ($f)(s::MIState, args...) = $(f)(s.mode_state[s.current_mode], args...)
@eval ($f)(s::MIState, args...) = $(f)(state(s), args...)
end

for f in [:edit_insert, :edit_insert_newline, :edit_backspace, :edit_move_left,
:edit_move_right, :edit_move_word_left, :edit_move_word_right]
@eval function ($f)(s::MIState, args...)
$(f)(s.mode_state[s.current_mode], args...)
$(f)(state(s), args...)
return $(Expr(:quote, f))
end
end
Expand Down Expand Up @@ -172,7 +173,7 @@ end

# Prompt Completions
function complete_line(s::MIState)
complete_line(s.mode_state[s.current_mode], s.key_repeats)
complete_line(state(s), s.key_repeats)
refresh_line(s)
:complete_line
end
Expand Down Expand Up @@ -725,10 +726,14 @@ function edit_copy_region(s::MIState)
end

function edit_kill_region(s::MIState)
push_kill!(s, edit_splice!(s)) || return :ignore
push_undo(s)
refresh_line(s)
:edit_kill_region
if push_kill!(s, edit_splice!(s))
refresh_line(s)
:edit_kill_region
else
pop_undo(s)
:ignore
end
end

function edit_transpose_chars(s::MIState)
Expand Down Expand Up @@ -1352,8 +1357,8 @@ 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

state(s::MIState, p) = s.mode_state[p]
state(s::PromptState, p) = (@assert s.p == p; s)
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
mode(s::SearchState) = @assert false
Expand Down Expand Up @@ -1668,7 +1673,8 @@ AnyDict(
# Meta Enter
"\e\r" => (s,o...)->edit_insert_newline(s),
"\e\n" => "\e\r",
"^_" => (s,o...)->(pop_undo(s) ? refresh_line(s) : beep(terminal(s))),
"^_" => (s,o...)->edit_undo!(s),
"\e_" => (s,o...)->edit_redo!(s),
# Simply insert it into the buffer by default
"*" => (s,data,c)->(edit_insert(s, c)),
"^U" => (s,o...)->edit_clear(s),
Expand Down Expand Up @@ -1791,31 +1797,32 @@ function activate(p::TextInterface, s::ModeState, termbuf, term::TextTerminal)
end

function activate(p::TextInterface, s::MIState, termbuf, term::TextTerminal)
@assert p == s.current_mode
activate(p, s.mode_state[s.current_mode], termbuf, term)
@assert p == mode(s)
activate(p, state(s), termbuf, term)
end
activate(m::ModalInterface, s::MIState, termbuf, term::TextTerminal) =
activate(s.current_mode, s, termbuf, term)
activate(mode(s), s, termbuf, term)

commit_changes(t::UnixTerminal, termbuf) = write(t, take!(termbuf.out_stream))
function transition(f::Function, s::MIState, mode)
if mode === :abort

function transition(f::Function, s::MIState, newmode)
if newmode === :abort
s.aborted = true
return
end
if mode === :reset
if newmode === :reset
reset_state(s)
return
end
if !haskey(s.mode_state,mode)
s.mode_state[mode] = init_state(terminal(s), mode)
if !haskey(s.mode_state, newmode)
s.mode_state[newmode] = init_state(terminal(s), newmode)
end
termbuf = TerminalBuffer(IOBuffer())
t = terminal(s)
s.mode_state[s.current_mode] = deactivate(s.current_mode, s.mode_state[s.current_mode], termbuf, t)
s.current_mode = mode
s.mode_state[mode(s)] = deactivate(mode(s), state(s), termbuf, t)
s.current_mode = newmode
f()
activate(mode, s.mode_state[mode], termbuf, t)
activate(newmode, state(s, newmode), termbuf, t)
commit_changes(t, termbuf)
end
transition(s::MIState, mode) = transition((args...)->nothing, s, mode)
Expand All @@ -1830,7 +1837,7 @@ function reset_state(s::PromptState)
end

function reset_state(s::MIState)
for (mode,state) in s.mode_state
for (mode, state) in s.mode_state
reset_state(state)
end
end
Expand All @@ -1855,7 +1862,7 @@ end
run_interface(::Prompt) = nothing

init_state(terminal, prompt::Prompt) =
PromptState(terminal, prompt, IOBuffer(), IOBuffer[], InputAreaState(1, 1),
PromptState(terminal, prompt, IOBuffer(), IOBuffer[], 1, InputAreaState(1, 1),
#=indent(spaces)=# -1)

function init_state(terminal, m::ModalInterface)
Expand All @@ -1878,7 +1885,7 @@ function run_interface(terminal, m::ModalInterface)
Expr(:body,
Expr(:return,
Expr(:call,
QuoteNode(mode(state(s, s.current_mode)).on_done),
QuoteNode(mode(state(s)).on_done),
QuoteNode(s),
QuoteNode(buf),
QuoteNode(ok)))))
Expand All @@ -1894,25 +1901,64 @@ position(s::Union{MIState,ModeState}) = position(buffer(s))

function empty_undo(s::PromptState)
empty!(s.undo_buffers)
s.undo_idx = 1
end

empty_undo(s) = nothing

function push_undo(s::PromptState)
push!(s.undo_buffers, copy(s.input_buffer))
function push_undo(s::PromptState, advance=true)
resize!(s.undo_buffers, s.undo_idx)
s.undo_buffers[end] = copy(s.input_buffer)
advance && (s.undo_idx += 1)
end

push_undo(s) = nothing

# must be called after a push_undo
function pop_undo(s::PromptState)
length(s.undo_buffers) > 0 || return false
s.input_buffer = pop!(s.undo_buffers)
pop!(s.undo_buffers)
s.undo_idx -= 1
end

function edit_undo!(s::MIState)
s.last_action ∉ (:edit_redo!, :edit_undo!) && push_undo(s, false)
if edit_undo!(state(s))
:edit_undo!
else
beep(terminal(s))
:ignore
end
end

function edit_undo!(s::PromptState)
s.undo_idx > 1 || return false
s.input_buffer = s.undo_buffers[s.undo_idx -=1]
refresh_line(s)
true
end
edit_undo!(s) = nothing

function edit_redo!(s::MIState)
if s.last_action ∈ (:edit_redo!, :edit_undo!) && edit_redo!(state(s))
:edit_redo!
else
beep(terminal(s))
:ignore
end
end

function edit_redo!(s::PromptState)
s.undo_idx < length(s.undo_buffers) || return false
s.input_buffer = s.undo_buffers[s.undo_idx += 1]
refresh_line(s)
true
end
pop_undo(s) = nothing
edit_redo!(s) = nothing

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)
keymap(ms::MIState, m::ModalInterface) = keymap(state(ms), mode(ms))
keymap_data(ms::MIState, m::ModalInterface) = keymap_data(state(ms), mode(ms))

function prompt!(term, prompt, s = init_state(term, prompt))
Base.reseteof(term)
Expand Down
Loading