Skip to content

Commit

Permalink
Improve abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed May 17, 2020
1 parent 9308520 commit ca72b97
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 38 deletions.
57 changes: 39 additions & 18 deletions stdlib/REPL/src/TerminalMenus/AbstractMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Details can be found in
# Subtypes
All subtypes must contain the fields `pagesize::Int` and
All subtypes must be mutable, and must contain the fields `pagesize::Int` and
`pageoffset::Int`. They must also implement the following functions.
## Necessary Functions
Expand All @@ -36,7 +36,9 @@ These functions must be implemented for all subtypes of AbstractMenu.
- `pick(m::AbstractMenu, cursor::Int)`
- `cancel(m::AbstractMenu)`
- `options(m::AbstractMenu)`
- `writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, showcursor::Bool)`
- `writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor)`
If `m` does not have a field called `selected`, then you must also implement `selected(m)`.
## Optional Functions
Expand All @@ -45,7 +47,8 @@ subtypes.
- `header(m::AbstractMenu)`
- `keypress(m::AbstractMenu, i::UInt32)`
- `noptions(m::AbstractMenu)`
- `numoptions(m::AbstractMenu)`
- `selected(m::AbstractMenu)`
"""
abstract type AbstractMenu end
Expand Down Expand Up @@ -78,28 +81,35 @@ cancel(m::AbstractMenu) = error("unimplemented")
Return a list of strings to be displayed as options in the current page.
Alternatively, implement `noptions`, in which case `options` is not needed.
Alternatively, implement `numoptions`, in which case `options` is not needed.
"""
options(m::AbstractMenu) = error("unimplemented")

"""
writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor::Union{Char,Nothing})
writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor::Bool, indicators::Indicators)
Write the option at index `idx` to the buffer. If `isa(cursor, Char)`, it should be printed.
Write the option at index `idx` to the buffer. If `indicators !== nothing`, the current line
corresponds to the cursor position. The method is responsible for displaying visual indicator(s)
about the state of this menu item; the configured characters are returned in `indicators`
in fields with the following names:
`cursor::Char`: the character used to indicate the cursor position
`checked::String`: a string used to indicate this option has been marked
`unchecked::String`: a string used to indicate this option has not been marked
The latter two are relevant only for menus that support multiple selection.
!!! compat "Julia 1.6"
`writeline` requires Julia 1.6 or higher.
On older versions of Julia, this was
`writeLine(buf::IOBuffer, m::AbstractMenu, idx, cursor::Bool)`
and if `cursor` is `true`, the cursor obtained from `TerminalMenus.CONFIG[:cursor]`
should be printed.
and the indicators can be obtained from `TerminalMenus.CONFIG`, a Dict indexed by `Symbol`
keys with the same names as the fields of `indicators`.
This older function is supported on all Julia 1.x versions but will be dropped in Julia 2.0.
"""
function writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor::Union{Char,Nothing})
function writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor::Bool, indicators)
# error("unimplemented") # TODO: use this in Julia 2.0
writeLine(buf, m, idx, isa(cursor, Char))
writeLine(buf, m, idx, cursor)
end


Expand All @@ -111,6 +121,7 @@ end
header(m::AbstractMenu)
Displays the header above the menu when it is rendered to the screen.
Defaults to "".
"""
header(m::AbstractMenu) = ""

Expand All @@ -119,22 +130,31 @@ header(m::AbstractMenu) = ""
Send any non-standard keypress event to this function.
If `true` is returned, `request()` will exit.
Defaults to `false`.
"""
keypress(m::AbstractMenu, i::UInt32) = false

"""
noptions(m::AbstractMenu)
numoptions(m::AbstractMenu)
Return the number of options in menu `m`. Defaults to `length(options(m))`.
"""
noptions(m::AbstractMenu) = length(options(m))
numoptions(m::AbstractMenu) = length(options(m))

