Skip to content

Commit 36270e9

Browse files
authored
Improve the API of REPL.TerminalMenus (#35915)
This overhaul of the `AbstractMenu` extension interface of `REPL.TerminalMenus` addresses several concerns: - Printing the "paging" navigation indicators was handled by TerminalMenus, but the subtype methods were expected to print the cursor indicator and, for multiple-selection menus, the selection status indicators in a manner that preserved alignment. There was no obvious reason for the inconsistency. - Printing these indicators relied on accessing a mutable private global variable in TerminalMenus. It was therefore not possible to use different settings simultaneously for two different menus (e.g. a main menu and its submenus). - The API required that subtype methods supply a list of strings for each menu-option when really it only needed to know how many options were available. This was particularly problematic for large, dynamic menus whose options might change and for which lists of options would have to be reallocated regularly but were then thrown away after checking their length. This deprecates the old interface in a backward-compatible manner, so is non-breaking.
1 parent 9738e4b commit 36270e9

File tree

15 files changed

+762
-229
lines changed

15 files changed

+762
-229
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ Standard library changes
6060

6161
#### REPL
6262

63+
* The `AbstractMenu` extension interface of `REPL.TerminalMenus` has been extensively
64+
overhauled. The new interface does not rely on global configuration variables, is more
65+
consistent in delegating printing of the navigation/selection markers, and provides
66+
improved support for dynamic menus. These changes are compatible with the previous
67+
(deprecated) interface, so are non-breaking.
6368

6469
#### SparseArrays
6570

stdlib/REPL/docs/src/index.md

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -544,24 +544,12 @@ You like the following fruits:
544544

545545
### Customization / Configuration
546546

547-
All interface customization is done through the keyword only
548-
`TerminalMenus.config()` function.
547+
#### ConfiguredMenu subtypes
549548

550-
#### Arguments
549+
Starting with Julia 1.6, the recommended way to configure menus is via the constructor.
550+
For instance, the default multiple-selection menu
551551

552-
- `charset::Symbol=:na`: ui characters to use (`:ascii` or `:unicode`); overridden by other arguments
553-
- `cursor::Char='>'|'→'`: character to use for cursor
554-
- `up_arrow::Char='^'|'↑'`: character to use for up arrow
555-
- `down_arrow::Char='v'|'↓'`: character to use for down arrow
556-
- `checked::String="[X]"|"✓"`: string to use for checked
557-
- `unchecked::String="[ ]"|"⬚")`: string to use for unchecked
558-
- `scroll::Symbol=:na`: If `:wrap` then wrap the cursor around top and bottom, if :`nowrap` do not wrap cursor
559-
- `supress_output::Bool=false`: For testing. If true, menu will not be printed to console.
560-
- `ctrl_c_interrupt::Bool=true`: If `false`, return empty on ^C, if `true` throw InterruptException() on ^C
561-
562-
#### Examples
563-
564-
```julia
552+
```
565553
julia> menu = MultiSelectMenu(options, pagesize=5);
566554
567555
julia> request(menu) # ASCII is used by default
@@ -571,9 +559,12 @@ julia> request(menu) # ASCII is used by default
571559
[ ] grape
572560
> [X] strawberry
573561
v [ ] blueberry
574-
Set([4, 2])
562+
```
575563

576-
julia> TerminalMenus.config(charset=:unicode)
564+
can instead be rendered with Unicode selection and navigation characters with
565+
566+
```julia
567+
julia> menu = MultiSelectMenu(options, pagesize=5, charset=:unicode);
577568

578569
julia> request(menu)
579570
[press: d=done, a=all, n=none]
@@ -582,23 +573,96 @@ julia> request(menu)
582573
⬚ grape
583574
✓ strawberry
584575
⬚ blueberry
585-
Set([4, 2])
576+
```
586577

587-
julia> TerminalMenus.config(checked="YEP!", unchecked="NOPE", cursor='')
578+
More fine-grained configuration is also possible:
588579

580+
```julia
581+
julia> menu = MultiSelectMenu(options, pagesize=5, charset=:unicode, checked="YEP!", unchecked="NOPE", cursor='');
582+
583+
julia> request(menu)
589584
julia> request(menu)
590585
[press: d=done, a=all, n=none]
591586
NOPE apple
592587
YEP! orange
593588
NOPE grape
594589
⧐ YEP! strawberry
595590
NOPE blueberry
596-
Set([4, 2])
597-
598591
```
599592

593+
Aside from the overall `charset` option, for `RadioMenu` the configurable options are:
594+
595+
- `cursor::Char='>'|'→'`: character to use for cursor
596+
- `up_arrow::Char='^'|'↑'`: character to use for up arrow
597+
- `down_arrow::Char='v'|'↓'`: character to use for down arrow
598+
- `scroll_wrap::Bool=false`: optionally wrap-around at the beginning/end of a menu
599+
- `ctrl_c_interrupt::Bool=true`: If `false`, return empty on ^C, if `true` throw InterruptException() on ^C
600+
601+
`MultiSelectMenu` adds:
602+
603+
- `checked::String="[X]"|"✓"`: string to use for checked
604+
- `unchecked::String="[ ]"|"⬚")`: string to use for unchecked
605+
606+
You can create new menu types of your own.
607+
Types that are derived from `TerminalMenus.ConfiguredMenu` configure the menu options at construction time.
608+
609+
#### Legacy interface
610+
611+
Prior to Julia 1.6, and still supported throughout Julia 1.x, one can also configure menus by calling
612+
`TerminalMenus.config()`.
613+
600614
## References
601615

616+
### REPL
617+
602618
```@docs
603619
Base.atreplinit
604620
```
621+
622+
### TerminalMenus
623+
624+
#### Configuration
625+
626+
```@docs
627+
REPL.TerminalMenus.Config
628+
REPL.TerminalMenus.MultiSelectConfig
629+
REPL.TerminalMenus.config
630+
```
631+
632+
#### User interaction
633+
634+
```@docs
635+
REPL.TerminalMenus.request
636+
```
637+
638+
#### AbstractMenu extension interface
639+
640+
Any subtype of `AbstractMenu` must be mutable, and must contain the fields `pagesize::Int` and
641+
`pageoffset::Int`.
642+
Any subtype must also implement the following functions:
643+
644+
```@docs
645+
REPL.TerminalMenus.pick
646+
REPL.TerminalMenus.cancel
647+
REPL.TerminalMenus.writeline
648+
```
649+
650+
It must also implement either `options` or `numoptions`:
651+
652+
```@docs
653+
REPL.TerminalMenus.options
654+
REPL.TerminalMenus.numoptions
655+
```
656+
657+
If the subtype does not have a field named `selected`, it must also implement
658+
659+
```@docs
660+
REPL.TerminalMenus.selected
661+
```
662+
663+
The following are optional but can allow additional customization:
664+
665+
```@docs
666+
REPL.TerminalMenus.header
667+
REPL.TerminalMenus.keypress
668+
```

0 commit comments

Comments
 (0)