Skip to content

Commit

Permalink
REPL: enable "redo" after "undo"
Browse files Browse the repository at this point in the history
  • Loading branch information
rfourquet committed Sep 6, 2017
1 parent b58d400 commit d4ab9b0
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 63 deletions.
55 changes: 48 additions & 7 deletions base/repl/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -1668,7 +1669,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 @@ -1856,7 +1858,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 Down Expand Up @@ -1895,20 +1897,59 @@ 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
Expand Down
106 changes: 50 additions & 56 deletions test/lineedit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ charpos(buf, pos=position(buf)) = Base.unsafe_ind2chr(content(buf), pos+1)-1
function transform!(f, s, i = -1) # i is char-based (not bytes) buffer position
buf = buffer(s)
i >= 0 && charseek(buf, i)
f(s)
content(buf), charpos(buf), charpos(buf, getmark(buf))
action = f(s)
if s isa LineEdit.MIState && action isa Symbol
s.last_action = action # simulate what happens in LineEdit.prompt!
end
content(s), charpos(buf), charpos(buf, getmark(buf))
end


Expand Down Expand Up @@ -604,10 +607,11 @@ end
@test s.kill_ring[end] == "a ≡ not"
charseek(buf, 0)
@test transform!(LineEdit.edit_yank, s) == ("a ≡ notçhing", 7, 0)
s.last_action = :unknown
# next action will fail, as yank-pop doesn't know a yank was just issued
@test transform!(LineEdit.edit_yank_pop, s) == ("a ≡ notçhing", 7, 0)
s.last_action = :edit_yank
# not this should work:
# now this should work:
@test transform!(LineEdit.edit_yank_pop, s) == ("ça ≡ nothingçhing", 12, 0)
@test s.kill_idx == 1
LineEdit.edit_kill_line(s)
Expand All @@ -617,88 +621,78 @@ end

@testset "undo" begin
s = new_state()
edit!(f) = transform!(f, s)[1]
edit_undo! = LineEdit.edit_undo!
edit_redo! = LineEdit.edit_redo!

edit_insert(s, "one two three")

LineEdit.edit_delete_prev_word(s)
@test content(s) == "one two "
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_delete_prev_word) == "one two "
@test edit!(edit_undo!) == "one two three"
@test edit!(edit_redo!) == "one two "
@test edit!(edit_undo!) == "one two three"

edit_insert(s, " four")
edit_insert(s, " five")
@test content(s) == "one two three four five"
LineEdit.pop_undo(s)
@test content(s) == "one two three four"
LineEdit.pop_undo(s)
@test content(s) == "one two three"

LineEdit.edit_clear(s)
@test content(s) == ""
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(s->edit_insert(s, " five")) == "one two three four five"
@test edit!(edit_undo!) == "one two three four"
@test edit!(edit_undo!) == "one two three"
@test edit!(edit_redo!) == "one two three four"
@test edit!(edit_redo!) == "one two three four five"
@test edit!(edit_undo!) == "one two three four"
@test edit!(edit_undo!) == "one two three"

@test edit!(LineEdit.edit_clear) == ""
@test edit!(edit_undo!) == "one two three"

LineEdit.edit_move_left(s)
LineEdit.edit_move_left(s)
LineEdit.edit_transpose_chars(s)
@test content(s) == "one two there"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_transpose_chars) == "one two there"
@test edit!(edit_undo!) == "one two three"

LineEdit.move_line_start(s)
LineEdit.edit_kill_line(s)
@test content(s) == ""
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_kill_line) == ""
@test edit!(edit_undo!) == "one two three"

LineEdit.move_line_start(s)
LineEdit.edit_kill_line(s)
LineEdit.edit_yank(s)
LineEdit.edit_yank(s)
@test content(s) == "one two threeone two three"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
LineEdit.pop_undo(s)
@test content(s) == ""
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_yank) == "one two threeone two three"
@test edit!(edit_undo!) == "one two three"
@test edit!(edit_undo!) == ""
@test edit!(edit_undo!) == "one two three"

LineEdit.move_line_end(s)
LineEdit.edit_backspace(s)
LineEdit.edit_backspace(s)
LineEdit.edit_backspace(s)
@test content(s) == "one two th"
LineEdit.pop_undo(s)
@test content(s) == "one two thr"
LineEdit.pop_undo(s)
@test content(s) == "one two thre"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_backspace) == "one two th"
@test edit!(edit_undo!) == "one two thr"
@test edit!(edit_undo!) == "one two thre"
@test edit!(edit_undo!) == "one two three"

LineEdit.push_undo(s) # TODO: incorporate push_undo into edit_splice! ?
LineEdit.edit_splice!(s, 4 => 7, "stott")
@test content(s) == "one stott three"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
s.last_action = :not_undo
@test edit!(edit_undo!) == "one two three"

LineEdit.edit_move_left(s)
LineEdit.edit_move_left(s)
LineEdit.edit_move_left(s)
LineEdit.edit_delete(s)
@test content(s) == "one two thee"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_delete) == "one two thee"
@test edit!(edit_undo!) == "one two three"

LineEdit.edit_move_word_left(s)
LineEdit.edit_werase(s)
LineEdit.edit_delete_next_word(s)
@test content(s) == "one "
LineEdit.pop_undo(s)
@test content(s) == "one three"
LineEdit.pop_undo(s)
@test content(s) == "one two three"
@test edit!(LineEdit.edit_delete_next_word) == "one "
@test edit!(edit_undo!) == "one three"
@test edit!(edit_undo!) == "one two three"
@test edit!(edit_redo!) == "one three"
@test edit!(edit_redo!) == "one "
@test edit!(edit_redo!) == "one " # nothing more to redo (this "beeps")
@test edit!(edit_undo!) == "one three"
@test edit!(edit_undo!) == "one two three"

# pop initial insert of "one two three"
LineEdit.pop_undo(s)
@test content(s) == ""
@test edit!(edit_undo!) == ""
@test edit!(edit_undo!) == "" # nothing more to undo (this "beeps")
end

0 comments on commit d4ab9b0

Please sign in to comment.