"""
selected(m::AbstractMenu)
Return information about the user-selected option. Defaults to `m.selected`.
"""
selected(m::AbstractMenu) = m.selected

"""
request(m::AbstractMenu; cursor=1)
Display the menu and enter interactive mode. Returns `m.selected` which
varies based on menu type.
Display the menu and enter interactive mode. `cursor` indicates the initial item
for the cursor.
Returns `selected(m)` which varies based on menu type.
"""
request(m::AbstractMenu; kwargs...) = request(terminal, m; kwargs...)

Expand All @@ -147,7 +167,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Int=
raw_mode_enabled = REPL.Terminals.raw!(term, true)
raw_mode_enabled && print(term.out_stream, "\x1b[?25l") # hide the cursor

lastoption = noptions(m)
lastoption = numoptions(m)
try
while true
c = readkey(term.in_stream)
Expand Down Expand Up @@ -223,7 +243,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Int=
end
println(term.out_stream)

return m.selected
return selected(m)
end


Expand Down Expand Up @@ -255,6 +275,7 @@ function printmenu(out, m::AbstractMenu, cursor::Int; init::Bool=false)
CONFIG[:suppress_output] && return

buf = IOBuffer()
indicators = Indicators()

lines = m.pagesize-1

Expand All @@ -274,13 +295,13 @@ function printmenu(out, m::AbstractMenu, cursor::Int; init::Bool=false)

if i == firstline && m.pageoffset > 0
print(buf, CONFIG[:up_arrow])
elseif i == lastline && i != noptions(m)
elseif i == lastline && i != numoptions(m)
print(buf, CONFIG[:down_arrow])
else
print(buf, " ")
end

writeline(buf, m, i, i == cursor ? CONFIG[:cursor] : nothing)
writeline(buf, m, i, i == cursor, indicators)

i != lastline && print(buf, "\r\n")
end
Expand Down
8 changes: 4 additions & 4 deletions stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ function pick(menu::MultiSelectMenu, cursor::Int)
return false #break out of the menu
end

function writeline(buf::IOBuffer, menu::MultiSelectMenu, idx::Int, cursor::Union{Char,Nothing})
function writeline(buf::IOBuffer, menu::MultiSelectMenu, idx::Int, cursor::Bool, indicators)
# print a ">" on the selected entry
isa(cursor, Char) ? print(buf, cursor ," ") : print(buf, " ")
cursor ? print(buf, indicators.cursor ," ") : print(buf, " ")
if idx in menu.selected
print(buf, CONFIG[:checked], " ")
print(buf, indicators.checked, " ")
else
print(buf, CONFIG[:unchecked], " ")
print(buf, indicators.unchecked, " ")
end

print(buf, replace(menu.options[idx], "\n" => "\\n"))
Expand Down
4 changes: 2 additions & 2 deletions stdlib/REPL/src/TerminalMenus/RadioMenu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ function pick(menu::RadioMenu, cursor::Int)
return true #break out of the menu
end

function writeline(buf::IOBuffer, menu::RadioMenu, idx::Int, cursor::Union{Char,Nothing})
function writeline(buf::IOBuffer, menu::RadioMenu, idx::Int, cursor::Bool, indicators)
# print a ">" on the selected entry
isa(cursor, Char) ? print(buf, cursor ," ") : print(buf, " ")
cursor ? print(buf, indicators.cursor ," ") : print(buf, " ")

print(buf, replace(menu.options[idx], "\n" => "\\n"))
end
8 changes: 8 additions & 0 deletions stdlib/REPL/src/TerminalMenus/config.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
"""global menu configuration parameters"""
const CONFIG = Dict{Symbol,Union{Char,String,Bool}}()

struct Indicators
cursor::Char
checked::String
unchecked::String
end
Indicators() = Indicators(CONFIG)
Indicators(settings) = Indicators(settings[:cursor], settings[:checked], settings[:unchecked])

