visidata 3.1.1__py3-none-any.whl → 3.3__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 +2 -2
- visidata/_input.py +106 -58
- visidata/_open.py +10 -7
- visidata/_types.py +2 -2
- visidata/aggregators.py +125 -16
- visidata/apps/vdsql/_ibis.py +8 -13
- visidata/basesheet.py +4 -3
- visidata/canvas.py +11 -7
- visidata/clipboard.py +11 -2
- visidata/cliptext.py +68 -23
- visidata/cmdlog.py +5 -1
- visidata/column.py +48 -33
- visidata/ddwplay.py +2 -2
- visidata/deprecated.py +96 -63
- visidata/errors.py +41 -5
- visidata/{features → experimental}/helloworld.py +1 -1
- visidata/experimental/liveupdate.py +1 -1
- visidata/expr.py +1 -0
- visidata/extensible.py +4 -0
- visidata/features/cmdpalette.py +64 -25
- visidata/features/describe.py +2 -2
- visidata/features/expand_cols.py +7 -5
- visidata/features/freeze.py +14 -2
- visidata/features/go_col.py +3 -3
- visidata/features/graph_zoom_y.py +47 -0
- visidata/features/incr.py +7 -3
- visidata/features/join.py +23 -12
- visidata/features/layout.py +8 -4
- visidata/features/melt.py +1 -0
- visidata/features/rank.py +103 -0
- visidata/features/reload_every.py +11 -8
- visidata/features/sysedit.py +14 -4
- visidata/features/transpose.py +1 -0
- visidata/features/window.py +12 -0
- visidata/form.py +10 -9
- visidata/freqtbl.py +47 -3
- visidata/fuzzymatch.py +11 -7
- visidata/graph.py +5 -3
- visidata/guides/AggregatorsSheet.md +84 -0
- visidata/guides/CommandsSheet.md +1 -0
- visidata/guides/MacrosSheet.md +1 -1
- visidata/guides/RankGuide.md +51 -0
- visidata/guides/TypesSheet.md +1 -1
- visidata/guides/WindowFunctionGuide.md +49 -0
- visidata/help.py +23 -6
- visidata/indexsheet.py +1 -1
- visidata/loaders/_pandas.py +3 -1
- visidata/loaders/archive.py +33 -6
- visidata/loaders/csv.py +12 -1
- visidata/loaders/eml.py +2 -0
- visidata/loaders/f5log.py +2 -2
- visidata/loaders/fec.py +6 -9
- visidata/loaders/fixed_width.py +2 -0
- visidata/loaders/hdf5.py +34 -10
- visidata/loaders/npy.py +54 -23
- visidata/loaders/orgmode.py +3 -2
- visidata/loaders/pandas_freqtbl.py +4 -0
- visidata/loaders/psv.py +13 -0
- visidata/loaders/sqlite.py +1 -1
- visidata/loaders/vds.py +3 -4
- visidata/macros.py +5 -4
- visidata/main.py +21 -11
- visidata/mainloop.py +8 -5
- visidata/man/parse_options.py +3 -2
- visidata/man/vd.1 +38 -17
- visidata/man/vd.txt +47 -17
- visidata/menu.py +10 -10
- visidata/metasheets.py +3 -3
- visidata/mouse.py +3 -0
- visidata/movement.py +6 -3
- visidata/pyobj.py +17 -9
- visidata/save.py +10 -2
- visidata/selection.py +29 -18
- visidata/settings.py +9 -5
- visidata/sheets.py +124 -48
- visidata/shell.py +2 -2
- visidata/sidebar.py +11 -8
- visidata/sort.py +89 -11
- visidata/statusbar.py +10 -9
- visidata/tests/test_cliptext.py +164 -0
- visidata/tests/test_commands.py +6 -2
- visidata/tests/test_menu.py +1 -1
- visidata/textsheet.py +34 -8
- visidata/themes/ascii8.py +2 -2
- visidata/themes/light.py +5 -0
- visidata/threads.py +38 -8
- visidata/utils.py +15 -1
- visidata/vendor/__init__.py +0 -0
- {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/vd.1 +38 -17
- {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/visidata.1 +38 -17
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/METADATA +62 -15
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/RECORD +98 -92
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/WHEEL +1 -1
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/entry_points.txt +1 -0
- visidata-3.1.1.data/scripts/vd +0 -6
- {visidata-3.1.1.data → visidata-3.3.data}/data/share/applications/visidata.desktop +0 -0
- {visidata-3.1.1.data → visidata-3.3.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/top_level.txt +0 -0
visidata/basesheet.py
CHANGED
@@ -76,6 +76,8 @@ class DrawablePane(Extensible):
|
|
76
76
|
|
77
77
|
try:
|
78
78
|
self.sheet = self
|
79
|
+
if cmd.deprecated:
|
80
|
+
vd.deprecated_warn(cmd.longname, cmd.deprecated, 'a different command')
|
79
81
|
code = compile(cmd.execstr, cmd.longname, 'exec')
|
80
82
|
exec(code, vdglobals, LazyChainMap(vd, self))
|
81
83
|
return False
|
@@ -105,6 +107,7 @@ class BaseSheet(DrawablePane):
|
|
105
107
|
precious = True # False for a few discardable metasheets
|
106
108
|
defer = False # False for not deferring changes until save
|
107
109
|
guide = '' # default to show in sidebar
|
110
|
+
icon = '›'
|
108
111
|
|
109
112
|
def _obj_options(self):
|
110
113
|
return vd.OptionsObject(vd._options, obj=self)
|
@@ -212,7 +215,7 @@ class BaseSheet(DrawablePane):
|
|
212
215
|
try:
|
213
216
|
for hookfunc in vd.beforeExecHooks:
|
214
217
|
hookfunc(self, cmd, '', keystrokes)
|
215
|
-
escaped =
|
218
|
+
escaped = self.execCommand2(cmd, vdglobals=vdglobals)
|
216
219
|
except Exception as e:
|
217
220
|
vd.debug(cmd.execstr)
|
218
221
|
err = vd.exceptionCaught(e)
|
@@ -299,8 +302,6 @@ class BaseSheet(DrawablePane):
|
|
299
302
|
'Return formatted string with *sheet* and *vd* accessible to expressions. Missing expressions return empty strings instead of error.'
|
300
303
|
return MissingAttrFormatter().format(fmt, sheet=self, vd=vd, **kwargs)
|
301
304
|
|
302
|
-
|
303
|
-
|
304
305
|
@VisiData.api
|
305
306
|
def redraw(vd):
|
306
307
|
'Clear the terminal screen and let the next draw cycle recreate the windows and redraw everything.'
|
visidata/canvas.py
CHANGED
@@ -11,7 +11,7 @@ from visidata.bezier import bezier
|
|
11
11
|
vd.theme_option('disp_graph_labels', True, 'show axes and legend on graph')
|
12
12
|
vd.theme_option('plot_colors', 'green red yellow cyan magenta white 38 136 168', 'list of distinct colors to use for plotting distinct objects')
|
13
13
|
vd.theme_option('disp_canvas_charset', ''.join(chr(0x2800+i) for i in range(256)), 'charset to render 2x4 blocks on canvas')
|
14
|
-
vd.theme_option('
|
14
|
+
vd.theme_option('disp_graph_pixel_random', False, 'randomly choose attr from set of pixels instead of most common')
|
15
15
|
vd.theme_option('disp_zoom_incr', 2.0, 'amount to multiply current zoomlevel when zooming')
|
16
16
|
vd.theme_option('color_graph_hidden', '238 blue', 'color of legend for hidden attribute')
|
17
17
|
vd.theme_option('color_graph_selected', 'bold', 'color of selected graph points')
|
@@ -258,7 +258,7 @@ class Plotter(BaseSheet):
|
|
258
258
|
disp_canvas_charset += (256 - len(disp_canvas_charset)) * disp_canvas_charset[-1]
|
259
259
|
if self.pixels:
|
260
260
|
cursorBBox = self.plotterCursorBox
|
261
|
-
getPixelAttr = self.getPixelAttrRandom if self.options.
|
261
|
+
getPixelAttr = self.getPixelAttrRandom if self.options.disp_graph_pixel_random else self.getPixelAttrMost
|
262
262
|
|
263
263
|
for char_y in range(0, self.plotheight//4):
|
264
264
|
for char_x in range(0, self.plotwidth//2):
|
@@ -304,8 +304,8 @@ class Plotter(BaseSheet):
|
|
304
304
|
def _overlaps(a, b):
|
305
305
|
a_x1, _, a_txt, _, _ = a
|
306
306
|
b_x1, _, b_txt, _, _ = b
|
307
|
-
a_x2 = a_x1 +
|
308
|
-
b_x2 = b_x1 +
|
307
|
+
a_x2 = a_x1 + dispwidth(a_txt)
|
308
|
+
b_x2 = b_x1 + dispwidth(b_txt)
|
309
309
|
if a_x1 < b_x1 < a_x2 or a_x1 < b_x2 < a_x2 or \
|
310
310
|
b_x1 < a_x1 < b_x2 or b_x1 < a_x2 < b_x2:
|
311
311
|
return True
|
@@ -325,10 +325,10 @@ class Plotter(BaseSheet):
|
|
325
325
|
for pix_x, pix_y, txt, attr, row in self.labels:
|
326
326
|
if attr in self.hiddenAttrs:
|
327
327
|
continue
|
328
|
-
if row is not None:
|
329
|
-
pix_x -= len(txt)/2*2
|
330
328
|
char_y = int(pix_y/4)
|
331
329
|
char_x = int(pix_x/2)
|
330
|
+
if row is not None:
|
331
|
+
char_x -= math.ceil(dispwidth(txt)/2)*2
|
332
332
|
o = (char_x, char_y, txt, attr, row)
|
333
333
|
_mark_overlap_text(labels_by_line[char_y], o)
|
334
334
|
|
@@ -356,6 +356,7 @@ class Canvas(Plotter):
|
|
356
356
|
rightMarginPixels = 4*2
|
357
357
|
topMarginPixels = 0*4
|
358
358
|
bottomMarginPixels = 1*4 # reserve bottom line for x axis
|
359
|
+
guide = '# Canvas\n'
|
359
360
|
|
360
361
|
def __init__(self, *names, **kwargs):
|
361
362
|
self.left_margin = self.leftMarginPixels
|
@@ -384,6 +385,9 @@ class Canvas(Plotter):
|
|
384
385
|
def reset(self):
|
385
386
|
'clear everything in preparation for a fresh reload()'
|
386
387
|
self.polylines.clear()
|
388
|
+
self.canvasBox = None
|
389
|
+
self.visibleBox = None
|
390
|
+
self.cursorBox = None
|
387
391
|
self.left_margin = self.leftMarginPixels
|
388
392
|
self.legends.clear()
|
389
393
|
self.legendwidth = 0
|
@@ -727,7 +731,7 @@ class Canvas(Plotter):
|
|
727
731
|
def plot_elements(self, invert_y=False):
|
728
732
|
'plots points and lines and text onto the plotter'
|
729
733
|
|
730
|
-
self.resetBounds()
|
734
|
+
self.resetBounds(refresh=False)
|
731
735
|
|
732
736
|
bb = self.visibleBox
|
733
737
|
xmin, ymin, xmax, ymax = bb.xmin, bb.ymin, bb.xmax, bb.ymax
|
visidata/clipboard.py
CHANGED
@@ -7,13 +7,21 @@ import tempfile
|
|
7
7
|
import functools
|
8
8
|
import os
|
9
9
|
import itertools
|
10
|
+
import platform
|
10
11
|
|
11
12
|
from visidata import VisiData, vd, asyncthread, SettableColumn
|
12
13
|
from visidata import Sheet, Path, Column
|
13
14
|
|
14
|
-
if
|
15
|
+
if (
|
16
|
+
# Windows
|
17
|
+
sys.platform == 'win32'
|
18
|
+
# WSL 2
|
19
|
+
or "microsoft-standard-WSL2" in platform.uname().release
|
20
|
+
# WSL 1
|
21
|
+
or sys.platform == 'linux' and platform.uname().release.endswith("-Microsoft")
|
22
|
+
):
|
15
23
|
syscopy_cmd_default = 'clip.exe'
|
16
|
-
syspaste_cmd_default = 'powershell -command Get-Clipboard'
|
24
|
+
syspaste_cmd_default = 'powershell.exe -noprofile -command Get-Clipboard'
|
17
25
|
elif sys.platform == 'darwin':
|
18
26
|
syscopy_cmd_default = 'pbcopy w'
|
19
27
|
syspaste_cmd_default = 'pbpaste'
|
@@ -196,6 +204,7 @@ Sheet.addCommand('Y', 'syscopy-row', 'syscopyCells(visibleCols, [cursorRow])', '
|
|
196
204
|
|
197
205
|
Sheet.addCommand('gY', 'syscopy-selected', 'syscopyCells(visibleCols, onlySelectedRows)', 'yank (copy) selected rows to system clipboard (using options.clipboard_copy_cmd)')
|
198
206
|
Sheet.addCommand('zY', 'syscopy-cell', 'syscopyValue(cursorDisplay)', 'yank (copy) current cell to system clipboard (using options.clipboard_copy_cmd)')
|
207
|
+
Sheet.addCommand('', 'syscopy-colname', 'syscopyValue(cursorCol.name)', 'yank (copy) current column header to system clipboard (using options.clipboard_copy_cmd)')
|
199
208
|
Sheet.addCommand('gzY', 'syscopy-cells', 'syscopyCells([cursorCol], onlySelectedRows, filetype="txt")', 'yank (copy) contents of current column from selected rows to system clipboard (using options.clipboard_copy_cmd')
|
200
209
|
|
201
210
|
Sheet.addCommand('x', 'cut-row', 'copyRows([sheet.delete_row(cursorRowIndex)]); defer and cursorDown(1)', 'delete (cut) current row and move it to clipboard')
|
visidata/cliptext.py
CHANGED
@@ -142,42 +142,52 @@ def iterchars(x):
|
|
142
142
|
|
143
143
|
@functools.lru_cache(maxsize=100000)
|
144
144
|
def _clipstr(s, dispw, trunch='', oddspacech='', combch='', modch=''):
|
145
|
-
'''
|
146
|
-
|
147
|
-
|
145
|
+
''' *s* is a string or an iterator that contains characters.
|
146
|
+
*dispw* is the integer screen width that the clipped string will fit inside, or None.
|
147
|
+
Return clipped string and width in terminal display characters.
|
148
|
+
Note: width may differ from len(s) if chars are 'fullwidth'.
|
149
|
+
If *dispw* is None, no clipping occurs.
|
150
|
+
If *trunch* has a width greater than *dispw*, the empty string
|
151
|
+
will be used as a truncator instead.'''
|
152
|
+
if not s or (dispw is not None and dispw < 1): #iterator s would be truthy
|
148
153
|
return '', 0
|
149
154
|
|
150
|
-
if dispw == 1:
|
151
|
-
return s[0], 1
|
152
|
-
|
153
155
|
w = 0
|
154
156
|
ret = ''
|
157
|
+
trunc_i = 0
|
158
|
+
w_truncated = 0
|
155
159
|
|
156
160
|
trunchlen = dispwidth(trunch)
|
161
|
+
if dispw is None:
|
162
|
+
s = ''.join(s)
|
163
|
+
return s, dispwidth(s)
|
164
|
+
if trunchlen > dispw: #if the truncator cannot fit, use a truncator of ''
|
165
|
+
return _clipstr(s, dispw, trunch='', oddspacech=oddspacech, combch=combch, modch=modch)
|
157
166
|
for c in s:
|
158
167
|
newc, chlen = _dispch(c, oddspacech=oddspacech, combch=combch, modch=modch)
|
159
168
|
if not newc:
|
160
169
|
newc = c
|
161
170
|
chlen = dispwidth(c)
|
162
171
|
|
163
|
-
if
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
ret += newc
|
172
|
+
#if the next character will fit
|
173
|
+
if w+chlen <= dispw:
|
174
|
+
ret += newc
|
175
|
+
w += chlen
|
176
|
+
#move the truncation spot forward only when the truncation character can fit
|
177
|
+
if w+trunchlen <= dispw:
|
178
|
+
trunc_i += 1
|
179
|
+
w_truncated += chlen
|
180
|
+
continue
|
181
|
+
# if we reach this line, a character did not fit, and the result needs truncation
|
182
|
+
return ret[:trunc_i] + trunch, w_truncated+trunchlen
|
175
183
|
|
176
184
|
return ret, w
|
177
185
|
|
178
186
|
|
179
187
|
@drawcache
|
180
188
|
def clipstr(s, dispw, truncator=None, oddspace=None):
|
189
|
+
''' *s* is a string or an iterator that contains characters.
|
190
|
+
*dispw* is the integer screen width that the clipped string will fit inside, or None.'''
|
181
191
|
if options.visibility:
|
182
192
|
return _clipstr(s, dispw,
|
183
193
|
trunch=options.disp_truncator if truncator is None else truncator,
|
@@ -205,8 +215,6 @@ def clipdraw(scr, y, x, s, attr, w=None, clear=True, literal=False, **kwargs):
|
|
205
215
|
|
206
216
|
x = max(0, x)
|
207
217
|
y = max(0, y)
|
208
|
-
assert x >= 0, x
|
209
|
-
assert y >= 0, y
|
210
218
|
|
211
219
|
return clipdraw_chunks(scr, y, x, chunks, attr, w=w, clear=clear, **kwargs)
|
212
220
|
|
@@ -310,6 +318,10 @@ def wraptext(text, width=80, indent=''):
|
|
310
318
|
line = _markdown_to_internal(line)
|
311
319
|
chunks = re.split(internal_markup_re, line)
|
312
320
|
textchunks = [x for x in chunks if not is_vdcode(x)]
|
321
|
+
if ''.join(textchunks) == '': #for markup with no contents, like '[:tag][/]' or '[:]' or '[/]'
|
322
|
+
yield '', ''
|
323
|
+
continue
|
324
|
+
# textwrap.wrap does not handle variable-width characters #2416
|
313
325
|
for linenum, textline in enumerate(textwrap.wrap(''.join(textchunks), width=width, drop_whitespace=False)):
|
314
326
|
txt = textline
|
315
327
|
r = ''
|
@@ -345,8 +357,39 @@ def clipbox(scr, lines, attr, title=''):
|
|
345
357
|
for i, line in enumerate(lines):
|
346
358
|
clipdraw(scr, i+1, 2, line, attr)
|
347
359
|
|
348
|
-
clipdraw(scr, 0, w-
|
349
|
-
|
360
|
+
clipdraw(scr, 0, w-dispwidth(title)-6, f"| {title} |", attr)
|
361
|
+
|
362
|
+
def clipstr_start(dispval, w, truncator=''):
|
363
|
+
'''Return a tuple (frag, dw), where *frag* is the longest ending substring
|
364
|
+
of *dispval* that will fit in a space *w* terminal display characters wide,
|
365
|
+
and *dw* is the substring's display width as an int.'''
|
366
|
+
# Note: this implementation is likely incorrect for unusual Unicode
|
367
|
+
# strings or encodings, where trimming an initial character produces
|
368
|
+
# an invalid string or does not make the string shorter.
|
369
|
+
if w <= 0: return '', 0
|
370
|
+
j = len(dispval)
|
371
|
+
while j >= 1:
|
372
|
+
if dispwidth((truncator if j > 1 else '') + dispval[j-1:]) <= w:
|
373
|
+
j -= 1
|
374
|
+
else:
|
375
|
+
break
|
376
|
+
frag = (truncator if j > 0 else '') + dispval[j:]
|
377
|
+
return frag, dispwidth(frag)
|
378
|
+
|
379
|
+
def clipstr_middle(s, n=10, truncator='…'):
|
380
|
+
'''Return a string having a display width <= *n*. Excess characters are
|
381
|
+
trimmed from the middle of the string, and replaced by a single
|
382
|
+
instance of *truncator*.'''
|
383
|
+
if n == 0: return '', 0
|
384
|
+
if dispwidth(s) > n:
|
385
|
+
#for even widths, give the leftover 1 space to the right fragment
|
386
|
+
l_space = n//2 if n%2 == 1 else max(n//2-1, 0)
|
387
|
+
l_frag, l_w = _clipstr(s, l_space)
|
388
|
+
#if left fragment did not fill its space, give the unused space to the right fragment
|
389
|
+
r_frag = clipstr_start(s, n//2+(l_space-l_w))[0]
|
390
|
+
res = l_frag + truncator + r_frag
|
391
|
+
return res, dispwidth(res)
|
392
|
+
return s, dispwidth(s)
|
350
393
|
|
351
394
|
vd.addGlobals(clipstr=clipstr,
|
352
395
|
clipdraw=clipdraw,
|
@@ -355,4 +398,6 @@ vd.addGlobals(clipstr=clipstr,
|
|
355
398
|
dispwidth=dispwidth,
|
356
399
|
iterchars=iterchars,
|
357
400
|
iterchunks=iterchunks,
|
358
|
-
wraptext=wraptext
|
401
|
+
wraptext=wraptext,
|
402
|
+
clipstr_start=clipstr_start,
|
403
|
+
clipstr_middle=clipstr_middle)
|
visidata/cmdlog.py
CHANGED
@@ -330,6 +330,9 @@ def replay_sync(vd, cmdlog):
|
|
330
330
|
with vd.DisableAsync():
|
331
331
|
vd.sync() #2352 let cmdlog finish loading
|
332
332
|
cmdlog.cursorRowIndex = 0
|
333
|
+
# save current replay, for cmdlogs that replay other cmdlogs, such as a macro executing another macro
|
334
|
+
prev_replay = vd.currentReplay
|
335
|
+
prev_replay_row = vd.currentReplayRow
|
333
336
|
vd.currentReplay = cmdlog
|
334
337
|
|
335
338
|
with Progress(total=len(cmdlog.rows)) as prog:
|
@@ -356,7 +359,8 @@ def replay_sync(vd, cmdlog):
|
|
356
359
|
vd.activeSheet.ensureLoaded()
|
357
360
|
|
358
361
|
vd.status('replay complete')
|
359
|
-
vd.currentReplay =
|
362
|
+
vd.currentReplay = prev_replay
|
363
|
+
vd.currentReplayRow = prev_replay_row
|
360
364
|
|
361
365
|
|
362
366
|
@VisiData.api
|
visidata/column.py
CHANGED
@@ -40,21 +40,11 @@ class DisplayWrapper:
|
|
40
40
|
def __eq__(self, other):
|
41
41
|
return self.value == other
|
42
42
|
|
43
|
-
def _default_colnames():
|
44
|
-
'A B C .. Z AA AB .. ZZ AAA .. to infinity'
|
45
|
-
i=0
|
46
|
-
while True:
|
47
|
-
i += 1
|
48
|
-
for x in itertools.product(string.ascii_uppercase, repeat=i):
|
49
|
-
yield ''.join(x)
|
50
|
-
|
51
|
-
default_colnames = _default_colnames()
|
52
|
-
|
53
43
|
|
54
44
|
class Column(Extensible):
|
55
45
|
'''Base class for all column types.
|
56
46
|
|
57
|
-
- *name*: name of this column
|
47
|
+
- *name*: name of this column; if None, current sheet will assign a name
|
58
48
|
- *type*: ``anytype str int float date`` or other type-like conversion function.
|
59
49
|
- *cache*: cache behavior
|
60
50
|
|
@@ -71,7 +61,10 @@ class Column(Extensible):
|
|
71
61
|
def __init__(self, name=None, *, type=anytype, cache=False, **kwargs):
|
72
62
|
self.sheet = ExplodingMock('use addColumn() on all columns') # owning Sheet, set in .recalc() via Sheet.addColumn
|
73
63
|
if name is None:
|
74
|
-
name
|
64
|
+
if vd.sheet: # get a column name from the current sheet
|
65
|
+
name = vd.sheet.incremented_colname()
|
66
|
+
else:
|
67
|
+
name = ''
|
75
68
|
self.name = str(name) # display visible name
|
76
69
|
self.fmtstr = '' # by default, use str()
|
77
70
|
self._type = type # anytype/str/int/float/date/func
|
@@ -86,7 +79,7 @@ class Column(Extensible):
|
|
86
79
|
self.formatter = ''
|
87
80
|
self.displayer = ''
|
88
81
|
self.defer = False
|
89
|
-
self.disp_expert = 0 #
|
82
|
+
self.disp_expert = 0 # do not show if 'nometacols' in options.disp_help_flags
|
90
83
|
|
91
84
|
self.setCache(cache)
|
92
85
|
for k, v in kwargs.items():
|
@@ -243,12 +236,16 @@ class Column(Extensible):
|
|
243
236
|
return self.make_formatter()(*args, **kwargs)
|
244
237
|
|
245
238
|
def formatValue(self, typedval, width=None):
|
246
|
-
'Return displayable string of *typedval* according to ``Column.fmtstr``.
|
239
|
+
'''Return displayable string of *typedval* according to ``Column.fmtstr``.
|
240
|
+
If *width* is not None, values are clipped to that width when *typedval*
|
241
|
+
is a dict/list/tuple, but not for other types.'''
|
247
242
|
if typedval is None:
|
248
243
|
return None
|
249
244
|
|
250
245
|
if self.type is anytype:
|
251
246
|
if isinstance(typedval, (dict, list, tuple)):
|
247
|
+
if width is None:
|
248
|
+
return ''.join(iterchars(typedval))
|
252
249
|
dispval, dispw = clipstr(iterchars(typedval), width)
|
253
250
|
return dispval
|
254
251
|
|
@@ -264,7 +261,9 @@ class Column(Extensible):
|
|
264
261
|
|
265
262
|
The 'generic' displayer does not do any formatting.
|
266
263
|
'''
|
267
|
-
if width is not None and width > 1 and
|
264
|
+
if width is not None and width > 1 and \
|
265
|
+
vd.isNumeric(self) and \
|
266
|
+
isinstance(dw.typedval, (int, float)):
|
268
267
|
yield ('', dw.text.rjust(width-2))
|
269
268
|
else:
|
270
269
|
yield ('', dw.text)
|
@@ -355,7 +354,8 @@ class Column(Extensible):
|
|
355
354
|
return ret
|
356
355
|
|
357
356
|
def getCell(self, row):
|
358
|
-
'Return DisplayWrapper for displayable cell value.
|
357
|
+
'''Return DisplayWrapper for displayable cell value.
|
358
|
+
For dict/list/tuple cells, the width of the value returned is capped at the column width.'''
|
359
359
|
cellval = wrapply(self.getValue, row)
|
360
360
|
typedval = wrapply(self.type, cellval)
|
361
361
|
|
@@ -394,7 +394,7 @@ class Column(Extensible):
|
|
394
394
|
dw.typedval = typedval
|
395
395
|
|
396
396
|
try:
|
397
|
-
dw.text = self.format(typedval, width=
|
397
|
+
dw.text = self.format(typedval, width=self.width) or ''
|
398
398
|
|
399
399
|
# annotate cells with raw value type in anytype columns, except for strings
|
400
400
|
if self.type is anytype and type(cellval) is not str:
|
@@ -417,7 +417,8 @@ class Column(Extensible):
|
|
417
417
|
return dw
|
418
418
|
|
419
419
|
def getDisplayValue(self, row):
|
420
|
-
'Return string displayed in this column for given *row*.
|
420
|
+
'''Return string displayed in this column for given *row*.
|
421
|
+
For dict/list/tuple cells, the width of the display value returned is capped at the column width.'''
|
421
422
|
return self.getCell(row).text
|
422
423
|
|
423
424
|
def putValue(self, row, val):
|
@@ -457,22 +458,36 @@ class Column(Extensible):
|
|
457
458
|
return vd.status('set %d cells to %d values' % (len(rows), len(values)))
|
458
459
|
|
459
460
|
def getMaxWidth(self, rows):
|
460
|
-
'Return the maximum length of any cell in column or its header (up to window width).'
|
461
|
-
|
461
|
+
'Return the maximum length of any cell in column or its header (up to drawable window width).'
|
462
|
+
drawable_width = self.sheet.windowWidth-1
|
462
463
|
nlen = dispwidth(self.name)
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
464
|
+
w_max = nlen
|
465
|
+
for r in rows:
|
466
|
+
row_w = self.measureValueWidthCapped(r, maxwidth=drawable_width)
|
467
|
+
if w_max < row_w:
|
468
|
+
w_max = row_w
|
469
|
+
if w_max >= self.sheet.windowWidth:
|
470
|
+
break #1747 early out to speed up wide columns
|
471
|
+
return min(w_max+2, drawable_width)
|
472
|
+
|
473
|
+
def measureValueWidthCapped(self, row, maxwidth=None):
|
474
|
+
'''Measure the width of the contents of a cell. Stop measuring at *maxwidth*,
|
475
|
+
to save time iterating over very long dict/list/tuple values.
|
476
|
+
If *maxwidth* is None, return the full width.'''
|
477
|
+
# The value classification logic here is taken from getCell,
|
478
|
+
# modified to cap the width examined for any dict/list/tuple
|
479
|
+
cellval = wrapply(self.getValue, row)
|
480
|
+
typedval = wrapply(self.type, cellval)
|
481
|
+
if isinstance(typedval, (TypedWrapper, threading.Thread)):
|
482
|
+
return dispwidth(self.getCell(row).text, maxwidth=maxwidth)
|
483
|
+
try:
|
484
|
+
text = self.format(typedval, width=maxwidth) or ''
|
485
|
+
except Exception as e: # formatting failure
|
486
|
+
try:
|
487
|
+
text = str(cellval)
|
488
|
+
except Exception as e:
|
489
|
+
text = str(e)
|
490
|
+
return dispwidth(text, maxwidth=maxwidth)
|
476
491
|
|
477
492
|
|
478
493
|
# ---- basic Columns
|
visidata/ddwplay.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
import json
|
3
3
|
import time
|
4
|
-
from visidata import colors, vd, clipdraw, ColorAttr
|
4
|
+
from visidata import colors, vd, clipdraw, ColorAttr, dispwidth
|
5
5
|
|
6
6
|
__all__ = ['Animation', 'AnimationMgr']
|
7
7
|
|
@@ -77,7 +77,7 @@ class Animation:
|
|
77
77
|
self.total_ms = sum(f.duration_ms or 0 for f in self.frames.values())
|
78
78
|
for f in self.frames.values():
|
79
79
|
for r, x, y, _ in self.iterdeep(f.rows):
|
80
|
-
self.width = max(self.width, x+
|
80
|
+
self.width = max(self.width, x+dispwidth(r.text))
|
81
81
|
self.height = max(self.height, y)
|
82
82
|
|
83
83
|
def draw(self, scr, *, t=0, x=0, y=0, loop=False, attr=ColorAttr(), **kwargs):
|