diff --git a/visidata/_input.py b/visidata/_input.py index 9ec5e56b2..6603f199a 100644 --- a/visidata/_input.py +++ b/visidata/_input.py @@ -14,7 +14,9 @@ vd.theme_option('disp_unprintable', '·', 'substitute character for unprintables') vd.theme_option('mouse_interval', 1, 'max time between press/release for click (ms)', sheettype=None) -vd.disp_help = 1 # current level of help shown (up to vd.options.disp_help as maximum) +vd.disp_help = 0 # current page of help shown +vd._help_sidebars = [] # list of (help:str|HelpPane, title:str) + class AcceptInput(Exception): '*args[0]* is the input to be accepted' @@ -89,31 +91,8 @@ def splice(v:str, i:int, s:str): return v if i < 0 else v[:i] + s + v[i:] -# vd.options.disp_help is the effective maximum disp_help. The user can cycle through the various levels of help. -class HelpCycler: - def __init__(self, scr=None, help=''): - self.help = help - self.scr = scr - - def __enter__(self): - self.draw() - - return self - - def __exit__(self, *args): - pass - - def cycle(self): - vd.disp_help = (vd.disp_help-1)%(vd.options.disp_help+1) - self.draw() - - def draw(self): - if self.scr: - vd.drawInputHelp(self.scr, self.help) - - @VisiData.api -def drawInputHelp(vd, scr, help:str=''): +def drawInputHelp(vd, scr): if not scr or not vd.cursesEnabled: return @@ -121,15 +100,7 @@ def drawInputHelp(vd, scr, help:str=''): if not sheet: return - curhelp = '' - if vd.disp_help == 0: - vd.drawSidebar(scr, sheet) - elif vd.disp_help == 1: - curhelp = help - sheet.drawSidebarText(scr, curhelp) - elif vd.disp_help >= 2: - curhelp = vd.getHelpPane('input', module='visidata') - sheet.drawSidebarText(scr, curhelp, title='Input Keystrokes Help') + vd.drawSidebar(scr, sheet) def clean_printable(s): @@ -223,7 +194,7 @@ def editline(vd, scr, y, x, w, i=0, If *clear* is True, clear whole editing area before displaying. ''' with EnableCursor(): - with HelpCycler(scr, help) as disp_help: + with vd.AddedHelp(vd.getHelpPane('input', module='visidata'), 'Input Keystrokes Help'), vd.AddedHelp(help, 'Input Field Help'): ESC='^[' TAB='^I' history_state = HistoryState(history) @@ -259,7 +230,7 @@ def find_nonword(s, a, b, incr): while True: vd.drawSheet(scr, vd.activeSheet) updater(v) - disp_help.draw() + vd.drawInputHelp(scr) if display: dispval = clean_printable(v) @@ -296,7 +267,7 @@ def find_nonword(s, a, b, incr): elif ch == '^E' or ch == 'KEY_END': i = len(v) elif ch == '^F' or ch == 'KEY_RIGHT': i += 1 elif ch == '^G': - disp_help.cycle() + vd.cycleSidebar() continue # not considered a first keypress elif ch in ('^H', 'KEY_BACKSPACE', '^?'): i -= 1; v = delchar(v, i) elif ch == TAB: v, i = complete_state.complete(v, i, +1) @@ -459,8 +430,7 @@ def _drawPrompt(val): return updater(val) - with HelpCycler() as disp_help: - while True: + while True: try: input_kwargs = kwargs[cur_input_key] input_kwargs['value'] = vd.input(**input_kwargs, diff --git a/visidata/help.py b/visidata/help.py index 882a8432d..bc9797a1e 100644 --- a/visidata/help.py +++ b/visidata/help.py @@ -4,7 +4,7 @@ from visidata import VisiData, MetaSheet, ColumnAttr, Column, BaseSheet, VisiDataMetaSheet, SuspendCurses from visidata import vd, asyncthread, ENTER, drawcache, AttrDict, TextSheet -vd.option('disp_help', 2, 'show help panel during input') +vd.option('disp_expert', 2, 'max level of options and columns to include') @BaseSheet.api def hint_basichelp(sheet): diff --git a/visidata/mainloop.py b/visidata/mainloop.py index b1709b7c3..3b53c881a 100644 --- a/visidata/mainloop.py +++ b/visidata/mainloop.py @@ -165,7 +165,7 @@ def mainloop(vd, scr): numTimeouts = 0 prefixWaiting = False vd.scrFull = scr - vd.disp_help = vd.options.disp_help + vd.disp_help = -1 if vd.options.disp_expert else 10 vd.keystrokes = '' vd.drawThread = threading.current_thread() diff --git a/visidata/sheets.py b/visidata/sheets.py index eb36d9a09..ec713a706 100644 --- a/visidata/sheets.py +++ b/visidata/sheets.py @@ -281,7 +281,7 @@ def resetCols(self): self.columns = [] for c in self.initialCols: self.addColumn(deepcopy(c)) - if self.options.disp_help > c.max_help: + if self.options.disp_expert > c.max_help: c.hide() self.setKeys(self.columns[:self.nKeys]) @@ -1121,7 +1121,7 @@ def _async_deepcopy(newlist, oldlist): BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload()', 'Reload current sheet') -Sheet.addCommand('^G', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line') +Sheet.addCommand('', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line') Sheet.addCommand('!', 'key-col', 'toggleKeys([cursorCol])', 'toggle current column as a key column') Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current column as a key column') diff --git a/visidata/sidebar.py b/visidata/sidebar.py index d711425ef..63dd0204a 100644 --- a/visidata/sidebar.py +++ b/visidata/sidebar.py @@ -12,19 +12,73 @@ vd.theme_option('color_sidebar', 'black on 114 blue', 'base color of sidebar') vd.theme_option('color_sidebar_title', 'black on yellow', 'color of sidebar title') +@VisiData.api +class AddedHelp: + '''Context manager to add help text/screen to list of available sidebars.''' + def __init__(self, text:Union[str,'HelpPane'], title=''): + if text: + self.helpfunc = lambda: (text, title) + else: + self.helpfunc = None + + def __enter__(self): + if self.helpfunc: + vd._help_sidebars.insert(0, self.helpfunc) + vd.clearCaches() + return self + + def __exit__(self, *args): + if self.helpfunc: + vd._help_sidebars.remove(self.helpfunc) + vd.clearCaches() + + @BaseSheet.property def formatter_helpstr(sheet): return AttrDict(commands=CommandHelpGetter(type(sheet)), options=OptionHelpGetter()) -@BaseSheet.property +@BaseSheet.cached_property def default_sidebar(sheet): 'Default to format options.disp_sidebar_fmt. Overridable.' fmt = sheet.options.disp_sidebar_fmt return sheet.formatString(fmt, help=sheet.formatter_helpstr) +@VisiData.api +def cycleSidebar(vd, n:int=1): + if vd.sheet.help_sidebars: + vd.disp_help += n + if vd.disp_help >= len(vd.sheet.help_sidebars): + vd.disp_help = -1 + + vd.options.disp_sidebar = (vd.disp_help >= 0) + else: + vd.disp_help = -1 + + +@BaseSheet.cached_property +def help_sidebars(sheet) -> 'list[Callable[[], tuple[str,str]]]': + r = [] + if sheet.default_sidebar: + r.append(lambda: (sheet.default_sidebar, '')) + if sheet.guide: + r.append(lambda: (sheet.formatString(sheet.guide, help=sheet.formatter_helpstr), '')) + return r + vd._help_sidebars + + +@VisiData.cached_property +def sidebarStatus(vd) -> str: + if vd.sheet.help_sidebars: + if vd.options.disp_sidebar and vd.disp_help >= 0: + n = vd.disp_help+1 + return f'[:onclick sidebar-toggle][:sidebar][{n}/{len(vd.sheet.help_sidebars)}][/]' + else: + return f'[:onclick sidebar-toggle][:sidebar][{len(vd.sheet.help_sidebars)}][/]' + + return '' + @VisiData.property def recentStatusMessages(vd) -> str: r = '' @@ -51,27 +105,24 @@ def recentStatusMessages(vd) -> str: @VisiData.api def drawSidebar(vd, scr, sheet): sidebar = vd.recentStatusMessages + title = '' bottommsg = '' overflowmsg = '[:reverse] Ctrl+P to view all status messages [/]' try: - if not sidebar and sheet.options.disp_sidebar: - sidebar = sheet.default_sidebar - if not sidebar and sheet.options.disp_help > 0: - sidebar = sheet.formatString(sheet.guide, help=sheet.formatter_helpstr) - - if sheet.options.disp_help < 0: - bottommsg = '[:onclick sidebar-toggle][:reverse][x][:]' - overflowmsg = '[:onclick open-sidebar]…↓…[/]' - else: - bottommsg = sheet.formatString('[:onclick sidebar-toggle][:reverse] {help.commands.sidebar_toggle} [:]', help=sheet.formatter_helpstr) - overflowmsg = '[:reverse] see full sidebar with [:code]gb[/] [:]' + if not sidebar and vd.options.disp_sidebar and vd.disp_help >= 0: + sidebar, title = sheet.help_sidebars[vd.disp_help%len(sheet.help_sidebars)]() + +# bottommsg = sheet.formatString('[:onclick sidebar-toggle][:reverse] {help.commands.sidebar_toggle} [:]', help=sheet.formatter_helpstr) +# overflowmsg = '[:reverse] see full sidebar with [:code]gb[/] [:]' + + overflowmsg = '[:onclick open-sidebar]…↓…[/]' except Exception as e: vd.exceptionCaught(e) sidebar = f'# error\n{e}' sheet.current_sidebar = sidebar - return sheet.drawSidebarText(scr, text=sheet.current_sidebar, overflowmsg=overflowmsg, bottommsg=bottommsg) + return sheet.drawSidebarText(scr, text=sheet.current_sidebar, title=title, overflowmsg=overflowmsg, bottommsg=bottommsg) @BaseSheet.api def drawSidebarText(sheet, scr, text:Union[None,str,'HelpPane'], title:str='', overflowmsg:str='', bottommsg:str=''): @@ -152,11 +203,13 @@ class SidebarSheet(TextSheet): - `b` to toggle the sidebar on/off for the current sheet ''' -BaseSheet.addCommand('b', 'sidebar-toggle', 'sheet.options.disp_sidebar = not sheet.options.disp_sidebar', 'toggle sidebar') +BaseSheet.addCommand('b', 'sidebar-toggle', 'vd.options.disp_sidebar = not vd.options.disp_sidebar', 'toggle sidebar') BaseSheet.addCommand('gb', 'open-sidebar', 'sheet.current_sidebar = "" if not hasattr(sheet, "current_sidebar") else sheet.current_sidebar; vd.push(SidebarSheet(name, options.disp_sidebar_fmt, source=sheet.current_sidebar.splitlines()))', 'open sidebar in new sheet') +BaseSheet.addCommand('^G', 'sidebar-cycle', 'vd.cycleSidebar()', 'cycle through available sidebar panels') vd.addMenuItems(''' View > Sidebar > toggle > sidebar-toggle + View > Sidebar > cycle > sidebar-cycle View > Sidebar > open in new sheet > open-sidebar ''') diff --git a/visidata/statusbar.py b/visidata/statusbar.py index 9b4c323fb..a603f6750 100644 --- a/visidata/statusbar.py +++ b/visidata/statusbar.py @@ -11,7 +11,8 @@ from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, dispwidth, ColorAttr -vd.option('disp_rstatus_fmt', '{sheet.threadStatus} {sheet.keystrokeStatus} [:longname_status]{sheet.longname}[/] {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus}{sheet.selectedStatus}{vd.replayStatus}', 'right-side status format string') + +vd.option('disp_rstatus_fmt', '{sheet.threadStatus} {sheet.keystrokeStatus} [:longname_status]{sheet.longname}[/] {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus}{sheet.selectedStatus}{vd.replayStatus}{vd.sidebarStatus}', 'right-side status format string') vd.option('disp_status_fmt', '{sheet.sheetlist}| ', 'left-side status format string') vd.theme_option('disp_lstatus_max', 0, 'maximum length of left status line') vd.theme_option('disp_status_sep', '│', 'separator between statuses')