Skip to content

Commit ca72b97

Browse files
committed
Improve abstraction
1 parent 9308520 commit ca72b97

File tree

7 files changed

+71
-38
lines changed

7 files changed

+71
-38
lines changed

stdlib/REPL/src/TerminalMenus/AbstractMenu.jl

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Details can be found in
2626
2727
# Subtypes
2828
29-
All subtypes must contain the fields `pagesize::Int` and
29+
All subtypes must be mutable, and must contain the fields `pagesize::Int` and
3030
`pageoffset::Int`. They must also implement the following functions.
3131
3232
## Necessary Functions
@@ -36,7 +36,9 @@ These functions must be implemented for all subtypes of AbstractMenu.
3636
- `pick(m::AbstractMenu, cursor::Int)`
3737
- `cancel(m::AbstractMenu)`
3838
- `options(m::AbstractMenu)`
39-
- `writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, showcursor::Bool)`
39+
- `writeline(buf::IOBuffer, m::AbstractMenu, idx::Int, cursor)`
40+
41+
If `m` does not have a field called `selected`, then you must also implement `selected(m)`.
4042
4143
## Optional Functions
4244
@@ -45,7 +47,8 @@ subtypes.
4547
4648
- `header(m::AbstractMenu)`
4749
- `keypress(m::AbstractMenu, i::UInt32)`
48-
- `noptions(m::AbstractMenu)`
50+
- `numoptions(m::AbstractMenu)`
51+
- `selected(m::AbstractMenu)`
4952
5053
"""
5154
abstract type AbstractMenu end
@@ -78,28 +81,35 @@ cancel(m::AbstractMenu) = error("unimplemented")
7881
7982
Return a list of strings to be displayed as options in the current page.
8083
81-
Alternatively, implement `noptions`, in which case `options` is not needed.
84+
Alternatively, implement `numoptions`, in which case `options` is not needed.
8285
"""
8386
options(m::AbstractMenu) = error("unimplemented")
8487

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

105115

@@ -111,6 +121,7 @@ end
111121
header(m::AbstractMenu)
112122
113123
Displays the header above the menu when it is rendered to the screen.
124+
Defaults to "".
114125
"""
115126
header(m::AbstractMenu) = ""
116127

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

125137
"""
126-
noptions(m::AbstractMenu)
138+
numoptions(m::AbstractMenu)
127139
128140
Return the number of options in menu `m`. Defaults to `length(options(m))`.
129141
"""
130-
noptions(m::AbstractMenu) = length(options(m))
142+
numoptions(m::AbstractMenu) = length(options(m))
131143

144+
"""
145+
selected(m::AbstractMenu)
146+
147+
Return information about the user-selected option. Defaults to `m.selected`.
148+
"""
149+
selected(m::AbstractMenu) = m.selected
132150

133151
"""
134152
request(m::AbstractMenu; cursor=1)
135153
136-
Display the menu and enter interactive mode. Returns `m.selected` which
137-
varies based on menu type.
154+
Display the menu and enter interactive mode. `cursor` indicates the initial item
155+
for the cursor.
156+
157+
Returns `selected(m)` which varies based on menu type.
138158
"""
139159
request(m::AbstractMenu; kwargs...) = request(terminal, m; kwargs...)
140160

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

150-
lastoption = noptions(m)
170+
lastoption = numoptions(m)
151171
try
152172
while true
153173
c = readkey(term.in_stream)
@@ -223,7 +243,7 @@ function request(term::REPL.Terminals.TTYTerminal, m::AbstractMenu; cursor::Int=
223243
end
224244
println(term.out_stream)
225245

226-
return m.selected
246+
return selected(m)
227247
end
228248

229249

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

257277
buf = IOBuffer()
278+
indicators = Indicators()
258279

259280
lines = m.pagesize-1
260281

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

275296
if i == firstline && m.pageoffset > 0
276297
print(buf, CONFIG[:up_arrow])
277-
elseif i == lastline && i != noptions(m)
298+
elseif i == lastline && i != numoptions(m)
278299
print(buf, CONFIG[:down_arrow])
279300
else
280301
print(buf, " ")
281302
end
282303

283-
writeline(buf, m, i, i == cursor ? CONFIG[:cursor] : nothing)
304+
writeline(buf, m, i, i == cursor, indicators)
284305

285306
i != lastline && print(buf, "\r\n")
286307
end

stdlib/REPL/src/TerminalMenus/MultiSelectMenu.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ function pick(menu::MultiSelectMenu, cursor::Int)
8787
return false #break out of the menu
8888
end
8989

90-
function writeline(buf::IOBuffer, menu::MultiSelectMenu, idx::Int, cursor::Union{Char,Nothing})
90+
function writeline(buf::IOBuffer, menu::MultiSelectMenu, idx::Int, cursor::Bool, indicators)
9191
# print a ">" on the selected entry
92-
isa(cursor, Char) ? print(buf, cursor ," ") : print(buf, " ")
92+
cursor ? print(buf, indicators.cursor ," ") : print(buf, " ")
9393
if idx in menu.selected
94-
print(buf, CONFIG[:checked], " ")
94+
print(buf, indicators.checked, " ")
9595
else
96-
print(buf, CONFIG[:unchecked], " ")
96+
print(buf, indicators.unchecked, " ")
9797
end
9898

9999
print(buf, replace(menu.options[idx], "\n" => "\\n"))

stdlib/REPL/src/TerminalMenus/RadioMenu.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ function pick(menu::RadioMenu, cursor::Int)
7171
return true #break out of the menu
7272
end
7373

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

7878
print(buf, replace(menu.options[idx], "\n" => "\\n"))
7979
end

stdlib/REPL/src/TerminalMenus/config.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
"""global menu configuration parameters"""
44
const CONFIG = Dict{Symbol,Union{Char,String,Bool}}()
55

6+
struct Indicators
7+
cursor::Char
8+
checked::String
9+
unchecked::String
10+
end
11+
Indicators() = Indicators(CONFIG)
12+
Indicators(settings) = Indicators(settings[:cursor], settings[:checked], settings[:unchecked])
13+
614
"""
715
config( <see arguments> )
816

