Skip to content

Commit

Permalink
Make vim bindings opt-in
Browse files Browse the repository at this point in the history
and make `simulate_input` more robust against menus not supporting vim
bindings.
  • Loading branch information
jonas-schulze committed Aug 9, 2021
1 parent ae6f06e commit ab74b57
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 6 deletions.
48 changes: 45 additions & 3 deletions stdlib/REPL/src/TerminalMenus/AbstractMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ subtypes.
- `keypress(m::AbstractMenu, i::UInt32)`
- `numoptions(m::AbstractMenu)`
- `selected(m::AbstractMenu)`
- `accepts_vim_bindings(m::AbstractMenu)`
- `accepts_jk(m::AbstractMenu)`
- `accepts_space(m::AbstractMenu)`
!!! compat "Julia 1.7"
The functions `accepts_vim_bindings`, `accepts_jk`, `accepts_space` require
Julia 1.7 or later.
"""
abstract type AbstractMenu end

Expand Down Expand Up @@ -203,9 +209,9 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio
lastoption = numoptions(m)
c = readkey(term.in_stream)

if c == Int(ARROW_UP) || c == Int('k')
if c == Int(ARROW_UP) || (accepts_jk(m) && c == Int('k'))
cursor[] = move_up!(m, cursor[], lastoption)
elseif c == Int(ARROW_DOWN) || c == Int('j')
elseif c == Int(ARROW_DOWN) || (accepts_jk(m) && c == Int('j'))
cursor[] = move_down!(m, cursor[], lastoption)
elseif c == Int(PAGE_UP)
cursor[] = page_up!(m, cursor[], lastoption)
Expand All @@ -217,7 +223,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Unio
elseif c == Int(END_KEY)
cursor[] = lastoption
m.pageoffset = lastoption - m.pagesize
elseif c == 13 || c == Int(' ') # <enter> or <space>
elseif c == 13 || (accepts_space(m) && c == Int(' ')) # <enter> or <space>
# will break if pick returns true
pick(m, cursor[]) && break
elseif c == UInt32('q')
Expand Down Expand Up @@ -259,6 +265,42 @@ function request(term::REPL.Terminals.TTYTerminal, msg::AbstractString, m::Abstr
request(term, m; kwargs...)
end

"""
accepts_vim_bindings(m::AbstractMenu)
Returns whether `m` accepts `j` and `k` to move the cursor and Space to pick
the selected option.
Defaults to `false`.
See also: [`accepts_jk`](@ref), [`accepts_space`](@ref).
!!! compat "Julia 1.7"
This function requires Julia 1.7 or later.
"""
accepts_vim_bindings(::AbstractMenu) = false

"""
accepts_jk(m::AbstractMenu)
Returns whether `m` accepts `j`/`k` to move the cursor down/up.
Defaults to `false`.
See also: [`accepts_vim_bindings`](@ref).
!!! compat "Julia 1.7"
This function requires Julia 1.7 or later.
"""
accepts_jk(m::AbstractMenu) = accepts_vim_bindings(m)

"""
accepts_space(m::AbstractMenu)
Returns whether `m` accepts Space to pick the currently selected option.
Defaults to `false`.
See also: [`accepts_vim_bindings`](@ref).
!!! compat "Julia 1.7"
This function requires Julia 1.7 or later.
"""
accepts_space(m::AbstractMenu) = accepts_vim_bindings(m)

function move_up!(m::AbstractMenu, cursor::Int, lastoption::Int=numoptions(m))
if cursor > 1
Expand Down
1 change: 1 addition & 0 deletions stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function keypress(menu::MultiSelectMenu, key::UInt32)
false # don't break
end

accepts_vim_bindings(::MultiSelectMenu) = true

## Legacy interface
function TerminalMenus.writeLine(buf::IOBuffer, menu::MultiSelectMenu{<:Dict}, idx::Int, cursor::Bool)
Expand Down
2 changes: 2 additions & 0 deletions stdlib/REPL/src/TerminalMenus/Pager.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ cancel(::Pager) = nothing

pick(::Pager, ::Int) = true

accepts_vim_bindings(::Pager) = true

function writeline(buf::IOBuffer, pager::Pager{Config}, idx::Int, iscursor::Bool)
print(buf, pager.lines[idx])
end
Expand Down
4 changes: 4 additions & 0 deletions stdlib/REPL/src/TerminalMenus/RadioMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ function keypress(m::RadioMenu, i::UInt32)
return true
end

accepts_jk(m::RadioMenu) = !('j' in m.keybindings || 'k' in m.keybindings)
accepts_space(m::RadioMenu) = !(' ' in m.keybindings)
accepts_vim_bindings(m::RadioMenu) = accepts_jk(m) && accepts_space(m)

# Legacy interface
function writeLine(buf::IOBuffer, menu::RadioMenu{<:Dict}, idx::Int, cursor::Bool)
# print a ">" on the selected entry
Expand Down
2 changes: 2 additions & 0 deletions stdlib/REPL/test/TerminalMenus/multiselect_with_skip_menu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ function TerminalMenus.keypress(menu::MultiSelectWithSkipMenu, key::UInt32)
false # don't break
end

TerminalMenus.accepts_vim_bindings(::MultiSelectWithSkipMenu) = true

function move_cursor!(menu, direction, selected)
c = menu.cursor[]
while true
Expand Down
6 changes: 6 additions & 0 deletions stdlib/REPL/test/TerminalMenus/radio_menu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ radio_menu = RadioMenu(string.(1:3), pagesize=1, charset=:ascii)
@test simulate_input(3, radio_menu, :down, :down, :down, :down, :enter)
radio_menu = RadioMenu(["apple", "banana", "cherry"]; keybindings=collect('a':'c'), charset=:ascii)
@test simulate_input(2, radio_menu, 'b')
@test TerminalMenus.accepts_vim_bindings(radio_menu)
radio_menu = RadioMenu(string.(1:3); keybindings=['j', '2', '3'], charset=:ascii)
@test TerminalMenus.accepts_space(radio_menu)
@test !TerminalMenus.accepts_jk(radio_menu)
@test !TerminalMenus.accepts_vim_bindings(radio_menu)
@test simulate_input(1, radio_menu, 'k', 'k', :enter)
8 changes: 5 additions & 3 deletions stdlib/REPL/test/TerminalMenus/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ function simulate_input(expected, menu::TerminalMenus.AbstractMenu, keys...;
:down => "j",
:enter => " ")
errs = []
got = _simulate_input(keydict, deepcopy(menu), keys...; kwargs...)
if TerminalMenus.accepts_vim_bindings(menu)
got = _simulate_input(vimdict, deepcopy(menu), keys...; kwargs...)
got == expected || push!(errs, :vim => got)
end
got = _simulate_input(keydict, menu, keys...; kwargs...)
got == expected || push!(errs, :arrows => got)
got = _simulate_input(vimdict, menu, keys...; kwargs...)
got == expected || push!(errs, :vim => got)
isempty(errs) || return errs
end

Expand Down

0 comments on commit ab74b57

Please sign in to comment.