visidata 3.0.1__py3-none-any.whl → 3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +12 -10
- visidata/_input.py +208 -199
- visidata/_open.py +4 -1
- visidata/_types.py +4 -3
- visidata/aggregators.py +88 -39
- visidata/apps/vdsql/_ibis.py +9 -11
- visidata/apps/vdsql/clickhouse.py +2 -2
- visidata/apps/vdsql/snowflake.py +1 -1
- visidata/apps/vgit/status.py +1 -1
- visidata/basesheet.py +11 -4
- visidata/canvas.py +66 -24
- visidata/clipboard.py +13 -6
- visidata/cliptext.py +7 -6
- visidata/cmdlog.py +40 -27
- visidata/column.py +14 -49
- visidata/ddw/regex.ddw +3 -2
- visidata/deprecated.py +14 -2
- visidata/desktop/visidata.desktop +2 -2
- visidata/editor.py +1 -0
- visidata/errors.py +1 -1
- visidata/experimental/sort_selected.py +54 -0
- visidata/expr.py +69 -18
- visidata/features/change_precision.py +1 -3
- visidata/features/cmdpalette.py +23 -4
- visidata/features/colorsheet.py +1 -1
- visidata/features/dedupe.py +3 -3
- visidata/features/go_col.py +71 -0
- visidata/features/graph_seaborn.py +1 -1
- visidata/features/join.py +20 -10
- visidata/features/layout.py +18 -5
- visidata/features/ping.py +16 -12
- visidata/features/regex.py +5 -5
- visidata/features/slide.py +15 -17
- visidata/features/status_source.py +3 -1
- visidata/features/sysedit.py +1 -1
- visidata/features/transpose.py +2 -1
- visidata/features/type_ipaddr.py +2 -4
- visidata/features/unfurl.py +1 -0
- visidata/form.py +2 -2
- visidata/freqtbl.py +16 -11
- visidata/fuzzymatch.py +1 -0
- visidata/graph.py +173 -12
- visidata/guide.py +61 -25
- visidata/guides/ClipboardGuide.md +48 -0
- visidata/guides/ColumnsGuide.md +52 -0
- visidata/guides/CommandsSheet.md +28 -0
- visidata/guides/DirSheet.md +34 -0
- visidata/guides/ErrorsSheet.md +17 -0
- visidata/guides/FrequencyTable.md +42 -0
- visidata/guides/GrepSheet.md +28 -0
- visidata/guides/JsonSheet.md +38 -0
- visidata/guides/MacrosSheet.md +19 -0
- visidata/guides/MeltGuide.md +52 -0
- visidata/guides/MemorySheet.md +7 -0
- visidata/guides/MenuGuide.md +26 -0
- visidata/guides/ModifyGuide.md +38 -0
- visidata/guides/PivotGuide.md +71 -0
- visidata/guides/RegexGuide.md +107 -0
- visidata/guides/SelectionGuide.md +44 -0
- visidata/guides/SlideGuide.md +26 -0
- visidata/guides/SortGuide.md +0 -0
- visidata/guides/SplitpaneGuide.md +15 -0
- visidata/guides/TypesSheet.md +43 -0
- visidata/guides/XsvGuide.md +36 -0
- visidata/help.py +6 -6
- visidata/hint.py +2 -1
- visidata/indexsheet.py +2 -2
- visidata/interface.py +13 -14
- visidata/keys.py +4 -1
- visidata/loaders/api_airtable.py +1 -1
- visidata/loaders/archive.py +1 -1
- visidata/loaders/csv.py +9 -5
- visidata/loaders/eml.py +11 -6
- visidata/loaders/f5log.py +1 -0
- visidata/loaders/fec.py +18 -42
- visidata/loaders/fixed_width.py +19 -3
- visidata/loaders/grep.py +121 -0
- visidata/loaders/html.py +1 -0
- visidata/loaders/http.py +6 -1
- visidata/loaders/json.py +22 -4
- visidata/loaders/jsonla.py +8 -2
- visidata/loaders/mailbox.py +1 -0
- visidata/loaders/markdown.py +25 -6
- visidata/loaders/msgpack.py +19 -0
- visidata/loaders/npy.py +0 -1
- visidata/loaders/odf.py +18 -4
- visidata/loaders/orgmode.py +1 -1
- visidata/loaders/rec.py +6 -4
- visidata/loaders/sas.py +11 -4
- visidata/loaders/scrape.py +0 -1
- visidata/loaders/texttables.py +2 -0
- visidata/loaders/tsv.py +24 -7
- visidata/loaders/unzip_http.py +127 -3
- visidata/loaders/vds.py +4 -0
- visidata/loaders/vdx.py +1 -1
- visidata/loaders/xlsx.py +5 -0
- visidata/loaders/xml.py +2 -1
- visidata/macros.py +14 -31
- visidata/main.py +20 -15
- visidata/mainloop.py +17 -6
- visidata/man/vd.1 +74 -39
- visidata/man/vd.txt +73 -41
- visidata/memory.py +16 -5
- visidata/menu.py +14 -3
- visidata/metasheets.py +5 -6
- visidata/modify.py +4 -4
- visidata/mouse.py +2 -0
- visidata/movement.py +14 -28
- visidata/optionssheet.py +3 -5
- visidata/path.py +59 -37
- visidata/pivot.py +8 -5
- visidata/pyobj.py +63 -9
- visidata/rename_col.py +18 -1
- visidata/save.py +16 -9
- visidata/search.py +4 -4
- visidata/selection.py +10 -56
- visidata/settings.py +37 -35
- visidata/sheets.py +189 -118
- visidata/shell.py +23 -14
- visidata/sidebar.py +71 -16
- visidata/sort.py +21 -6
- visidata/statusbar.py +42 -5
- visidata/stored_list.py +5 -2
- visidata/tests/conftest.py +1 -0
- visidata/tests/test_commands.py +9 -1
- visidata/tests/test_completer.py +18 -0
- visidata/tests/test_edittext.py +3 -2
- visidata/text_source.py +7 -4
- visidata/textsheet.py +20 -6
- visidata/themes/ascii8.py +9 -6
- visidata/themes/asciimono.py +14 -4
- visidata/threads.py +13 -3
- visidata/tuiwin.py +5 -1
- visidata/type_currency.py +1 -2
- visidata/type_date.py +6 -1
- visidata/undo.py +10 -13
- visidata/utils.py +9 -3
- visidata/vdobj.py +21 -1
- visidata/wrappers.py +9 -1
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/applications/visidata.desktop +2 -2
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/vd.1 +74 -39
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/visidata.1 +74 -39
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/METADATA +33 -5
- visidata-3.1.dist-info/RECORD +284 -0
- visidata-3.0.1.dist-info/RECORD +0 -258
- {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd +0 -0
- {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/WHEEL +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/entry_points.txt +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/top_level.txt +0 -0
visidata/shell.py
CHANGED
@@ -41,6 +41,12 @@ def exec_shell(*args):
|
|
41
41
|
|
42
42
|
@VisiData.api
|
43
43
|
def open_dir(vd, p):
|
44
|
+
if p.is_dir():
|
45
|
+
return DirSheet(p.base_stem, source=p)
|
46
|
+
if p.is_file():
|
47
|
+
vd.status(f'opening {p.given} as txt')
|
48
|
+
return vd.open_txt(p)
|
49
|
+
vd.warning(f'could not determine file type for {p.given}')
|
44
50
|
return DirSheet(p.base_stem, source=p)
|
45
51
|
|
46
52
|
@VisiData.api
|
@@ -48,8 +54,8 @@ def open_fdir(vd, p):
|
|
48
54
|
return FileListSheet(p.base_stem, source=p)
|
49
55
|
|
50
56
|
@VisiData.api
|
51
|
-
def addShellColumns(vd, cmd, sheet):
|
52
|
-
shellcol = ColumnShell(cmd, source=sheet, width=0)
|
57
|
+
def addShellColumns(vd, cmd, sheet, curcol=None):
|
58
|
+
shellcol = ColumnShell(cmd, source=sheet, width=0, curcol=curcol)
|
53
59
|
sheet.addColumnAtCursor(
|
54
60
|
Column(cmd+'_stdout', type=bytes.rstrip, srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[0]),
|
55
61
|
Column(cmd+'_stderr', type=bytes.rstrip, srccol=shellcol, getter=lambda col,row: col.srccol.getValue(row)[1]),
|
@@ -57,23 +63,23 @@ def addShellColumns(vd, cmd, sheet):
|
|
57
63
|
|
58
64
|
|
59
65
|
class ColumnShell(Column):
|
60
|
-
def __init__(self, name, cmd=None, **kwargs):
|
66
|
+
def __init__(self, name, cmd=None, curcol=None, **kwargs):
|
61
67
|
super().__init__(name, **kwargs)
|
62
68
|
self.expr = cmd or name
|
69
|
+
self.curcol = curcol
|
63
70
|
|
64
71
|
@asynccache(lambda col,row: (col, col.sheet.rowid(row)))
|
65
72
|
def calcValue(self, row):
|
66
73
|
try:
|
67
74
|
import shlex
|
68
75
|
args = []
|
69
|
-
context = LazyComputeRow(self.source, row)
|
76
|
+
context = LazyComputeRow(self.source, row, curcol=self.curcol)
|
70
77
|
for arg in shlex.split(self.expr):
|
71
78
|
if arg.startswith('$'):
|
72
|
-
|
73
|
-
|
74
|
-
args.append(arg)
|
79
|
+
arg = shlex.quote(str(context[arg[1:]]))
|
80
|
+
args.append(arg)
|
75
81
|
|
76
|
-
p = subprocess.Popen([os.getenv('SHELL', 'bash'), '-c',
|
82
|
+
p = subprocess.Popen([os.getenv('SHELL', 'bash'), '-c', shlex.join(args)],
|
77
83
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
78
84
|
return p.communicate()
|
79
85
|
except Exception as e:
|
@@ -88,20 +94,19 @@ class DirSheet(Sheet):
|
|
88
94
|
|
89
95
|
- {help.commands.open_row_file}
|
90
96
|
- {help.commands.open_rows}
|
91
|
-
-
|
97
|
+
- {help.commands.open_dir_parent}
|
92
98
|
- {help.commands.sysopen_row}
|
93
99
|
|
94
100
|
## Options (must reload to take effect)
|
95
101
|
|
96
102
|
- {help.options.dir_depth}
|
97
|
-
- [CLI] `-r` to include all files in all subfolders
|
98
103
|
- {help.options.dir_hidden}
|
99
104
|
'''
|
100
105
|
rowtype = 'files' # rowdef: Path
|
101
106
|
defer = True
|
102
107
|
columns = [
|
103
108
|
Column('directory',
|
104
|
-
getter=lambda col,row: str(row.parent) if str(row.parent)
|
109
|
+
getter=lambda col,row: str(row.parent) if str(row.parent) in ('.', '/') else str(row.parent) + '/',
|
105
110
|
setter=lambda col,row,val: col.sheet.moveFile(row, val)),
|
106
111
|
Column('filename',
|
107
112
|
getter=lambda col,row: row._path.name,
|
@@ -248,15 +253,18 @@ def inputShell(vd):
|
|
248
253
|
DirSheet.addCommand('`', 'open-dir-parent', 'vd.push(openSource(source.parent if source.resolve()!=Path(".").resolve() else os.path.dirname(source.resolve())))', 'open parent directory') #1801
|
249
254
|
BaseSheet.addCommand('', 'open-dir-current', 'vd.push(vd.currentDirSheet)', 'open Directory Sheet: browse properties of files in current directory')
|
250
255
|
|
251
|
-
Sheet.addCommand('z;', 'addcol-shell', 'cmd=inputShell(); addShellColumns(cmd, sheet)', 'create new column from bash expression, with $columnNames as variables')
|
256
|
+
Sheet.addCommand('z;', 'addcol-shell', 'cmd=inputShell(); addShellColumns(cmd, sheet, curcol=cursorCol)', 'create new column from bash expression, with $columnNames as variables')
|
252
257
|
|
253
258
|
DirSheet.addCommand(ENTER, 'open-row-file', 'vd.push(openSource(cursorRow or fail("no row"), filetype="dir" if cursorRow.is_dir() else LazyComputeRow(sheet, cursorRow).ext))', 'open current file as a new sheet')
|
254
259
|
DirSheet.addCommand('g'+ENTER, 'open-rows', 'for r in selectedRows: vd.push(openSource(r))', 'open selected files as new sheets')
|
255
260
|
DirSheet.addCommand('^O', 'sysopen-row', 'launchEditor(cursorRow)', 'open current file in external $EDITOR')
|
256
261
|
DirSheet.addCommand('g^O', 'sysopen-rows', 'launchEditor(*selectedRows)', 'open selected files in external $EDITOR')
|
257
262
|
|
258
|
-
DirSheet.addCommand('y', 'copy-row', 'copy_files([cursorRow], inputPath("copy to dest: "))', 'copy file to given directory')
|
259
|
-
DirSheet.addCommand('gy', 'copy-selected', 'copy_files(selectedRows, inputPath("copy to dest: ", value=cursorRow.given))', 'copy selected files to given directory')
|
263
|
+
DirSheet.addCommand('y', 'copy-row', 'copy_files([cursorRow], inputPath("copy to dest: "))', 'copy file to given directory *path*')
|
264
|
+
DirSheet.addCommand('gy', 'copy-selected', 'copy_files(selectedRows, inputPath("copy to dest: ", value=cursorRow.given))', 'copy selected files to given directory *path*')
|
265
|
+
|
266
|
+
DirSheet.addCommand('z'+ENTER, 'open-row-filetype', 'ft = input("filetype: ", type="filetype", value=options.filetype or LazyComputeRow(sheet, cursorRow).ext); vd.push(openSource(cursorRow, filetype=ft) or fail(f"file {cursorDisplay} does not exist"))', 'open file in current row as input filetype')
|
267
|
+
|
260
268
|
|
261
269
|
@DirSheet.api
|
262
270
|
@asyncthread
|
@@ -282,4 +290,5 @@ vd.addGlobals({
|
|
282
290
|
|
283
291
|
vd.addMenuItems('''
|
284
292
|
Column > Add column > shell > addcol-shell
|
293
|
+
Open > file in row > open-row-filetype
|
285
294
|
''')
|
visidata/sidebar.py
CHANGED
@@ -6,25 +6,81 @@ from visidata import CommandHelpGetter, OptionHelpGetter
|
|
6
6
|
|
7
7
|
|
8
8
|
vd.option('disp_sidebar', True, 'whether to display sidebar')
|
9
|
-
vd.option('disp_sidebar_fmt', '
|
9
|
+
vd.option('disp_sidebar_fmt', '', 'format string for default sidebar')
|
10
10
|
vd.theme_option('disp_sidebar_width', 0, 'max width for sidebar')
|
11
11
|
vd.theme_option('disp_sidebar_height', 0, 'max height for sidebar')
|
12
12
|
vd.theme_option('color_sidebar', 'black on 114 blue', 'base color of sidebar')
|
13
13
|
vd.theme_option('color_sidebar_title', 'black on yellow', 'color of sidebar title')
|
14
14
|
|
15
|
+
@VisiData.api
|
16
|
+
class AddedHelp:
|
17
|
+
'''Context manager to add help text/screen to list of available sidebars.'''
|
18
|
+
def __init__(self, text:Union[str,'HelpPane'], title=''):
|
19
|
+
if text:
|
20
|
+
self.helpfunc = lambda: (text, title)
|
21
|
+
else:
|
22
|
+
self.helpfunc = None
|
23
|
+
|
24
|
+
def __enter__(self):
|
25
|
+
if self.helpfunc:
|
26
|
+
vd._help_sidebars.insert(0, self.helpfunc)
|
27
|
+
vd.clearCaches()
|
28
|
+
return self
|
29
|
+
|
30
|
+
def __exit__(self, *args):
|
31
|
+
if self.helpfunc:
|
32
|
+
vd._help_sidebars.remove(self.helpfunc)
|
33
|
+
vd.clearCaches()
|
34
|
+
|
35
|
+
|
15
36
|
@BaseSheet.property
|
16
37
|
def formatter_helpstr(sheet):
|
17
38
|
return AttrDict(commands=CommandHelpGetter(type(sheet)),
|
18
39
|
options=OptionHelpGetter())
|
19
40
|
|
20
41
|
|
21
|
-
@BaseSheet.
|
42
|
+
@BaseSheet.cached_property
|
22
43
|
def default_sidebar(sheet):
|
23
44
|
'Default to format options.disp_sidebar_fmt. Overridable.'
|
24
45
|
fmt = sheet.options.disp_sidebar_fmt
|
46
|
+
if not fmt: return ''
|
25
47
|
return sheet.formatString(fmt, help=sheet.formatter_helpstr)
|
26
48
|
|
27
49
|
|
50
|
+
@VisiData.api
|
51
|
+
def cycleSidebar(vd, n:int=1):
|
52
|
+
if vd.sheet.help_sidebars:
|
53
|
+
vd.disp_help += n
|
54
|
+
if vd.disp_help >= len(vd.sheet.help_sidebars):
|
55
|
+
vd.disp_help = -1
|
56
|
+
|
57
|
+
vd.options.disp_sidebar = (vd.disp_help >= 0)
|
58
|
+
else:
|
59
|
+
vd.disp_help = -1
|
60
|
+
vd.clearCaches()
|
61
|
+
|
62
|
+
|
63
|
+
@BaseSheet.cached_property
|
64
|
+
def help_sidebars(sheet) -> 'list[Callable[[], tuple[str,str]]]':
|
65
|
+
r = []
|
66
|
+
if sheet.default_sidebar:
|
67
|
+
r.append(lambda: (sheet.default_sidebar, ''))
|
68
|
+
if sheet.guide:
|
69
|
+
r.append(lambda: (sheet.formatString(sheet.guide, help=sheet.formatter_helpstr), ''))
|
70
|
+
return r + vd._help_sidebars
|
71
|
+
|
72
|
+
|
73
|
+
@VisiData.cached_property
|
74
|
+
def sidebarStatus(vd) -> str:
|
75
|
+
if vd.sheet.help_sidebars:
|
76
|
+
if vd.options.disp_sidebar and vd.disp_help >= 0:
|
77
|
+
n = vd.disp_help+1
|
78
|
+
return f'[:onclick sidebar-toggle][:sidebar][{n}/{len(vd.sheet.help_sidebars)}][/]'
|
79
|
+
else:
|
80
|
+
return f'[:onclick sidebar-toggle][:sidebar][{len(vd.sheet.help_sidebars)}][/]'
|
81
|
+
|
82
|
+
return ''
|
83
|
+
|
28
84
|
@VisiData.property
|
29
85
|
def recentStatusMessages(vd) -> str:
|
30
86
|
r = ''
|
@@ -51,27 +107,24 @@ def recentStatusMessages(vd) -> str:
|
|
51
107
|
@VisiData.api
|
52
108
|
def drawSidebar(vd, scr, sheet):
|
53
109
|
sidebar = vd.recentStatusMessages
|
110
|
+
title = ''
|
54
111
|
bottommsg = ''
|
55
112
|
overflowmsg = '[:reverse] Ctrl+P to view all status messages [/]'
|
56
113
|
try:
|
57
|
-
if not sidebar and
|
58
|
-
sidebar = sheet.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
overflowmsg = '[:onclick open-sidebar]…↓…[/]'
|
65
|
-
else:
|
66
|
-
bottommsg = '[:onclick sidebar-toggle][:reverse] b to toggle sidebar [:]'
|
67
|
-
overflowmsg = '[:reverse] see full sidebar with [:code]gb[/] [:]'
|
114
|
+
if not sidebar and vd.options.disp_sidebar and vd.disp_help >= 0:
|
115
|
+
sidebar, title = sheet.help_sidebars[vd.disp_help%len(sheet.help_sidebars)]()
|
116
|
+
|
117
|
+
# bottommsg = sheet.formatString('[:onclick sidebar-toggle][:reverse] {help.commands.sidebar_toggle} [:]', help=sheet.formatter_helpstr)
|
118
|
+
# overflowmsg = '[:reverse] see full sidebar with [:code]gb[/] [:]'
|
119
|
+
|
120
|
+
overflowmsg = '[:onclick open-sidebar]…↓…[/]'
|
68
121
|
except Exception as e:
|
69
122
|
vd.exceptionCaught(e)
|
70
123
|
sidebar = f'# error\n{e}'
|
71
124
|
|
72
125
|
sheet.current_sidebar = sidebar
|
73
126
|
|
74
|
-
return sheet.drawSidebarText(scr, text=sheet.current_sidebar, overflowmsg=overflowmsg, bottommsg=bottommsg)
|
127
|
+
return sheet.drawSidebarText(scr, text=sheet.current_sidebar, title=title, overflowmsg=overflowmsg, bottommsg=bottommsg)
|
75
128
|
|
76
129
|
@BaseSheet.api
|
77
130
|
def drawSidebarText(sheet, scr, text:Union[None,str,'HelpPane'], title:str='', overflowmsg:str='', bottommsg:str=''):
|
@@ -136,7 +189,7 @@ def drawSidebarText(sheet, scr, text:Union[None,str,'HelpPane'], title:str='', o
|
|
136
189
|
x = max(0, w-titlew-6)
|
137
190
|
clipdraw(sidebarscr, 0, x, f"|[:sidebar_title] {title} [:]|", cattr, w=titlew+4)
|
138
191
|
if bottommsg:
|
139
|
-
clipdraw(sidebarscr, h-1, winw-dispwidth(bottommsg)-4, '|'+bottommsg+'|
|
192
|
+
clipdraw(sidebarscr, h-1, winw-dispwidth(bottommsg)-4, '|'+bottommsg+'|', cattr)
|
140
193
|
|
141
194
|
sidebarscr.refresh()
|
142
195
|
|
@@ -152,11 +205,13 @@ class SidebarSheet(TextSheet):
|
|
152
205
|
- `b` to toggle the sidebar on/off for the current sheet
|
153
206
|
'''
|
154
207
|
|
155
|
-
BaseSheet.addCommand('b', 'sidebar-toggle', '
|
208
|
+
BaseSheet.addCommand('b', 'sidebar-toggle', 'vd.options.disp_sidebar = not vd.options.disp_sidebar', 'toggle sidebar')
|
156
209
|
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')
|
210
|
+
BaseSheet.addCommand('^G', 'sidebar-cycle', 'vd.cycleSidebar()', 'cycle through available sidebar panels')
|
157
211
|
|
158
212
|
|
159
213
|
vd.addMenuItems('''
|
160
214
|
View > Sidebar > toggle > sidebar-toggle
|
215
|
+
View > Sidebar > cycle > sidebar-cycle
|
161
216
|
View > Sidebar > open in new sheet > open-sidebar
|
162
217
|
''')
|
visidata/sort.py
CHANGED
@@ -35,18 +35,27 @@ class Reversor:
|
|
35
35
|
return other.obj < self.obj
|
36
36
|
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
|
39
|
+
@Sheet.cached_property
|
40
|
+
def ordering(sheet) -> 'list[tuple[Column, bool]]':
|
40
41
|
ret = []
|
41
|
-
for col, reverse in
|
42
|
+
for col, reverse in sheet._ordering:
|
42
43
|
if isinstance(col, str):
|
43
|
-
col =
|
44
|
+
col = sheet.column(col)
|
45
|
+
ret.append((col, reverse))
|
46
|
+
return ret
|
47
|
+
|
48
|
+
|
49
|
+
@Sheet.api
|
50
|
+
def sortkey(sheet, r, ordering:'list[tuple[Column, bool]]'=[]):
|
51
|
+
ret = []
|
52
|
+
for col, reverse in (ordering or sheet.ordering):
|
44
53
|
val = col.getTypedValue(r)
|
45
54
|
ret.append(Reversor(val) if reverse else val)
|
46
55
|
|
47
|
-
|
48
56
|
return ret
|
49
57
|
|
58
|
+
|
50
59
|
@Sheet.api
|
51
60
|
@asyncthread
|
52
61
|
def sort(self):
|
@@ -55,8 +64,14 @@ def sort(self):
|
|
55
64
|
return
|
56
65
|
try:
|
57
66
|
with Progress(gerund='sorting', total=self.nRows) as prog:
|
67
|
+
# replace ambiguous colname strings with unambiguous Column objects #2494
|
68
|
+
self._ordering = self.ordering
|
69
|
+
def _sortkey(r):
|
70
|
+
prog.addProgress(1)
|
71
|
+
return self.sortkey(r, ordering=self._ordering)
|
72
|
+
|
58
73
|
# must not reassign self.rows: use .sort() instead of sorted()
|
59
|
-
self.rows.sort(key=
|
74
|
+
self.rows.sort(key=_sortkey)
|
60
75
|
except TypeError as e:
|
61
76
|
vd.warning('sort incomplete due to TypeError; change column type')
|
62
77
|
vd.exceptionCaught(e, status=False)
|
visidata/statusbar.py
CHANGED
@@ -11,14 +11,16 @@ import visidata
|
|
11
11
|
from visidata import vd, VisiData, BaseSheet, Sheet, ColumnItem, Column, RowColorizer, options, colors, wrmap, clipdraw, ExpectedException, update_attr, dispwidth, ColorAttr
|
12
12
|
|
13
13
|
|
14
|
-
|
15
|
-
vd.option('
|
14
|
+
|
15
|
+
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')
|
16
|
+
vd.option('disp_status_fmt', '{sheet.sheetlist}| ', 'left-side status format string')
|
16
17
|
vd.theme_option('disp_lstatus_max', 0, 'maximum length of left status line')
|
17
18
|
vd.theme_option('disp_status_sep', '│', 'separator between statuses')
|
18
19
|
|
19
20
|
vd.theme_option('color_keystrokes', 'bold white on 237', 'color of input keystrokes')
|
20
|
-
vd.theme_option('
|
21
|
-
vd.theme_option('
|
21
|
+
vd.theme_option('color_longname_guide', '237', 'color of command longnames')
|
22
|
+
vd.theme_option('color_longname_status', 'white', 'color of command longnames')
|
23
|
+
vd.theme_option('color_keys', 'bold reverse', 'color of keystrokes in help')
|
22
24
|
vd.theme_option('color_status', 'bold on 238', 'status line color')
|
23
25
|
vd.theme_option('color_error', '202 1', 'error message color')
|
24
26
|
vd.theme_option('color_warning', '166 15', 'warning message color')
|
@@ -29,6 +31,41 @@ vd.theme_option('color_highlight_status', 'black on green', 'color of highlighte
|
|
29
31
|
|
30
32
|
BaseSheet.init('longname', lambda: '')
|
31
33
|
|
34
|
+
def fitWithin(s, n=10):
|
35
|
+
if len(s) > n:
|
36
|
+
return s[:n//2-1] + '…' + s[-n//2+1:]
|
37
|
+
return s
|
38
|
+
|
39
|
+
@BaseSheet.property
|
40
|
+
def ancestors(sheet):
|
41
|
+
if isinstance(sheet.source, BaseSheet):
|
42
|
+
return sheet.source.ancestors + [sheet.source]
|
43
|
+
else:
|
44
|
+
return []
|
45
|
+
|
46
|
+
@BaseSheet.property
|
47
|
+
def sheetlist(sheet):
|
48
|
+
leafsheets = []
|
49
|
+
parents = set()
|
50
|
+
|
51
|
+
sheetstack = vd.sheetstack(sheet.pane)
|
52
|
+
sheets = [x for x in vd.allSheets if x in sheetstack]+ [x for x in sheetstack if x not in vd.allSheets]
|
53
|
+
|
54
|
+
sheetnames = []
|
55
|
+
for vs in sheets:
|
56
|
+
if isinstance(vs, BaseSheet):
|
57
|
+
shortcut = ' '
|
58
|
+
if vs.shortcut in '1 2 3 4 5 6 7 8 9 10'.split():
|
59
|
+
shortcut = vs.shortcut[-1] + '›'
|
60
|
+
if vs is vd.sheet:
|
61
|
+
sheetnames.append(f'[:menu_active]{shortcut}{vs.name}[:]')
|
62
|
+
else:
|
63
|
+
sheetnames.append(f'[:onclick jump-sheet-{vs.shortcut}]' + fitWithin(f'{shortcut}{vs.name}', 20) + '[:]')
|
64
|
+
else:
|
65
|
+
sheetnames.append(vs)
|
66
|
+
|
67
|
+
return ' | '.join(sheetnames)
|
68
|
+
|
32
69
|
@BaseSheet.api
|
33
70
|
def _updateStatusBeforeExec(sheet, cmd, args, ks):
|
34
71
|
sheet.longname = cmd.longname
|
@@ -223,7 +260,7 @@ class StatusSheet(Sheet):
|
|
223
260
|
ColumnItem('nrepeats', 2, type=int, width=0),
|
224
261
|
ColumnItem('args', 1, width=0),
|
225
262
|
Column('message', width=50, getter=lambda col,row: composeStatus(row[1], row[2])),
|
226
|
-
ColumnItem('source', 3,
|
263
|
+
ColumnItem('source', 3, width=0),
|
227
264
|
]
|
228
265
|
colorizers = [
|
229
266
|
RowColorizer(1, 'color_error', lambda s,c,r,v: r and r[0] == 3),
|
visidata/stored_list.py
CHANGED
@@ -13,8 +13,11 @@ class StoredList(list):
|
|
13
13
|
@property
|
14
14
|
def path(self):
|
15
15
|
vdpath = Path(vd.options.visidata_dir)
|
16
|
-
if vdpath.exists():
|
17
|
-
|
16
|
+
if not vdpath.exists():
|
17
|
+
if vd.options.nothing:
|
18
|
+
return
|
19
|
+
vdpath.mkdir(parents=True)
|
20
|
+
return vdpath/(self.name + '.jsonl')
|
18
21
|
|
19
22
|
def reload(self):
|
20
23
|
p = self.path
|
visidata/tests/conftest.py
CHANGED
visidata/tests/test_commands.py
CHANGED
@@ -56,6 +56,7 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
|
|
56
56
|
'exec-python': 'import time',
|
57
57
|
'unselect-cols-regex': '.',
|
58
58
|
'go-col-regex': 'Units', # column name in sample
|
59
|
+
'go-col-name': 'Units', # column name in sample
|
59
60
|
'go-col-number': '2',
|
60
61
|
'go-row-number': '5', # go to row 5
|
61
62
|
'addcol-bulk': '1',
|
@@ -67,6 +68,7 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
|
|
67
68
|
'setcol-incr-step': '2',
|
68
69
|
'setcol-iter': 'range(1, 100)',
|
69
70
|
'setcol-format-enum': '1=cat',
|
71
|
+
'open-ping': 'github.com',
|
70
72
|
'setcol-input': '5',
|
71
73
|
'show-expr': 'OrderDate',
|
72
74
|
'setcol-expr': 'OrderDate',
|
@@ -92,7 +94,8 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
|
|
92
94
|
'expand-cols-depth': '0',
|
93
95
|
'save-cmdlog': 'test_commands.vdj',
|
94
96
|
'aggregate-col': 'mean',
|
95
|
-
'memo-aggregate': '
|
97
|
+
'memo-aggregate': 'count',
|
98
|
+
'memo-cell': 'memoname',
|
96
99
|
'addcol-shell': '',
|
97
100
|
'theme-input': 'light',
|
98
101
|
'add-rows': '1',
|
@@ -170,12 +173,17 @@ class TestCommands:
|
|
170
173
|
|
171
174
|
sample_file = vd.pkg_resources_files(visidata) / 'tests/sample.tsv'
|
172
175
|
vs = visidata.TsvSheet('test_commands', source=visidata.Path(sample_file))
|
176
|
+
cmd = vs.getCommand(longname)
|
177
|
+
if not cmd:
|
178
|
+
vd.warning(f'command cannot be tested on TsvSheet, skipping: {longname}')
|
179
|
+
return
|
173
180
|
vs.reload.__wrapped__(vs)
|
174
181
|
vs.vd = vd
|
175
182
|
vd.sheets = [vs]
|
176
183
|
vd.allSheets = [vs]
|
177
184
|
vs.mouseX, vs.mouseY = (4, 4)
|
178
185
|
vs.draw(mock_screen)
|
186
|
+
vs._scr = mock_screen
|
179
187
|
if longname in inputLines:
|
180
188
|
vd.currentReplayRow = vd.cmdlog.newRow(longname=longname, input=inputLines[longname])
|
181
189
|
else:
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import pytest
|
2
|
+
import visidata
|
3
|
+
|
4
|
+
class TestCompleteExpr:
|
5
|
+
def test_completer(self):
|
6
|
+
vs = visidata.DirSheet('test', source=visidata.Path('.'))
|
7
|
+
vs.reload()
|
8
|
+
cexpr = visidata.CompleteExpr(vs)
|
9
|
+
assert cexpr('fi', 0) == 'filename' # visible column first
|
10
|
+
assert cexpr('fi', 1) == 'filetype' # hidden column second
|
11
|
+
assert cexpr('logn', 0) == 'lognormvariate' # global from math
|
12
|
+
assert cexpr('a+logn', 0) == 'a+lognormvariate'
|
13
|
+
|
14
|
+
assert cexpr('testv', 0) == 'testv' # no match returns same
|
15
|
+
|
16
|
+
visidata.vd.memoValue('testvalue', 42, '42')
|
17
|
+
cexpr = visidata.CompleteExpr(vs)
|
18
|
+
assert cexpr('testv', 0) == 'testvalue'
|
visidata/tests/test_edittext.py
CHANGED
@@ -46,9 +46,10 @@ class TestEditText:
|
|
46
46
|
self.chars.extend(keys.split())
|
47
47
|
|
48
48
|
exception = kwargs.pop('exception', None)
|
49
|
+
widget = visidata.InputWidget(**kwargs)
|
49
50
|
if exception:
|
50
51
|
with pytest.raises(exception):
|
51
|
-
|
52
|
+
widget.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr())
|
52
53
|
else:
|
53
|
-
r =
|
54
|
+
r = widget.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr())
|
54
55
|
assert r == result
|
visidata/text_source.py
CHANGED
@@ -2,7 +2,7 @@ import re
|
|
2
2
|
|
3
3
|
from visidata import vd, BaseSheet
|
4
4
|
|
5
|
-
vd.option('regex_skip', '', 'regex of lines to skip in text sources', help='regex')
|
5
|
+
vd.option('regex_skip', '', 'regex of lines to skip in text sources', help='regex', replay=True)
|
6
6
|
vd.option('regex_flags', 'I', 'flags to pass to re.compile() [AILMSUX]', replay=True)
|
7
7
|
|
8
8
|
@BaseSheet.api
|
@@ -44,9 +44,12 @@ class FilterFile:
|
|
44
44
|
|
45
45
|
|
46
46
|
@BaseSheet.api
|
47
|
-
def open_text_source(sheet):
|
48
|
-
'Open sheet source as text,
|
49
|
-
|
47
|
+
def open_text_source(sheet, **kwargs):
|
48
|
+
'Open sheet source as text, passing **kwargs to .open() (default to sheet options for encoding and regex_skip).'
|
49
|
+
openkwargs = dict(encoding=sheet.options.encoding,
|
50
|
+
encoding_errors=sheet.options.encoding_errors)
|
51
|
+
openkwargs.update(kwargs)
|
52
|
+
fp = sheet.source.open(**openkwargs)
|
50
53
|
regex_skip = sheet.options.regex_skip
|
51
54
|
if regex_skip:
|
52
55
|
return FilterFile(fp, regex_skip, sheet.regex_flags())
|
visidata/textsheet.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
import textwrap
|
2
2
|
|
3
3
|
from visidata import vd, BaseSheet, options, Sheet, ColumnItem, asyncthread
|
4
|
-
from visidata import Column,
|
4
|
+
from visidata import Column, vlen
|
5
5
|
from visidata import globalCommand, VisiData
|
6
6
|
import visidata
|
7
7
|
|
8
8
|
|
9
|
-
vd.option('wrap', False, 'wrap text to fit window width on TextSheet'
|
9
|
+
vd.option('wrap', False, 'wrap text to fit window width on TextSheet')
|
10
10
|
vd.option('save_filetype', 'tsv', 'specify default file type to save as', replay=True)
|
11
11
|
|
12
12
|
|
@@ -32,7 +32,7 @@ class TextSheet(Sheet):
|
|
32
32
|
for i, L in enumerate(textwrap.wrap(str(text), width=winWidth)):
|
33
33
|
yield [startingLine+i+1, L]
|
34
34
|
else:
|
35
|
-
yield [startingLine+1, text
|
35
|
+
yield [startingLine+1, text]
|
36
36
|
|
37
37
|
def sysopen(sheet, linenum=0):
|
38
38
|
@asyncthread
|
@@ -55,8 +55,23 @@ class TextSheet(Sheet):
|
|
55
55
|
# .source is list of source text lines to 'load'
|
56
56
|
# .sourceSheet is Sheet error came from
|
57
57
|
class ErrorSheet(TextSheet):
|
58
|
+
columns = [
|
59
|
+
ColumnItem('linenum', 0, type=int, width=0),
|
60
|
+
ColumnItem('error', 1),
|
61
|
+
]
|
62
|
+
guide = '''# Error Sheet'''
|
58
63
|
precious = False
|
59
64
|
|
65
|
+
class ErrorCellSheet(ErrorSheet):
|
66
|
+
columns = [
|
67
|
+
ColumnItem('linenum', 0, type=int, width=0),
|
68
|
+
ColumnItem('cell_error', 1),
|
69
|
+
]
|
70
|
+
guide = '''# Error Cell Sheet
|
71
|
+
This sheet shows the error that occurred when calculating a cell.
|
72
|
+
- `q` to quit this error sheet.
|
73
|
+
'''
|
74
|
+
|
60
75
|
|
61
76
|
class ErrorsSheet(Sheet):
|
62
77
|
columns = [
|
@@ -83,15 +98,14 @@ def recentErrorsSheet(self):
|
|
83
98
|
BaseSheet.addCommand('^E', 'error-recent', 'vd.lastErrors and vd.push(recentErrorsSheet) or status("no error")', 'view traceback for most recent error')
|
84
99
|
BaseSheet.addCommand('g^E', 'errors-all', 'vd.push(vd.allErrorsSheet)', 'view traceback for most recent errors')
|
85
100
|
|
86
|
-
Sheet.addCommand(
|
87
|
-
Sheet.addCommand('z^E', 'error-cell', 'vd.push(ErrorSheet(sheet.name+"_cell_error", sourceSheet=sheet, source=getattr(cursorCell, "error", None) or fail("no error this cell")))', 'view traceback for error in current cell')
|
101
|
+
Sheet.addCommand('z^E', 'error-cell', 'vd.push(ErrorCellSheet(sheet.name+"_cell_error", sourceSheet=sheet, source=getattr(cursorCell, "error", None) or fail("no error this cell")))', 'view traceback for error in current cell')
|
88
102
|
|
89
103
|
TextSheet.addCommand('^O', 'sysopen-sheet', 'sheet.sysopen(sheet.cursorRowIndex)', 'open copy of text sheet in $EDITOR and reload on exit')
|
90
104
|
|
91
105
|
|
92
106
|
TextSheet.options.save_filetype = 'txt'
|
93
107
|
|
94
|
-
vd.addGlobals({'TextSheet': TextSheet, 'ErrorSheet': ErrorSheet})
|
108
|
+
vd.addGlobals({'TextSheet': TextSheet, 'ErrorSheet': ErrorSheet, 'ErrorCellSheet': ErrorCellSheet})
|
95
109
|
|
96
110
|
vd.addMenuItems('''
|
97
111
|
View > Errors > recent > error-recent
|
visidata/themes/ascii8.py
CHANGED
@@ -13,10 +13,10 @@ vd.themes['ascii8'] = dict(
|
|
13
13
|
disp_ambig_width=1,
|
14
14
|
|
15
15
|
disp_pending='',
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
disp_note_pending=':',
|
17
|
+
disp_note_fmtexc='?',
|
18
|
+
disp_note_getexc='!',
|
19
|
+
disp_note_typeexc='!',
|
20
20
|
|
21
21
|
color_note_pending='bold magenta',
|
22
22
|
color_note_type='yellow',
|
@@ -68,7 +68,8 @@ vd.themes['ascii8'] = dict(
|
|
68
68
|
color_top_status='underline',
|
69
69
|
color_active_status='black on cyan',
|
70
70
|
color_inactive_status='8 on black',
|
71
|
-
color_working='
|
71
|
+
color_working='bold white',
|
72
|
+
color_longname_status='black on cyan',
|
72
73
|
|
73
74
|
color_menu='black on cyan',
|
74
75
|
color_menu_active='yellow on black',
|
@@ -80,5 +81,7 @@ vd.themes['ascii8'] = dict(
|
|
80
81
|
disp_menu_input='_',
|
81
82
|
disp_menu_fmt='7-bit ASCII 3-bit color',
|
82
83
|
plot_colors = 'white',
|
83
|
-
disp_histogram='*'
|
84
|
+
disp_histogram='*',
|
85
|
+
disp_graph_lines_x_charset='||||',
|
86
|
+
disp_graph_lines_y_charset='----'
|
84
87
|
)
|
visidata/themes/asciimono.py
CHANGED
@@ -13,10 +13,10 @@ vd.themes['asciimono'] = dict(
|
|
13
13
|
disp_ambig_width=1,
|
14
14
|
|
15
15
|
disp_pending='',
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
disp_note_pending=':',
|
17
|
+
disp_note_fmtexc='?',
|
18
|
+
disp_note_getexc='!',
|
19
|
+
disp_note_typeexc='!',
|
20
20
|
|
21
21
|
color_note_pending='bold',
|
22
22
|
color_note_type='',
|
@@ -48,9 +48,13 @@ vd.themes['asciimono'] = dict(
|
|
48
48
|
color_hidden_col='8',
|
49
49
|
color_selected_row='',
|
50
50
|
color_edit_cell='',
|
51
|
+
color_edit_unfocused='',
|
51
52
|
color_graph_hidden='',
|
52
53
|
color_graph_selected='bold',
|
53
54
|
color_status_replay='',
|
55
|
+
color_currency_neg='',
|
56
|
+
color_match='',
|
57
|
+
color_cmdpalette='',
|
54
58
|
|
55
59
|
color_graph_axis='bold',
|
56
60
|
color_sidebar='reverse',
|
@@ -69,6 +73,12 @@ vd.themes['asciimono'] = dict(
|
|
69
73
|
color_active_status='reverse',
|
70
74
|
color_inactive_status='8',
|
71
75
|
color_working='',
|
76
|
+
color_longname='',
|
77
|
+
color_highlight_status='',
|
78
|
+
color_sidebar_title='',
|
79
|
+
color_heading='',
|
80
|
+
color_guide_unwritten='',
|
81
|
+
color_code='',
|
72
82
|
|
73
83
|
color_menu='reverse',
|
74
84
|
color_menu_active='',
|