"""
config( <see arguments> )
Expand Down
15 changes: 9 additions & 6 deletions stdlib/REPL/test/TerminalMenus/multiselect_menu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ CONFIG = TerminalMenus.CONFIG

multi_menu = MultiSelectMenu(string.(1:10))
buf = IOBuffer()
TerminalMenus.writeline(buf, multi_menu, 1, true)
@test String(take!(buf)) == string(CONFIG[:cursor], " ", CONFIG[:unchecked], " 1")
TerminalMenus.writeline(buf, multi_menu, 1, true, TerminalMenus.Indicators('@',"c","u"))
@test String(take!(buf)) == "@ u 1"
TerminalMenus.config(cursor='+')
TerminalMenus.writeline(buf, multi_menu, 1, true)
@test String(take!(buf)) == string("+ ", CONFIG[:unchecked], " 1")
TerminalMenus.printmenu(buf, multi_menu, 1; init=true)
@test startswith(String(take!(buf)), string("\e[2K + ", CONFIG[:unchecked], " 1"))
TerminalMenus.config(charset=:unicode)
TerminalMenus.writeline(buf, multi_menu, 1, true)
@test String(take!(buf)) == string(CONFIG[:cursor], " ", CONFIG[:unchecked], " 1")
push!(multi_menu.selected, 1)
TerminalMenus.printmenu(buf, multi_menu, 2; init=true)
@test startswith(String(take!(buf)), string("\e[2K ", CONFIG[:checked], " 1\r\n\e[2K ", CONFIG[:cursor], " ", CONFIG[:unchecked], " 2"))

# Test SDTIN
multi_menu = MultiSelectMenu(string.(1:10))
CONFIG[:suppress_output] = true
@test simulate_input(Set([1,2]), multi_menu, :enter, :down, :enter, 'd')
CONFIG[:suppress_output] = false
14 changes: 8 additions & 6 deletions stdlib/REPL/test/TerminalMenus/radio_menu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ CONFIG = TerminalMenus.CONFIG

radio_menu = RadioMenu(string.(1:10))
buf = IOBuffer()
TerminalMenus.writeline(buf, radio_menu, 1, true)
@test String(take!(buf)) == string(CONFIG[:cursor], " 1")
TerminalMenus.writeline(buf, radio_menu, 1, true, TerminalMenus.Indicators('@',"",""))
@test String(take!(buf)) == "@ 1"
TerminalMenus.config(cursor='+')
TerminalMenus.writeline(buf, radio_menu, 1, true)
@test String(take!(buf)) == "+ 1"
TerminalMenus.printmenu(buf, radio_menu, 1; init=true)
@test startswith(String(take!(buf)), "\e[2K + 1")
TerminalMenus.config(charset=:unicode)
TerminalMenus.writeline(buf, radio_menu, 1, true)
@test String(take!(buf)) == string(CONFIG[:cursor], " 1")
TerminalMenus.printmenu(buf, radio_menu, 2; init=true)
@test startswith(String(take!(buf)), string("\e[2K 1\r\n\e[2K ", CONFIG[:cursor], " 2"))

# Test using stdin
radio_menu = RadioMenu(string.(1:10))
CONFIG[:suppress_output] = true
@test simulate_input(3, radio_menu, :down, :down, :enter)
CONFIG[:suppress_output] = false
3 changes: 1 addition & 2 deletions stdlib/REPL/test/TerminalMenus/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import REPL
using REPL.TerminalMenus
using Test

TerminalMenus.config(suppress_output=true)

function simulate_input(expected, menu::TerminalMenus.AbstractMenu, keys...)
keydict = Dict(:up => "\e[A",
:down => "\e[B",
Expand All @@ -24,6 +22,7 @@ end

include("radio_menu.jl")
include("multiselect_menu.jl")
println("done")

# Other test

Expand Down

0 comments on commit ca72b97

Please sign in to comment.