visidata 3.0.2__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 -202
- visidata/_open.py +4 -1
- visidata/_types.py +4 -3
- visidata/aggregators.py +88 -39
- visidata/apps/vdsql/_ibis.py +7 -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 +54 -20
- 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 +17 -2
- 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 +16 -3
- visidata/features/ping.py +16 -12
- visidata/features/regex.py +5 -5
- 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 +163 -12
- visidata/guide.py +57 -24
- 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 +14 -13
- visidata/mainloop.py +14 -6
- visidata/man/vd.1 +72 -39
- visidata/man/vd.txt +72 -41
- visidata/memory.py +15 -4
- 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/save.py +16 -9
- visidata/search.py +4 -4
- visidata/selection.py +10 -56
- visidata/settings.py +37 -35
- visidata/sheets.py +186 -108
- visidata/shell.py +22 -12
- 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 -5
- visidata/utils.py +9 -3
- visidata/vdobj.py +21 -1
- visidata/wrappers.py +9 -1
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/applications/visidata.desktop +2 -2
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/man/man1/vd.1 +72 -39
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/man/man1/visidata.1 +72 -39
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/METADATA +24 -6
- visidata-3.1.dist-info/RECORD +284 -0
- visidata-3.0.2.dist-info/RECORD +0 -258
- {visidata-3.0.2.data → visidata-3.1.data}/scripts/vd +0 -0
- {visidata-3.0.2.data → visidata-3.1.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/WHEEL +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/entry_points.txt +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/top_level.txt +0 -0
visidata/sheets.py
CHANGED
@@ -4,8 +4,8 @@ from copy import copy, deepcopy
|
|
4
4
|
import textwrap
|
5
5
|
|
6
6
|
from visidata import VisiData, Extensible, globalCommand, ColumnAttr, ColumnItem, vd, ENTER, EscapeException, drawcache, drawcache_property, LazyChainMap, asyncthread, ExpectedException
|
7
|
-
from visidata import (options, Column, namedlist, SettableColumn,
|
8
|
-
TypedExceptionWrapper, BaseSheet, UNLOADED,
|
7
|
+
from visidata import (options, Column, namedlist, SettableColumn, AttrDict, DisplayWrapper,
|
8
|
+
TypedExceptionWrapper, BaseSheet, UNLOADED, wrapply,
|
9
9
|
clipdraw, clipdraw_chunks, ColorAttr, update_attr, colors, undoAttrFunc, vlen, dispwidth)
|
10
10
|
import visidata
|
11
11
|
|
@@ -13,20 +13,30 @@ import visidata
|
|
13
13
|
vd.activePane = 1 # pane numbering starts at 1; pane 0 means active pane
|
14
14
|
|
15
15
|
|
16
|
-
vd.option('name_joiner', '_', 'string to join sheet or column names'
|
17
|
-
vd.option('value_joiner', ' ', 'string to join display values'
|
16
|
+
vd.option('name_joiner', '_', 'string to join sheet or column names')
|
17
|
+
vd.option('value_joiner', ' ', 'string to join display values')
|
18
|
+
vd.option('max_rows', 1_000_000_000, 'number of rows to load from source')
|
19
|
+
|
20
|
+
vd.option('disp_wrap_max_lines', 3, 'max lines for multiline view')
|
21
|
+
vd.option('disp_wrap_break_long_words', False, 'break words longer than column width in multiline')
|
22
|
+
vd.option('disp_wrap_replace_whitespace', False, 'replace whitespace with spaces in multiline')
|
23
|
+
vd.option('disp_wrap_placeholder', '…', 'multiline string to indicate truncation')
|
24
|
+
vd.option('disp_multiline_focus', True, 'only multiline cursor row')
|
25
|
+
vd.option('color_aggregator', 'bold 255 white on 234 black', 'color of aggregator summary on bottom row')
|
18
26
|
|
19
27
|
|
20
28
|
@drawcache
|
21
29
|
def _splitcell(sheet, s, width=0, maxheight=1):
|
22
|
-
|
30
|
+
height = max(maxheight, sheet.options.disp_wrap_max_lines or 0)
|
31
|
+
if width <= 0 or height <= 0:
|
23
32
|
return [s]
|
24
33
|
|
34
|
+
wrap_kwargs = sheet.options.getall('disp_wrap_')
|
35
|
+
wrap_kwargs['max_lines'] = height
|
36
|
+
|
25
37
|
ret = []
|
26
38
|
for attr, text in s:
|
27
|
-
for line in textwrap.wrap(
|
28
|
-
text, width=width, break_long_words=False, replace_whitespace=False
|
29
|
-
):
|
39
|
+
for line in textwrap.wrap(text, width=width, **wrap_kwargs):
|
30
40
|
if len(ret) >= maxheight:
|
31
41
|
ret[-1][0][1] += ' ' + line
|
32
42
|
break
|
@@ -34,26 +44,42 @@ def _splitcell(sheet, s, width=0, maxheight=1):
|
|
34
44
|
ret.append([[attr, line]])
|
35
45
|
return ret
|
36
46
|
|
37
|
-
disp_column_fill = ' ' # pad chars
|
47
|
+
disp_column_fill = ' ' # pad chars before column value
|
48
|
+
|
49
|
+
class Colorizer:
|
50
|
+
'''higher precedence color overrides lower; all non-color attributes combine.
|
51
|
+
coloropt is the color option name (like 'color_error').
|
52
|
+
func(sheet,col,row,value) should return a true value if coloropt should be applied
|
53
|
+
If coloropt is None, func() should return a coloropt (or None) instead'''
|
54
|
+
|
55
|
+
def __init__(self, precedence:int, coloropt:str, func=lambda s,c,r,v: None):
|
56
|
+
self.precedence = precedence
|
57
|
+
self.coloropt = coloropt
|
58
|
+
self._func = func
|
59
|
+
|
60
|
+
class RowColorizer(Colorizer):
|
61
|
+
def func(self, s, c, r, v):
|
62
|
+
return r is not None and self._func(s,c,r,v)
|
63
|
+
|
64
|
+
class ColumnColorizer(Colorizer):
|
65
|
+
def func(self, s, c, r, v):
|
66
|
+
return c is not None and self._func(s,c,r,v)
|
67
|
+
|
68
|
+
class CellColorizer(Colorizer):
|
69
|
+
def func(self, s, c, r, v):
|
70
|
+
return r is not None and c is not None and self._func(s,c,r,v)
|
38
71
|
|
39
|
-
# higher precedence color overrides lower; all non-color attributes combine
|
40
|
-
# coloropt is the color option name (like 'color_error')
|
41
|
-
# func(sheet,col,row,value) should return a true value if coloropt should be applied
|
42
|
-
# if coloropt is None, func() should return a coloropt (or None) instead
|
43
|
-
Colorizer = collections.namedtuple('Colorizer', 'precedence coloropt func')
|
44
|
-
RowColorizer = collections.namedtuple('RowColorizer', 'precedence coloropt func')
|
45
|
-
CellColorizer = collections.namedtuple('CellColorizer', 'precedence coloropt func')
|
46
|
-
ColumnColorizer = collections.namedtuple('ColumnColorizer', 'precedence coloropt func')
|
47
72
|
|
48
73
|
class RecursiveExprException(Exception):
|
49
74
|
pass
|
50
75
|
|
51
76
|
class LazyComputeRow:
|
52
77
|
'Calculate column values as needed.'
|
53
|
-
def __init__(self, sheet, row, col=None):
|
78
|
+
def __init__(self, sheet, row, col=None, **kwargs):
|
54
79
|
self.row = row
|
55
80
|
self.col = col
|
56
81
|
self.sheet = sheet
|
82
|
+
self.extra = AttrDict(kwargs) # extra bindings
|
57
83
|
self._usedcols = set()
|
58
84
|
|
59
85
|
self._lcm.clear() # reset locals on lcm
|
@@ -62,11 +88,11 @@ class LazyComputeRow:
|
|
62
88
|
def _lcm(self):
|
63
89
|
lcmobj = self.col or self.sheet
|
64
90
|
if not hasattr(lcmobj, '_lcm'):
|
65
|
-
lcmobj._lcm = LazyChainMap(self.sheet, self.col, *vd.contexts)
|
91
|
+
lcmobj._lcm = LazyChainMap(self.sheet, self.col, self.extra, *vd.contexts)
|
66
92
|
return lcmobj._lcm
|
67
93
|
|
68
94
|
def __iter__(self):
|
69
|
-
yield from self.sheet.
|
95
|
+
yield from self.sheet.availColnames
|
70
96
|
yield from self._lcm.keys()
|
71
97
|
yield 'row'
|
72
98
|
yield 'sheet'
|
@@ -86,18 +112,19 @@ class LazyComputeRow:
|
|
86
112
|
|
87
113
|
def __getitem__(self, colid):
|
88
114
|
try:
|
89
|
-
i = self.sheet.
|
90
|
-
c = self.sheet.
|
115
|
+
i = self.sheet.availColnames.index(colid)
|
116
|
+
c = self.sheet.availCols[i]
|
91
117
|
if c is self.col: # ignore current column
|
92
|
-
j = self.sheet.
|
93
|
-
c = self.sheet.
|
118
|
+
j = self.sheet.availColnames[i+1:].index(colid)
|
119
|
+
c = self.sheet.availCols[i+j+1]
|
94
120
|
|
95
121
|
except ValueError:
|
96
122
|
try:
|
97
123
|
c = self._lcm[colid]
|
98
124
|
except (KeyError, AttributeError) as e:
|
99
125
|
if colid == 'sheet': return self.sheet
|
100
|
-
elif colid == 'row':
|
126
|
+
elif colid == 'row': return self
|
127
|
+
elif colid == '_row': return self.row
|
101
128
|
elif colid == 'col': c = self.col
|
102
129
|
else:
|
103
130
|
raise KeyError(colid) from e
|
@@ -125,7 +152,7 @@ class TableSheet(BaseSheet):
|
|
125
152
|
_coltype = SettableColumn
|
126
153
|
|
127
154
|
rowtype = 'rows'
|
128
|
-
guide = '# {sheet.help_title}\n
|
155
|
+
guide = '# {sheet.help_title}\n'
|
129
156
|
|
130
157
|
@property
|
131
158
|
def help_title(self):
|
@@ -134,13 +161,6 @@ class TableSheet(BaseSheet):
|
|
134
161
|
else:
|
135
162
|
return 'Table Sheet'
|
136
163
|
|
137
|
-
@property
|
138
|
-
def help_columns(self):
|
139
|
-
hiddenCols = [c for c in self.columns if c.hidden]
|
140
|
-
if hiddenCols:
|
141
|
-
return f'- `gv` to unhide {len(hiddenCols)} hidden columns'
|
142
|
-
return ''
|
143
|
-
|
144
164
|
columns = [] # list of Column
|
145
165
|
colorizers = [ # list of Colorizer
|
146
166
|
CellColorizer(2, 'color_default_hdr', lambda s,c,r,v: r is None),
|
@@ -148,7 +168,8 @@ class TableSheet(BaseSheet):
|
|
148
168
|
ColumnColorizer(1, 'color_key_col', lambda s,c,r,v: c and c.keycol),
|
149
169
|
CellColorizer(0, 'color_default', lambda s,c,r,v: True),
|
150
170
|
RowColorizer(1, 'color_error', lambda s,c,r,v: isinstance(r, (Exception, TypedExceptionWrapper))),
|
151
|
-
CellColorizer(3, 'color_current_cell', lambda s,c,r,v: c is s.cursorCol and r is s.cursorRow)
|
171
|
+
CellColorizer(3, 'color_current_cell', lambda s,c,r,v: c is s.cursorCol and r is s.cursorRow),
|
172
|
+
ColumnColorizer(1, 'color_hidden_col', lambda s,c,r,v: c and c.hidden),
|
152
173
|
]
|
153
174
|
nKeys = 0 # columns[:nKeys] are key columns
|
154
175
|
_ordering = [] # list of (col:Column|str, reverse:bool)
|
@@ -272,7 +293,7 @@ class TableSheet(BaseSheet):
|
|
272
293
|
self.columns = []
|
273
294
|
for c in self.initialCols:
|
274
295
|
self.addColumn(deepcopy(c))
|
275
|
-
if self.options.
|
296
|
+
if self.options.disp_expert < c.disp_expert:
|
276
297
|
c.hide()
|
277
298
|
|
278
299
|
self.setKeys(self.columns[:self.nKeys])
|
@@ -282,7 +303,9 @@ class TableSheet(BaseSheet):
|
|
282
303
|
self.rows = []
|
283
304
|
try:
|
284
305
|
with vd.Progress(gerund='loading', total=0):
|
285
|
-
for r in self.iterload():
|
306
|
+
for i, r in enumerate(self.iterload()):
|
307
|
+
if self.precious and i > self.options.max_rows:
|
308
|
+
break
|
286
309
|
self.addRow(r)
|
287
310
|
except FileNotFoundError:
|
288
311
|
return # let it be a blank sheet without error
|
@@ -367,10 +390,15 @@ class TableSheet(BaseSheet):
|
|
367
390
|
def __repr__(self):
|
368
391
|
return f'<{type(self).__name__}: {self.name}>'
|
369
392
|
|
370
|
-
|
393
|
+
@drawcache_property
|
394
|
+
def currow(self):
|
395
|
+
return LazyComputeRow(self, self.cursorRow, self.cursorCol)
|
396
|
+
|
397
|
+
def evalExpr(self, expr:str, row=None, col=None, **kwargs):
|
398
|
+
'eval() expr in the context of (row, col), with extra bindings in kwargs'
|
371
399
|
if row is not None:
|
372
400
|
# contexts are cached by sheet/rowid for duration of drawcycle
|
373
|
-
contexts = vd._evalcontexts.setdefault((self, self.rowid(row), col), LazyComputeRow(self, row, col
|
401
|
+
contexts = vd._evalcontexts.setdefault((self, self.rowid(row), col), LazyComputeRow(self, row, col, **kwargs))
|
374
402
|
else:
|
375
403
|
contexts = dict(sheet=self)
|
376
404
|
|
@@ -383,22 +411,25 @@ class TableSheet(BaseSheet):
|
|
383
411
|
@property
|
384
412
|
def nScreenRows(self):
|
385
413
|
'Number of visible rows at the current window height.'
|
386
|
-
|
414
|
+
n = (self.windowHeight-self.nHeaderRows-self.nFooterRows)
|
415
|
+
if self.options.disp_multiline_focus: # focus multiline mode
|
416
|
+
return n-self.rowHeight+1
|
417
|
+
return n//self.rowHeight
|
387
418
|
|
388
419
|
@drawcache_property
|
389
420
|
def nHeaderRows(self):
|
390
421
|
vcols = self.visibleCols
|
391
|
-
return max(len(col.name.split('\n')) for col in vcols)
|
422
|
+
return max(0, 1, *(len(col.name.split('\n')) for col in vcols))
|
392
423
|
|
393
424
|
@property
|
394
425
|
def nFooterRows(self):
|
395
426
|
'Number of lines reserved at the bottom, including status line.'
|
396
|
-
return 1
|
427
|
+
return len(self.allAggregators) + 1
|
397
428
|
|
398
429
|
@property
|
399
430
|
def cursorCol(self):
|
400
431
|
'Current Column object.'
|
401
|
-
vcols = self.
|
432
|
+
vcols = self.availCols
|
402
433
|
return vcols[min(self.cursorVisibleColIndex, len(vcols)-1)] if vcols else None
|
403
434
|
|
404
435
|
@property
|
@@ -415,7 +446,7 @@ class TableSheet(BaseSheet):
|
|
415
446
|
@drawcache_property
|
416
447
|
def visibleCols(self): # non-hidden cols
|
417
448
|
'List of non-hidden columns in display order.'
|
418
|
-
return self.keyCols + [c for c in self.columns if not c.hidden and not c.keycol]
|
449
|
+
return (self.keyCols + [c for c in self.columns if not c.hidden and not c.keycol]) or [Column('', sheet=self)]
|
419
450
|
|
420
451
|
@drawcache_property
|
421
452
|
def keyCols(self):
|
@@ -423,14 +454,14 @@ class TableSheet(BaseSheet):
|
|
423
454
|
return sorted([c for c in self.columns if c.keycol and not c.hidden], key=lambda c:c.keycol)
|
424
455
|
|
425
456
|
@drawcache_property
|
426
|
-
def
|
427
|
-
'List of all columns, visible columns first.'
|
457
|
+
def availCols(self):
|
458
|
+
'List of all available columns, visible columns first.'
|
428
459
|
return self.visibleCols + [c for c in self.columns if c.hidden]
|
429
460
|
|
430
461
|
@drawcache_property
|
431
|
-
def
|
432
|
-
'List of all column names, visible columns first.'
|
433
|
-
return [c.name for c in self.
|
462
|
+
def availColnames(self):
|
463
|
+
'List of all available column names, visible columns first.'
|
464
|
+
return [c.name for c in self.availCols]
|
434
465
|
|
435
466
|
@property
|
436
467
|
def cursorColIndex(self):
|
@@ -438,7 +469,7 @@ class TableSheet(BaseSheet):
|
|
438
469
|
try:
|
439
470
|
return self.columns.index(self.cursorCol)
|
440
471
|
except ValueError:
|
441
|
-
return
|
472
|
+
return 0
|
442
473
|
|
443
474
|
@property
|
444
475
|
def nonKeyVisibleCols(self):
|
@@ -530,7 +561,13 @@ class TableSheet(BaseSheet):
|
|
530
561
|
idx = len(self.columns) if index is None else index
|
531
562
|
col.recalc(self)
|
532
563
|
self.columns.insert(idx+i, col)
|
533
|
-
|
564
|
+
|
565
|
+
# statements after addColumn in the same command may want to use these cached properties
|
566
|
+
Sheet.keyCols.fget.cache_clear()
|
567
|
+
Sheet.visibleCols.fget.cache_clear()
|
568
|
+
Sheet.availCols.fget.cache_clear()
|
569
|
+
Sheet.availColnames.fget.cache_clear()
|
570
|
+
Sheet.colsByName.fget.cache_clear()
|
534
571
|
|
535
572
|
return cols[0]
|
536
573
|
|
@@ -578,7 +615,7 @@ class TableSheet(BaseSheet):
|
|
578
615
|
'Return tuple of the key for *row*.'
|
579
616
|
return tuple(c.getTypedValue(row) for c in self.keyCols)
|
580
617
|
|
581
|
-
def
|
618
|
+
def rowname(self, row):
|
582
619
|
'Return string of the key for *row*.'
|
583
620
|
return ','.join(map(str, self.rowkey(row)))
|
584
621
|
|
@@ -592,8 +629,8 @@ class TableSheet(BaseSheet):
|
|
592
629
|
|
593
630
|
if self.cursorVisibleColIndex <= 0:
|
594
631
|
self.cursorVisibleColIndex = 0
|
595
|
-
elif self.cursorVisibleColIndex >= self.
|
596
|
-
self.cursorVisibleColIndex = self.
|
632
|
+
elif self.cursorVisibleColIndex >= len(self.availCols):
|
633
|
+
self.cursorVisibleColIndex = len(self.availCols)-1
|
597
634
|
|
598
635
|
if self.topRowIndex < 0:
|
599
636
|
self.topRowIndex = 0
|
@@ -639,8 +676,8 @@ class TableSheet(BaseSheet):
|
|
639
676
|
self._visibleColLayout = {}
|
640
677
|
x = 0
|
641
678
|
vcolidx = 0
|
642
|
-
for vcolidx in
|
643
|
-
width = self.calcSingleColLayout(vcolidx, x, minColWidth)
|
679
|
+
for vcolidx, col in enumerate(self.availCols):
|
680
|
+
width = self.calcSingleColLayout(col, vcolidx, x, minColWidth)
|
644
681
|
if width:
|
645
682
|
x += width+sepColWidth
|
646
683
|
if x > winWidth-1:
|
@@ -648,17 +685,21 @@ class TableSheet(BaseSheet):
|
|
648
685
|
|
649
686
|
self.rightVisibleColIndex = vcolidx
|
650
687
|
|
651
|
-
def calcSingleColLayout(self, vcolidx:int, x:int=0, minColWidth:int=4):
|
652
|
-
col = self.visibleCols[vcolidx]
|
688
|
+
def calcSingleColLayout(self, col:Column, vcolidx:int, x:int=0, minColWidth:int=4):
|
653
689
|
if col.width is None and len(self.visibleRows) > 0:
|
654
690
|
vrows = self.visibleRows if self.nRows > 1000 else self.rows[:1000] #1964
|
655
691
|
# handle delayed column width-finding
|
656
692
|
col.width = max(col.getMaxWidth(vrows), minColWidth)
|
657
|
-
if vcolidx
|
693
|
+
if vcolidx < self.nVisibleCols-1: # let last column fill up the max width
|
658
694
|
col.width = min(col.width, self.options.default_width)
|
695
|
+
|
659
696
|
width = col.width if col.width is not None else self.options.default_width
|
660
|
-
|
661
|
-
|
697
|
+
|
698
|
+
# when cursor showing a hidden column
|
699
|
+
if vcolidx >= self.nVisibleCols and vcolidx == self.cursorVisibleColIndex:
|
700
|
+
width = self.options.default_width
|
701
|
+
|
702
|
+
width = max(width, 1)
|
662
703
|
if col in self.keyCols or vcolidx >= self.leftVisibleColIndex: # visible columns
|
663
704
|
self._visibleColLayout[vcolidx] = [x, min(width, self.windowWidth-x)]
|
664
705
|
return width
|
@@ -666,7 +707,7 @@ class TableSheet(BaseSheet):
|
|
666
707
|
|
667
708
|
def drawColHeader(self, scr, y, h, vcolidx):
|
668
709
|
'Compose and draw column header for given vcolidx.'
|
669
|
-
col = self.
|
710
|
+
col = self.availCols[vcolidx]
|
670
711
|
|
671
712
|
# hdrattr highlights whole column header
|
672
713
|
# sepattr is for header separators and indicators
|
@@ -677,7 +718,7 @@ class TableSheet(BaseSheet):
|
|
677
718
|
hdrcattr = update_attr(hdrcattr, colors.color_current_hdr, 2)
|
678
719
|
|
679
720
|
C = self.options.disp_column_sep
|
680
|
-
if (self.keyCols and col is self.keyCols[-1]) or vcolidx == self.
|
721
|
+
if (self.keyCols and col is self.keyCols[-1]) or vcolidx == self.nVisibleCols-1:
|
681
722
|
C = self.options.disp_keycol_sep
|
682
723
|
|
683
724
|
x, colwidth = self._visibleColLayout[vcolidx]
|
@@ -699,13 +740,14 @@ class TableSheet(BaseSheet):
|
|
699
740
|
if i == h-1:
|
700
741
|
hdrcattr = update_attr(hdrcattr, colors.color_bottom_hdr, 5)
|
701
742
|
|
702
|
-
|
743
|
+
if y+i < self.windowHeight:
|
744
|
+
clipdraw(scr, y+i, x, name, hdrcattr, w=colwidth)
|
703
745
|
vd.onMouse(scr, x, y+i, colwidth, 1, BUTTON3_RELEASED='rename-col')
|
704
746
|
|
705
|
-
if C and x+colwidth+len(C) < self.windowWidth and y+i < self.
|
747
|
+
if C and x+colwidth+len(C) < self.windowWidth and y+i < self.windowHeight:
|
706
748
|
scr.addstr(y+i, x+colwidth, C, sepcattr.attr)
|
707
749
|
|
708
|
-
clipdraw(scr, y+h-1, x+colwidth-
|
750
|
+
clipdraw(scr, y+h-1, min(x+colwidth, self.windowWidth-1)-dispwidth(T), T, hdrcattr)
|
709
751
|
|
710
752
|
try:
|
711
753
|
if vcolidx == self.leftVisibleColIndex and col not in self.keyCols and self.nonKeyVisibleCols.index(col) > 0:
|
@@ -718,7 +760,7 @@ class TableSheet(BaseSheet):
|
|
718
760
|
A = ''
|
719
761
|
for j, (sortcol, sortdir) in enumerate(self._ordering):
|
720
762
|
if isinstance(sortcol, str):
|
721
|
-
sortcol = self.
|
763
|
+
sortcol = self.colsByName.get(sortcol) # self.column will fail if sortcol was renamed
|
722
764
|
if col is sortcol:
|
723
765
|
A = self.options.disp_sort_desc[j] if sortdir else self.options.disp_sort_asc[j]
|
724
766
|
scr.addstr(y+h-1, x, A, hdrcattr.attr)
|
@@ -730,6 +772,17 @@ class TableSheet(BaseSheet):
|
|
730
772
|
'Return boolean: is given column index a key column?'
|
731
773
|
return self.visibleCols[vcolidx] in self.keyCols
|
732
774
|
|
775
|
+
@drawcache_property
|
776
|
+
def allAggregators(self):
|
777
|
+
'Return dict of aggname -> list of cols with that aggregator.'
|
778
|
+
allaggs = collections.defaultdict(list) # aggname -> list of cols with that aggregator
|
779
|
+
for vcolidx, (x, colwidth) in sorted(self._visibleColLayout.items()):
|
780
|
+
col = self.availCols[vcolidx]
|
781
|
+
if not col.hidden:
|
782
|
+
for aggr in col.aggregators:
|
783
|
+
allaggs[aggr.name].append(vcolidx)
|
784
|
+
return allaggs
|
785
|
+
|
733
786
|
def draw(self, scr):
|
734
787
|
'Draw entire screen onto the `scr` curses object.'
|
735
788
|
if not self.columns:
|
@@ -767,7 +820,7 @@ class TableSheet(BaseSheet):
|
|
767
820
|
|
768
821
|
y = headerRow + numHeaderRows
|
769
822
|
|
770
|
-
rows = self.rows[self.topRowIndex:min(self.topRowIndex+self.nScreenRows
|
823
|
+
rows = self.rows[self.topRowIndex:min(self.topRowIndex+self.nScreenRows, self.nRows)]
|
771
824
|
vd.callNoExceptions(self.checkCursor)
|
772
825
|
|
773
826
|
for rowidx, row in enumerate(rows):
|
@@ -781,15 +834,42 @@ class TableSheet(BaseSheet):
|
|
781
834
|
if vcolidx+1 < self.nVisibleCols:
|
782
835
|
scr.addstr(headerRow, self.windowWidth-2, self.options.disp_more_right, colors.color_column_sep.attr)
|
783
836
|
|
837
|
+
# draw bottom-row aggregators #2209
|
838
|
+
rightx, rightw = self._visibleColLayout[self.rightVisibleColIndex]
|
839
|
+
rightx += rightw+1
|
840
|
+
|
841
|
+
for aggrname, colidxs in self.allAggregators.items():
|
842
|
+
clipdraw(scr, y, 0, ' '*rightx + f' {aggrname:9}', colors.color_aggregator, truncator='+')
|
843
|
+
|
844
|
+
for vcolidx in colidxs:
|
845
|
+
x, colwidth = self._visibleColLayout[vcolidx]
|
846
|
+
col = self.availCols[vcolidx]
|
847
|
+
|
848
|
+
if not col.hidden:
|
849
|
+
dw = DisplayWrapper('')
|
850
|
+
try:
|
851
|
+
agg = vd.aggregators[aggrname]
|
852
|
+
dw.value = col.aggregateTotal(agg)
|
853
|
+
dw.typedval = wrapply(agg.type or col.type, dw.value)
|
854
|
+
dw.text = col.format(dw.typedval)
|
855
|
+
except Exception as e:
|
856
|
+
dw.note = self.options.disp_note_typeexc
|
857
|
+
dw.notecolor = 'color_warning'
|
858
|
+
vd.exceptionCaught(e, status=False)
|
859
|
+
disps = [('', ' ')] + list(col.display(dw, width=colwidth))
|
860
|
+
clipdraw_chunks(scr, y, x, disps, colors.color_aggregator, w=colwidth)
|
861
|
+
y += 1
|
862
|
+
|
863
|
+
|
784
864
|
def calc_height(self, row, displines=None, isNull=None, maxheight=1):
|
785
|
-
'render cell contents
|
865
|
+
'render cell contents for row into displines'
|
786
866
|
if displines is None:
|
787
867
|
displines = {} # [vcolidx] -> list of lines in that cell
|
788
868
|
|
789
869
|
for vcolidx, (x, colwidth) in sorted(self._visibleColLayout.items()):
|
790
870
|
if x < self.windowWidth: # only draw inside window
|
791
|
-
vcols = self.
|
792
|
-
if vcolidx >=
|
871
|
+
vcols = self.availCols
|
872
|
+
if vcolidx >= self.nVisibleCols and vcolidx != self.cursorVisibleColIndex:
|
793
873
|
continue
|
794
874
|
col = vcols[vcolidx]
|
795
875
|
cellval = col.getCell(row)
|
@@ -842,14 +922,35 @@ class TableSheet(BaseSheet):
|
|
842
922
|
|
843
923
|
# calc_height renders cell contents into displines
|
844
924
|
displines = {} # [vcolidx] -> list of lines in that cell
|
845
|
-
|
925
|
+
if options.disp_multiline_focus:
|
926
|
+
height = self.rowHeight if rowidx == self.cursorRowIndex else 1
|
927
|
+
else:
|
928
|
+
height = min(self.rowHeight, maxheight) or 1 # display even empty rows
|
929
|
+
|
930
|
+
self.calc_height(row, displines, maxheight=height)
|
846
931
|
|
847
|
-
height = min(self.rowHeight, maxheight) or 1 # display even empty rows
|
848
932
|
self._rowLayout[rowidx] = (ybase, height)
|
849
933
|
|
934
|
+
if height > 1:
|
935
|
+
colseps = [topsep] + [midsep]*(height-2) + [botsep]
|
936
|
+
endseps = [endtopsep] + [endmidsep]*(height-2) + [endbotsep]
|
937
|
+
keyseps = [keytopsep] + [keymidsep]*(height-2) + [keybotsep]
|
938
|
+
else:
|
939
|
+
colseps = [colsep]
|
940
|
+
endseps = [endsep]
|
941
|
+
keyseps = [keysep]
|
942
|
+
|
850
943
|
for vcolidx, (col, cellval, lines) in displines.items():
|
851
944
|
if vcolidx not in self._visibleColLayout:
|
852
945
|
continue
|
946
|
+
|
947
|
+
if vcolidx == self.nVisibleCols-1: # right edge of sheet
|
948
|
+
seps = endseps
|
949
|
+
elif (self.keyCols and col is self.keyCols[-1]): # last keycol
|
950
|
+
seps = keyseps
|
951
|
+
else:
|
952
|
+
seps = colseps
|
953
|
+
|
853
954
|
x, colwidth = self._visibleColLayout[vcolidx]
|
854
955
|
hoffset = col.hoffset
|
855
956
|
voffset = col.voffset
|
@@ -861,7 +962,7 @@ class TableSheet(BaseSheet):
|
|
861
962
|
notewidth = 1 if note else 0
|
862
963
|
if note:
|
863
964
|
notecattr = update_attr(cattr, colors.get_color(cellval.notecolor), 10)
|
864
|
-
scr
|
965
|
+
clipdraw(scr, ybase, x+colwidth-notewidth, note, notecattr)
|
865
966
|
|
866
967
|
lines = lines[voffset:]
|
867
968
|
|
@@ -873,36 +974,7 @@ class TableSheet(BaseSheet):
|
|
873
974
|
for i, chunks in enumerate(lines):
|
874
975
|
y = ybase+i
|
875
976
|
|
876
|
-
|
877
|
-
if len(lines) == 1:
|
878
|
-
sepchars = endsep
|
879
|
-
else:
|
880
|
-
if i == 0:
|
881
|
-
sepchars = endtopsep
|
882
|
-
elif i == len(lines)-1:
|
883
|
-
sepchars = endbotsep
|
884
|
-
else:
|
885
|
-
sepchars = endmidsep
|
886
|
-
elif (self.keyCols and col is self.keyCols[-1]): # last keycol
|
887
|
-
if len(lines) == 1:
|
888
|
-
sepchars = keysep
|
889
|
-
else:
|
890
|
-
if i == 0:
|
891
|
-
sepchars = keytopsep
|
892
|
-
elif i == len(lines)-1:
|
893
|
-
sepchars = keybotsep
|
894
|
-
else:
|
895
|
-
sepchars = keymidsep
|
896
|
-
else:
|
897
|
-
if len(lines) == 1:
|
898
|
-
sepchars = colsep
|
899
|
-
else:
|
900
|
-
if i == 0:
|
901
|
-
sepchars = topsep
|
902
|
-
elif i == len(lines)-1:
|
903
|
-
sepchars = botsep
|
904
|
-
else:
|
905
|
-
sepchars = midsep
|
977
|
+
sepchars = seps[i]
|
906
978
|
|
907
979
|
pre = disp_truncator if hoffset != 0 else disp_column_fill
|
908
980
|
prechunks = []
|
@@ -915,7 +987,7 @@ class TableSheet(BaseSheet):
|
|
915
987
|
clipdraw_chunks(scr, y, x, prechunks, cattr, w=colwidth-notewidth)
|
916
988
|
vd.onMouse(scr, x, y, colwidth, 1, BUTTON3_RELEASED='edit-cell')
|
917
989
|
|
918
|
-
if x+colwidth+
|
990
|
+
if sepchars and x+colwidth+dispwidth(sepchars) <= self.windowWidth:
|
919
991
|
scr.addstr(y, x+colwidth, sepchars, sepcattr.attr)
|
920
992
|
|
921
993
|
for notefunc in vd.rowNoters:
|
@@ -976,7 +1048,9 @@ class SequenceSheet(Sheet):
|
|
976
1048
|
|
977
1049
|
self.rows = []
|
978
1050
|
# add the rest of the rows
|
979
|
-
for r in vd.Progress(itsource, gerund='loading', total=0):
|
1051
|
+
for i, r in enumerate(vd.Progress(itsource, gerund='loading', total=0)):
|
1052
|
+
if self.precious and i > self.options.max_rows:
|
1053
|
+
break
|
980
1054
|
self.addRow(r)
|
981
1055
|
|
982
1056
|
|
@@ -1032,6 +1106,8 @@ def push(vd, vs, pane=0, load=True):
|
|
1032
1106
|
|
1033
1107
|
if load:
|
1034
1108
|
vs.ensureLoaded()
|
1109
|
+
if vd.activeCommand:
|
1110
|
+
vs.longname = vd.activeCommand.longname
|
1035
1111
|
|
1036
1112
|
|
1037
1113
|
@VisiData.api
|
@@ -1042,6 +1118,8 @@ def quit(vd, *sheets):
|
|
1042
1118
|
vs.confirmQuit('quit')
|
1043
1119
|
vs.pane = 0
|
1044
1120
|
vd.remove(vs)
|
1121
|
+
if vd.activeCommand:
|
1122
|
+
vd.activeSheet.longname = vd.activeCommand.longname
|
1045
1123
|
|
1046
1124
|
|
1047
1125
|
@BaseSheet.api
|
@@ -1064,7 +1142,7 @@ def preloadHook(sheet):
|
|
1064
1142
|
|
1065
1143
|
@VisiData.api
|
1066
1144
|
def newSheet(vd, name, ncols, **kwargs):
|
1067
|
-
return Sheet(name, columns=[SettableColumn() for i in range(ncols)], **kwargs)
|
1145
|
+
return Sheet(name, columns=[SettableColumn(width=vd.options.default_width) for i in range(ncols)], **kwargs)
|
1068
1146
|
|
1069
1147
|
|
1070
1148
|
@BaseSheet.api
|
@@ -1109,7 +1187,7 @@ BaseSheet.init('pane', lambda: 1)
|
|
1109
1187
|
|
1110
1188
|
|
1111
1189
|
BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload()', 'Reload current sheet')
|
1112
|
-
Sheet.addCommand('
|
1190
|
+
Sheet.addCommand('', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line')
|
1113
1191
|
|
1114
1192
|
Sheet.addCommand('!', 'key-col', 'toggleKeys([cursorCol])', 'toggle current column as a key column')
|
1115
1193
|
Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current column as a key column')
|
@@ -1151,7 +1229,7 @@ BaseSheet.addCommand('A', 'open-new', 'vd.push(vd.newSheet("unnamed", 1))', 'Ope
|
|
1151
1229
|
BaseSheet.addCommand('`', 'open-source', 'vd.push(source)', 'open source sheet')
|
1152
1230
|
BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=cleanName(sheet.name))', 'Rename current sheet')
|
1153
1231
|
|
1154
|
-
Sheet.addCommand('', 'addcol-source', 'source
|
1232
|
+
Sheet.addCommand('', 'addcol-source', 'source.addColumn(copy(cursorCol)) if isinstance (source, BaseSheet) else error("source must be sheet")', 'add copy of current column to source sheet') #988 frosencrantz
|
1155
1233
|
|
1156
1234
|
|
1157
1235
|
@Column.api
|