stdlib/REPL/test/TerminalMenus/multiselect_menu.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ CONFIG = TerminalMenus.CONFIG
2323

2424
multi_menu = MultiSelectMenu(string.(1:10))
2525
buf = IOBuffer()
26-
TerminalMenus.writeline(buf, multi_menu, 1, true)
27-
@test String(take!(buf)) == string(CONFIG[:cursor], " ", CONFIG[:unchecked], " 1")
26+
TerminalMenus.writeline(buf, multi_menu, 1, true, TerminalMenus.Indicators('@',"c","u"))
27+
@test String(take!(buf)) == "@ u 1"
2828
TerminalMenus.config(cursor='+')
29-
TerminalMenus.writeline(buf, multi_menu, 1, true)
30-
@test String(take!(buf)) == string("+ ", CONFIG[:unchecked], " 1")
29+
TerminalMenus.printmenu(buf, multi_menu, 1; init=true)
30+
@test startswith(String(take!(buf)), string("\e[2K + ", CONFIG[:unchecked], " 1"))
3131
TerminalMenus.config(charset=:unicode)
32-
TerminalMenus.writeline(buf, multi_menu, 1, true)
33-
@test String(take!(buf)) == string(CONFIG[:cursor], " ", CONFIG[:unchecked], " 1")
32+
push!(multi_menu.selected, 1)
33+
TerminalMenus.printmenu(buf, multi_menu, 2; init=true)
34+
@test startswith(String(take!(buf)), string("\e[2K ", CONFIG[:checked], " 1\r\n\e[2K ", CONFIG[:cursor], " ", CONFIG[:unchecked], " 2"))
3435

3536
# Test SDTIN
3637
multi_menu = MultiSelectMenu(string.(1:10))
38+
CONFIG[:suppress_output] = true
3739
@test simulate_input(Set([1,2]), multi_menu, :enter, :down, :enter, 'd')
40+
CONFIG[:suppress_output] = false

stdlib/REPL/test/TerminalMenus/radio_menu.jl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@ CONFIG = TerminalMenus.CONFIG
2626

2727
radio_menu = RadioMenu(string.(1:10))
2828
buf = IOBuffer()
29-
TerminalMenus.writeline(buf, radio_menu, 1, true)
30-
@test String(take!(buf)) == string(CONFIG[:cursor], " 1")
29+
TerminalMenus.writeline(buf, radio_menu, 1, true, TerminalMenus.Indicators('@',"",""))
30+
@test String(take!(buf)) == "@ 1"
3131
TerminalMenus.config(cursor='+')
32-
TerminalMenus.writeline(buf, radio_menu, 1, true)
33-
@test String(take!(buf)) == "+ 1"
32+
TerminalMenus.printmenu(buf, radio_menu, 1; init=true)
33+
@test startswith(String(take!(buf)), "\e[2K + 1")
3434
TerminalMenus.config(charset=:unicode)
35-
TerminalMenus.writeline(buf, radio_menu, 1, true)
36-
@test String(take!(buf)) == string(CONFIG[:cursor], " 1")
35+
TerminalMenus.printmenu(buf, radio_menu, 2; init=true)
36+
@test startswith(String(take!(buf)), string("\e[2K 1\r\n\e[2K ", CONFIG[:cursor], " 2"))
3737

3838
# Test using stdin
3939
radio_menu = RadioMenu(string.(1:10))
40+
CONFIG[:suppress_output] = true
4041
@test simulate_input(3, radio_menu, :down, :down, :enter)
42+
CONFIG[:suppress_output] = false

stdlib/REPL/test/TerminalMenus/runtests.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import REPL
44
using REPL.TerminalMenus
55
using Test
66

7-
TerminalMenus.config(suppress_output=true)
8-
97
function simulate_input(expected, menu::TerminalMenus.AbstractMenu, keys...)
108
keydict = Dict(:up => "\e[A",
119
:down => "\e[B",
@@ -24,6 +22,7 @@ end
2422

2523
include("radio_menu.jl")
2624
include("multiselect_menu.jl")
25+
println("done")
2726

2827
# Other test
2928

0 commit comments

Comments
 (0)