@ -33,9 +33,9 @@ class CliMenuCursor:
""" Collection of cursors pointing at the active menu item """
""" Collection of cursors pointing at the active menu item """
BULLET = ' ● '
BULLET = ' ● '
TRIANGLE = ' ▶ '
TRIANGLE = ' ▶ '
CL I_STAR = ' * '
ASCI I_STAR = ' * '
CL I_ARROW = ' --> '
ASCI I_ARROW = ' --> '
CL I_CAT = ' =^.^= '
ASCI I_CAT = ' =^.^= '
CAT = ' 😸 '
CAT = ' 😸 '
ARROW = ' → '
ARROW = ' → '
@ -50,15 +50,14 @@ class CliMenuStyle:
self . highlight_style = highlight_style
self . highlight_style = highlight_style
self . header_style = header_style
self . header_style = header_style
# self.option_color = '#aa0000'
# #self.highlight_color = 'fg:ansiblue bg:ansired bold'
class CliSelectionStyle :
# self.highlight_color = 'bold'
SQUARE_BRACKETS = ( ' [x] ' , ' [ ] ' )
# self.cursor = ' --> '
ROUND_BRACKETS = ( ' (x) ' , ' ( ) ' )
# self.cursor = ' ● '
CHECKMARK = ( ' ✔ ' , ' ✖ ' )
# self.no_cursor = ' '
THUMBS = ( ' 👍 ' , ' 👎 ' )
# self.header_color = '#aa22cc'
SMILEY = ( ' 🙂 ' , ' 🙁 ' )
# self.option_indent = 4
SMILEY_EXTREME = ( ' 😁 ' , ' 😨 ' )
# self.header_indent = 4
class CliMenuTheme :
class CliMenuTheme :
@ -75,36 +74,48 @@ class CliMenu:
default_style = CliMenuTheme . BASIC
default_style = CliMenuTheme . BASIC
default_cursor = CliMenuCursor . TRIANGLE
default_cursor = CliMenuCursor . TRIANGLE
@classmethod
def set_default_style ( cls , style ) :
cls . default_style = style
@classmethod
def set_default_cursor ( cls , cursor ) :
cls . default_cursor = cursor
def __init__ ( self , options = None , header = None , cursor = None , style = None ,
def __init__ ( self , options = None , header = None , cursor = None , style = None ,
indent = 2 , dedent_selection = False ) :
indent = 2 , dedent_selection = False , initial_pos = 0 ):
self . _items = [ ]
self . _items = [ ]
self . _item_num = 0
self . _item_num = 0
self . _ran = False
self . _ran = False
self . _success = None
self . _success = None
self . _pos = 0
self . _pos = 0
self . _initial_pos = initial_pos
self . _option_indent = indent
self . _option_indent = indent
self . _header_indent = indent
self . _header_indent = indent
self . _dedent_selection = dedent_selection
self . _dedent_selection = dedent_selection
self . _cursor = cursor
self . _cursor = cursor if cursor is not None else self . default_cursor
if not self . _cursor :
self . _style = style if style is not None else self . default_style
self . _cursor = self . default_cursor
self . _style = style
if not self . _style :
self . _style = self . default_style
if header :
if header :
self . add_ header ( header , indent = False )
self . add_text ( header , indent = False )
if options :
if options :
for option in options :
for option in options :
if isinstance ( option , tuple ) and len ( option ) == 2 :
if isinstance ( option , tuple ) :
self . add_option ( * option )
self . add_option ( * option )
else :
elif isinstance ( option , dict ) :
self . add_option ( * * option )
elif isinstance ( option , str ) :
self . add_option ( option , option )
self . add_option ( option , option )
else :
raise ValueError ( " Option needs to be either tuple, dict or string, found ' {} ' of type {} "
. format ( option , type ( option ) ) )
def add_header ( self , * args , * * kwargs ) :
return self . add_text ( * args , * * kwargs )
def add_header ( self , title , indent = True ) :
def add_ text ( self , title , indent = True ) :
for text in title . split ( ' \n ' ) :
for text in title . split ( ' \n ' ) :
self . _items . append ( CliMenuHeader ( text , indent = indent ) )
self . _items . append ( CliMenuHeader ( text , indent = indent ) )
@ -129,7 +140,6 @@ class CliMenu:
def get_selection ( self ) :
def get_selection ( self ) :
if self . success :
if self . success :
item = self . _items [ self . _pos ]
item = self . _items [ self . _pos ]
return ( item . num , item . item )
return ( item . num , item . item )
else :
else :
return ( None , None )
return ( None , None )
@ -140,13 +150,8 @@ class CliMenu:
def get_selection_item ( self ) :
def get_selection_item ( self ) :
return self . get_selection ( ) [ 1 ]
return self . get_selection ( ) [ 1 ]
def cursor ( self ) :
def _transform_prefix ( self , item , lineno , prefix ) :
return ' {} ' . format ( self . _cursor )
return prefix
@property
def no_cursor ( self ) :
# cursor with spaces minus dedent
return ' ' * ( len ( self . _cursor ) + 1 * self . _dedent_selection )
def _transform_line ( self , ti ) :
def _transform_line ( self , ti ) :
if len ( list ( ti . fragments ) ) == 0 :
if len ( list ( ti . fragments ) ) == 0 :
@ -173,6 +178,7 @@ class CliMenu:
style = s . header_style
style = s . header_style
items = [ ( s if s else style , t ) for s , t in ti . fragments ]
items = [ ( s if s else style , t ) for s , t in ti . fragments ]
prefix = self . _transform_prefix ( item , ti . lineno , prefix )
return Transformation ( [ ( ' ' , indent ) , ( style , prefix ) ] + items )
return Transformation ( [ ( ' ' , indent ) , ( style , prefix ) ] + items )
@ -221,7 +227,14 @@ class CliMenu:
return lines
return lines
def _register_extra_kb_cbs ( self , kb ) :
pass
def _run ( self ) :
def _run ( self ) :
if self . _initial_pos < 0 or self . _initial_pos > = self . _item_num :
raise ValueError ( " Initial position {} is out of range, needs to be in range of [0, {} ) "
. format ( self . _initial_pos , self . _item_num ) )
class MenuColorizer ( Processor ) :
class MenuColorizer ( Processor ) :
def apply_transformation ( _self , ti ) :
def apply_transformation ( _self , ti ) :
return self . _transform_line ( ti )
return self . _transform_line ( ti )
@ -246,7 +259,7 @@ class CliMenu:
@self._kb.add ( ' N ' , filter = ~ is_searching )
@self._kb.add ( ' N ' , filter = ~ is_searching )
@self._kb.add ( ' n ' , filter = ~ is_searching )
@self._kb.add ( ' n ' , filter = ~ is_searching )
def search_inc ( event , filter = is_searching ):
def search_inc ( event ):
if not self . _bufctrl . search_state . text :
if not self . _bufctrl . search_state . text :
return
return
@ -258,7 +271,6 @@ class CliMenu:
@self._kb.add ( ' c-m ' , filter = ~ is_searching )
@self._kb.add ( ' c-m ' , filter = ~ is_searching )
@self._kb.add ( ' right ' , filter = ~ is_searching )
@self._kb.add ( ' right ' , filter = ~ is_searching )
@self._kb.add ( ' c-space ' , filter = ~ is_searching )
def accept ( event ) :
def accept ( event ) :
self . _success = True
self . _success = True
event . app . exit ( )
event . app . exit ( )
@ -269,6 +281,8 @@ class CliMenu:
new_line , _ = self . _doc . translate_index_to_position ( self . _buf . cursor_position )
new_line , _ = self . _doc . translate_index_to_position ( self . _buf . cursor_position )
self . sync_cursor_to_line ( new_line )
self . sync_cursor_to_line ( new_line )
self . _register_extra_kb_cbs ( self . _kb )
self . _searchbar = SearchToolbar ( ignore_case = True )
self . _searchbar = SearchToolbar ( ignore_case = True )
text = ' \n ' . join ( map ( lambda _x : _x . text , self . _items ) )
text = ' \n ' . join ( map ( lambda _x : _x . text , self . _items ) )
@ -284,7 +298,8 @@ class CliMenu:
self . _searchbar ] )
self . _searchbar ] )
# set initial pos
# set initial pos
self . sync_cursor_to_line ( 0 )
for _ in range ( self . _initial_pos + 1 ) :
self . next_item ( 1 )
app = Application ( layout = Layout ( split ) ,
app = Application ( layout = Layout ( split ) ,
key_bindings = self . _kb ,
key_bindings = self . _kb ,
@ -295,6 +310,61 @@ class CliMenu:
self . _ran = True
self . _ran = True
class CliMultiMenu ( CliMenu ) :
default_selection_icons = ( ' [x] ' , ' [ ] ' )
@classmethod
def set_default_selector_icons ( cls , selection_icons ) :
cls . default_selection_icons = selection_icons
def __init__ ( self , * args , selection_icons = None , * * kwargs ) :
self . _multi_selected = [ ]
self . _selection_icons = selection_icons if selection_icons is not None else self . default_selection_icons
super ( ) . __init__ ( * args , * * kwargs )
def add_option ( self , text , item = None , selected = False ) :
super ( ) . add_option ( text , item )
if selected :
self . _multi_selected . append ( len ( self . _items ) - 1 )
def get_selection ( self ) :
if self . success :
return [ ( self . _items [ n ] . num , self . _items [ n ] . item ) for n in self . _multi_selected ]
else :
return None
def get_selection_num ( self ) :
if self . success :
return [ self . _items [ n ] . num for n in self . _multi_selected ]
else :
return None
def get_selection_item ( self ) :
if self . success :
return [ self . _items [ n ] . item for n in self . _multi_selected ]
else :
return None
def _register_extra_kb_cbs ( self , kb ) :
@kb.add ( ' space ' , filter = ~ is_searching )
@kb.add ( ' right ' , filter = ~ is_searching )
def mark ( event ) :
if self . _pos not in self . _multi_selected :
self . _multi_selected . append ( self . _pos )
else :
self . _multi_selected . remove ( self . _pos )
def _transform_prefix ( self , item , lineno , prefix ) :
if item . focusable :
if lineno in self . _multi_selected :
icon = self . _selection_icons [ 0 ]
else :
icon = self . _selection_icons [ 1 ]
return " {} {} " . format ( prefix , icon )
else :
return prefix
def cli_select_item ( options , header = None , abort_exc = ValueError , abort_text = " Selection aborted. " , style = None ,
def cli_select_item ( options , header = None , abort_exc = ValueError , abort_text = " Selection aborted. " , style = None ,
return_single = True ) :
return_single = True ) :
""" Helper function to quickly get a selection with just a few arguments """
""" Helper function to quickly get a selection with just a few arguments """