Compare commits
9 Commits
option-sea
...
master
Author | SHA1 | Date |
---|---|---|
|
648d5c956e | |
|
8c6ee790bf | |
|
9fe596bc01 | |
|
d926606976 | |
|
3d1d2d5acd | |
|
58d6170a5c | |
|
206dcc46df | |
|
58455be58a | |
|
f0e5b7bb58 |
|
@ -1,11 +1,12 @@
|
|||
# Written by Sebastian Lohff <seba@someserver.de>
|
||||
# Licensed under Apache License 2.0
|
||||
|
||||
from clintermission.climenu import CliMenu, CliMenuStyle, CliMenuCursor, CliMenuTheme
|
||||
from clintermission.climenu import CliMenu, CliMenuStyle, CliMenuCursor, CliMenuTheme, cli_select_item
|
||||
|
||||
__all__ = [
|
||||
CliMenu,
|
||||
CliMenuStyle,
|
||||
CliMenuCursor,
|
||||
CliMenuTheme,
|
||||
cli_select_item,
|
||||
]
|
||||
|
|
|
@ -4,11 +4,11 @@ from prompt_toolkit.application import Application
|
|||
from prompt_toolkit.buffer import Buffer
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.filters import is_searching
|
||||
from prompt_toolkit.filters.base import Condition
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.layout import Layout, Window, HSplit
|
||||
from prompt_toolkit.layout.controls import BufferControl
|
||||
from prompt_toolkit.layout.processors import Processor, Transformation
|
||||
from prompt_toolkit import search
|
||||
from prompt_toolkit.widgets import SearchToolbar
|
||||
|
||||
|
||||
|
@ -68,16 +68,11 @@ class CliMenuTheme:
|
|||
CYAN = CliMenuStyle('cyan', 'lightcyan', 'cyan')
|
||||
BLUE = CliMenuStyle('ansiblue', 'ansired', 'ansiblue')
|
||||
ANSI_CYAN = CliMenuStyle('ansicyan', 'ansibrightcyan', 'ansicyan')
|
||||
|
||||
|
||||
@Condition
|
||||
def is_not_searching():
|
||||
print("CALLED IT")
|
||||
return not is_searching()
|
||||
BOLD_HIGHLIGHT = CliMenuStyle(header_style='bold', highlight_style='bold fg:black bg:white')
|
||||
|
||||
|
||||
class CliMenu:
|
||||
default_stye = CliMenuTheme.BASIC
|
||||
default_style = CliMenuTheme.BASIC
|
||||
default_cursor = CliMenuCursor.TRIANGLE
|
||||
|
||||
def __init__(self, options=None, header=None, cursor=None, style=None,
|
||||
|
@ -97,17 +92,17 @@ class CliMenu:
|
|||
|
||||
self._style = style
|
||||
if not self._style:
|
||||
self._style = self.default_stye
|
||||
self._style = self.default_style
|
||||
|
||||
if header:
|
||||
self.add_header(header, indent=False)
|
||||
|
||||
if options:
|
||||
for option in options:
|
||||
if isinstance(option, tuple):
|
||||
if isinstance(option, tuple) and len(option) == 2:
|
||||
self.add_option(*option)
|
||||
else:
|
||||
self.add_option(option)
|
||||
self.add_option(option, option)
|
||||
|
||||
def add_header(self, title, indent=True):
|
||||
for text in title.split('\n'):
|
||||
|
@ -124,6 +119,13 @@ class CliMenu:
|
|||
|
||||
return self._success
|
||||
|
||||
def get_options(self):
|
||||
return [_item for _item in self._items if isinstance(_item, CliMenuOption)]
|
||||
|
||||
@property
|
||||
def num_options(self):
|
||||
return self._item_num
|
||||
|
||||
def get_selection(self):
|
||||
if self.success:
|
||||
item = self._items[self._pos]
|
||||
|
@ -187,6 +189,38 @@ class CliMenu:
|
|||
if self._items[self._pos].focusable:
|
||||
break
|
||||
|
||||
def sync_cursor_to_line(self, line, sync_dir=1):
|
||||
"""Sync cursor to next fousable item starting on `line`"""
|
||||
assert sync_dir in (1, -1)
|
||||
|
||||
self._pos = line
|
||||
while not self._items[self._pos].focusable:
|
||||
self._pos = (self._pos + sync_dir) % len(self._items)
|
||||
self._buf.cursor_position = self._doc.translate_row_col_to_index(self._pos, 0)
|
||||
|
||||
def _get_search_result_lines(self):
|
||||
"""Get a list of all lines that have a match with the current search result"""
|
||||
if not self._bufctrl.search_state.text:
|
||||
return []
|
||||
|
||||
idx_list = []
|
||||
i = 1
|
||||
while True:
|
||||
next_idx = self._buf.get_search_position(self._bufctrl.search_state, count=i,
|
||||
include_current_position=False)
|
||||
if next_idx in idx_list:
|
||||
break
|
||||
idx_list.append(next_idx)
|
||||
i += 1
|
||||
|
||||
lines = []
|
||||
for idx in idx_list:
|
||||
line, _ = self._doc.translate_index_to_position(idx)
|
||||
if line not in lines:
|
||||
lines.append(line)
|
||||
|
||||
return lines
|
||||
|
||||
def _run(self):
|
||||
class MenuColorizer(Processor):
|
||||
def apply_transformation(_self, ti):
|
||||
|
@ -195,43 +229,47 @@ class CliMenu:
|
|||
# keybindings
|
||||
self._kb = KeyBindings()
|
||||
|
||||
@self._kb.add('q')
|
||||
@self._kb.add('q', filter=~is_searching)
|
||||
@self._kb.add('c-c')
|
||||
def quit(event):
|
||||
event.app.exit()
|
||||
|
||||
@self._kb.add('down')
|
||||
@self._kb.add('j')
|
||||
@self._kb.add('down', filter=~is_searching)
|
||||
@self._kb.add('j', filter=~is_searching)
|
||||
def down(event):
|
||||
self.next_item(1)
|
||||
|
||||
@self._kb.add('up')
|
||||
@self._kb.add('k')
|
||||
@self._kb.add('up', filter=~is_searching)
|
||||
@self._kb.add('k', filter=~is_searching)
|
||||
def up(event):
|
||||
self.next_item(-1)
|
||||
|
||||
@self._kb.add('n')
|
||||
@self._kb.add('N', filter=~is_searching)
|
||||
@self._kb.add('n', filter=~is_searching)
|
||||
def search_inc(event, filter=is_searching):
|
||||
print(self._bufctrl.search_state.text)
|
||||
print("curr pos", self._buf.get_search_position(self._bufctrl.search_state))
|
||||
# search_state.direction = search.SearchDirection.BACKWARD
|
||||
next10 = []
|
||||
for i in range(10):
|
||||
p = self._buf.get_search_position(self._bufctrl.search_state, count=i + 1, include_current_position=False)
|
||||
next10.append(p)
|
||||
if not self._bufctrl.search_state.text:
|
||||
return
|
||||
|
||||
print("next 10 pos are", next10)
|
||||
search_dir = 1 if event.data == 'n' else -1
|
||||
sr_lines = self._get_search_result_lines()
|
||||
if sr_lines:
|
||||
line = sr_lines[search_dir] if len(sr_lines) > 1 else sr_lines[0]
|
||||
self.sync_cursor_to_line(line, search_dir)
|
||||
|
||||
#search.do_incremental_search(search.SearchDirection.FORWARD)
|
||||
|
||||
@self._kb.add('right')
|
||||
#@self._kb.add('c-m')
|
||||
@self._kb.add('c-space')
|
||||
@self._kb.add('c-m', filter=~is_searching)
|
||||
@self._kb.add('right', filter=~is_searching)
|
||||
@self._kb.add('c-space', filter=~is_searching)
|
||||
def accept(event):
|
||||
self._success = True
|
||||
event.app.exit()
|
||||
|
||||
self._searchbar = SearchToolbar(text_if_not_searching=[('class:not-searching', 'NOOT NOOT')], ignore_case=True)
|
||||
@self._kb.add('c-m', filter=is_searching)
|
||||
def accept_search(event):
|
||||
search.accept_search()
|
||||
new_line, _ = self._doc.translate_index_to_position(self._buf.cursor_position)
|
||||
self.sync_cursor_to_line(new_line)
|
||||
|
||||
self._searchbar = SearchToolbar(ignore_case=True)
|
||||
|
||||
text = '\n'.join(map(lambda _x: _x.text, self._items))
|
||||
self._doc = Document(text, cursor_position=self._pos)
|
||||
|
@ -246,8 +284,7 @@ class CliMenu:
|
|||
self._searchbar])
|
||||
|
||||
# set initial pos
|
||||
if not self._items[self._pos].focusable:
|
||||
self.next_item(1)
|
||||
self.sync_cursor_to_line(0)
|
||||
|
||||
app = Application(layout=Layout(split),
|
||||
key_bindings=self._kb,
|
||||
|
@ -256,3 +293,18 @@ class CliMenu:
|
|||
|
||||
app.run()
|
||||
self._ran = True
|
||||
|
||||
|
||||
def cli_select_item(options, header=None, abort_exc=ValueError, abort_text="Selection aborted.", style=None,
|
||||
return_single=True):
|
||||
"""Helper function to quickly get a selection with just a few arguments"""
|
||||
menu = CliMenu(header=header, options=options, style=style)
|
||||
|
||||
if return_single and menu.num_options == 1:
|
||||
item = menu.get_options()[0]
|
||||
return item.num, item.item
|
||||
|
||||
if not menu.success:
|
||||
raise abort_exc(abort_text)
|
||||
|
||||
return menu.get_selection()
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
from clintermission import CliMenu, CliMenuTheme
|
||||
|
||||
|
||||
def main():
|
||||
q = ["Foo", "Bar", "Baz baz baz baz baz"]
|
||||
m = CliMenu(q, "Time to choose:\n", style=CliMenuTheme.BOLD_HIGHLIGHT)
|
||||
|
||||
if m.success:
|
||||
print("You selected", m.get_selection())
|
||||
else:
|
||||
print("You aborted the selection")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue