visidata 3.2__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 +1 -1
- visidata/_input.py +36 -22
- visidata/_open.py +1 -1
- visidata/basesheet.py +0 -2
- visidata/cliptext.py +4 -1
- visidata/column.py +43 -32
- visidata/deprecated.py +5 -0
- visidata/experimental/liveupdate.py +1 -1
- visidata/features/cmdpalette.py +61 -22
- visidata/features/expand_cols.py +0 -1
- visidata/features/go_col.py +1 -2
- visidata/features/layout.py +1 -2
- visidata/features/reload_every.py +3 -3
- visidata/form.py +6 -5
- visidata/freqtbl.py +2 -2
- visidata/fuzzymatch.py +6 -5
- visidata/guides/CommandsSheet.md +1 -0
- visidata/help.py +20 -2
- visidata/loaders/archive.py +27 -3
- visidata/loaders/csv.py +7 -0
- visidata/macros.py +1 -1
- visidata/main.py +11 -7
- visidata/mainloop.py +1 -1
- visidata/man/vd.1 +13 -4
- visidata/man/vd.txt +23 -4
- visidata/menu.py +1 -1
- visidata/mouse.py +2 -0
- visidata/movement.py +6 -3
- visidata/save.py +5 -1
- visidata/settings.py +7 -3
- visidata/sheets.py +79 -31
- visidata/sidebar.py +7 -6
- visidata/tests/test_cliptext.py +13 -0
- visidata/tests/test_commands.py +1 -0
- visidata/threads.py +22 -0
- visidata/undo.py +1 -1
- visidata/utils.py +15 -1
- {visidata-3.2.data → visidata-3.3.data}/data/share/man/man1/vd.1 +13 -4
- {visidata-3.2.data → visidata-3.3.data}/data/share/man/man1/visidata.1 +13 -4
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/METADATA +3 -3
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/RECORD +47 -47
- {visidata-3.2.data → visidata-3.3.data}/data/share/applications/visidata.desktop +0 -0
- {visidata-3.2.data → visidata-3.3.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/WHEEL +0 -0
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/entry_points.txt +0 -0
- {visidata-3.2.dist-info → visidata-3.3.dist-info}/top_level.txt +0 -0
visidata/__init__.py
CHANGED
visidata/_input.py
CHANGED
@@ -245,7 +245,8 @@ class InputWidget:
|
|
245
245
|
clipdraw(scr, y, x+w, ' ', attr, 1, clear=False, literal=True)
|
246
246
|
if scr:
|
247
247
|
prew = dispwidth(dispval[:i])
|
248
|
-
|
248
|
+
if x+prew < scr.getmaxyx()[1]: #move cursor back to where the user is editing
|
249
|
+
scr.move(y, x+prew)
|
249
250
|
|
250
251
|
def handle_key(self, ch:str, scr) -> bool:
|
251
252
|
'Return True to accept current input. Raise EscapeException on Ctrl+C, Ctrl+Q, or ESC.'
|
@@ -373,7 +374,7 @@ def editText(vd, y, x, w, attr=ColorAttr(), value='',
|
|
373
374
|
help='',
|
374
375
|
updater=lambda val: None, bindings={},
|
375
376
|
display=True, record=True, clear=True, **kwargs):
|
376
|
-
'Invoke modal single-line editor at (*y*, *x*) for *w* terminal chars. Use *display* is False for sensitive input like passphrases. If *record* is True, get input from the cmdlog in batch mode, and save input to the cmdlog if *display* is also True. Return new value as string.'
|
377
|
+
'''Invoke modal single-line editor at (*y*, *x*) for *w* terminal chars. Use *display* is False for sensitive input like passphrases. If *record* is True, get input from the cmdlog in batch mode, and save input to the cmdlog if *display* is also True. Return new value as string. Callers should handle curses.error, which will be raised if the terminal is resized during the edit, in a way that moves the editor coordinates offscreen.'''
|
377
378
|
v = None
|
378
379
|
if record and vd.cmdlog:
|
379
380
|
v = vd.getCommandInput()
|
@@ -391,8 +392,8 @@ def editText(vd, y, x, w, attr=ColorAttr(), value='',
|
|
391
392
|
try:
|
392
393
|
widget = InputWidget(value=str(value), display=display, **kwargs)
|
393
394
|
|
394
|
-
with vd.AddedHelp(vd.getHelpPane('input', module='visidata'), 'Input Keystrokes Help'), \
|
395
|
-
vd.AddedHelp(help, 'Input Field Help'):
|
395
|
+
with vd.AddedHelp(vd.getHelpPane('input', module='visidata'), 'Input Keystrokes Help', 'inputkeys'), \
|
396
|
+
vd.AddedHelp(help, 'Input Field Help', 'inputfield'):
|
396
397
|
v = widget.editline(vd.activeSheet._scr, y, x, w, attr=attr, updater=updater, bindings=bindings, clear=clear)
|
397
398
|
except AcceptInput as e:
|
398
399
|
v = e.args[0]
|
@@ -468,7 +469,6 @@ def inputMultiple(vd, updater=lambda val: None, record=True, **kwargs):
|
|
468
469
|
|
469
470
|
assert False, type(previnput)
|
470
471
|
|
471
|
-
y = sheet.windowHeight-1
|
472
472
|
maxw = sheet.windowWidth//2
|
473
473
|
attr = colors.color_edit_unfocused
|
474
474
|
|
@@ -494,9 +494,11 @@ def inputMultiple(vd, updater=lambda val: None, record=True, **kwargs):
|
|
494
494
|
|
495
495
|
def _drawPrompt(val):
|
496
496
|
for k, v in kwargs.items():
|
497
|
+
#recalculate y to adjust for screen resizes during input()
|
498
|
+
y = sheet.windowHeight-v.get('dy')-1
|
497
499
|
maxw = min(sheet.windowWidth-1, max(dispwidth(v.get('prompt')), dispwidth(str(v.get('value', '')))))
|
498
|
-
promptlen = clipdraw(scr, y
|
499
|
-
promptlen = clipdraw(scr, y
|
500
|
+
promptlen = clipdraw(scr, y, 0, v.get('prompt'), attr, w=maxw) #1947
|
501
|
+
promptlen = clipdraw(scr, y, promptlen, v.get('value', ''), attr, w=maxw)
|
500
502
|
|
501
503
|
return updater(val)
|
502
504
|
|
@@ -583,21 +585,30 @@ def input(vd, prompt, type=None, defaultLast=False, history=[], dy=0, attr=None,
|
|
583
585
|
return sheet.windowWidth-promptlen-rstatuslen-2
|
584
586
|
|
585
587
|
w = kwargs.pop('w', _drawPrompt())
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
588
|
+
restarts = 0
|
589
|
+
while restarts < 100:
|
590
|
+
#recalculate y to handle resize events
|
591
|
+
y = sheet.windowHeight-dy-1
|
592
|
+
try:
|
593
|
+
ret = vd.editText(y, promptlen, w=w,
|
594
|
+
attr=colors.color_edit_cell,
|
595
|
+
options=vd.options,
|
596
|
+
history=history,
|
597
|
+
updater=_drawPrompt,
|
598
|
+
**kwargs)
|
599
|
+
if ret:
|
600
|
+
if kwargs.get('record', True) and kwargs.get('display', True):
|
601
|
+
vd.addInputHistory(ret, type=type)
|
602
|
+
elif defaultLast:
|
603
|
+
history or vd.fail("no previous input")
|
604
|
+
ret = history[-1]
|
599
605
|
|
600
|
-
|
606
|
+
return ret
|
607
|
+
except curses.error:
|
608
|
+
vd.warning('restarting input due to resize')
|
609
|
+
restarts += 1
|
610
|
+
# if it keeps happening, it's probably not resize events, so give some debug output
|
611
|
+
vd.error(f'aborting input: y={y}, w={w}, windowHeight={sheet.windowHeight}, windowWidth={sheet.windowWidth}')
|
601
612
|
|
602
613
|
|
603
614
|
@VisiData.api
|
@@ -673,7 +684,10 @@ def editCell(self, vcolidx=None, rowidx=None, value=None, **kwargs):
|
|
673
684
|
editargs = dict(value=value, options=self.options)
|
674
685
|
|
675
686
|
editargs.update(kwargs) # update with user-specified args
|
676
|
-
|
687
|
+
try:
|
688
|
+
r = vd.editText(y, x, w, attr=colors.color_edit_cell, **editargs)
|
689
|
+
except curses.error:
|
690
|
+
vd.fail(f'aborting edit due to resize')
|
677
691
|
|
678
692
|
if rowidx >= 0: # if not header
|
679
693
|
r = col.type(r) # convert input to column type, let exceptions be raised
|
visidata/_open.py
CHANGED
@@ -183,7 +183,7 @@ def open_txt(vd, p):
|
|
183
183
|
if delimiter and delimiter in next(fp): # peek at the first line
|
184
184
|
return vd.open_tsv(p) # TSV often have .txt extension
|
185
185
|
except StopIteration:
|
186
|
-
return
|
186
|
+
return vd.newSheet(p.base_stem, 1, source=p)
|
187
187
|
return TextSheet(p.base_stem, source=p)
|
188
188
|
|
189
189
|
|
visidata/basesheet.py
CHANGED
@@ -302,8 +302,6 @@ class BaseSheet(DrawablePane):
|
|
302
302
|
'Return formatted string with *sheet* and *vd* accessible to expressions. Missing expressions return empty strings instead of error.'
|
303
303
|
return MissingAttrFormatter().format(fmt, sheet=self, vd=vd, **kwargs)
|
304
304
|
|
305
|
-
|
306
|
-
|
307
305
|
@VisiData.api
|
308
306
|
def redraw(vd):
|
309
307
|
'Clear the terminal screen and let the next draw cycle recreate the windows and redraw everything.'
|
visidata/cliptext.py
CHANGED
@@ -171,7 +171,7 @@ def _clipstr(s, dispw, trunch='', oddspacech='', combch='', modch=''):
|
|
171
171
|
|
172
172
|
#if the next character will fit
|
173
173
|
if w+chlen <= dispw:
|
174
|
-
ret +=
|
174
|
+
ret += newc
|
175
175
|
w += chlen
|
176
176
|
#move the truncation spot forward only when the truncation character can fit
|
177
177
|
if w+trunchlen <= dispw:
|
@@ -318,6 +318,9 @@ def wraptext(text, width=80, indent=''):
|
|
318
318
|
line = _markdown_to_internal(line)
|
319
319
|
chunks = re.split(internal_markup_re, line)
|
320
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
|
321
324
|
# textwrap.wrap does not handle variable-width characters #2416
|
322
325
|
for linenum, textline in enumerate(textwrap.wrap(''.join(textchunks), width=width, drop_whitespace=False)):
|
323
326
|
txt = textline
|
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,7 +236,9 @@ 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
|
|
@@ -359,7 +354,8 @@ class Column(Extensible):
|
|
359
354
|
return ret
|
360
355
|
|
361
356
|
def getCell(self, row):
|
362
|
-
'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.'''
|
363
359
|
cellval = wrapply(self.getValue, row)
|
364
360
|
typedval = wrapply(self.type, cellval)
|
365
361
|
|
@@ -398,7 +394,7 @@ class Column(Extensible):
|
|
398
394
|
dw.typedval = typedval
|
399
395
|
|
400
396
|
try:
|
401
|
-
dw.text = self.format(typedval, width=
|
397
|
+
dw.text = self.format(typedval, width=self.width) or ''
|
402
398
|
|
403
399
|
# annotate cells with raw value type in anytype columns, except for strings
|
404
400
|
if self.type is anytype and type(cellval) is not str:
|
@@ -421,7 +417,8 @@ class Column(Extensible):
|
|
421
417
|
return dw
|
422
418
|
|
423
419
|
def getDisplayValue(self, row):
|
424
|
-
'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.'''
|
425
422
|
return self.getCell(row).text
|
426
423
|
|
427
424
|
def putValue(self, row, val):
|
@@ -461,22 +458,36 @@ class Column(Extensible):
|
|
461
458
|
return vd.status('set %d cells to %d values' % (len(rows), len(values)))
|
462
459
|
|
463
460
|
def getMaxWidth(self, rows):
|
464
|
-
'Return the maximum length of any cell in column or its header (up to window width).'
|
465
|
-
|
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
|
466
463
|
nlen = dispwidth(self.name)
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
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)
|
480
491
|
|
481
492
|
|
482
493
|
# ---- basic Columns
|
visidata/deprecated.py
CHANGED
@@ -278,3 +278,8 @@ def toggleKeys(self, cols):
|
|
278
278
|
vd.optalias('disp_pixel_random', 'disp_graph_pixel_random') #2661
|
279
279
|
|
280
280
|
vd.addGlobals(deprecated_warn=deprecated_warn)
|
281
|
+
|
282
|
+
# v3.3
|
283
|
+
|
284
|
+
#vd.option('disp_expert', 'max level of options and columns to include')
|
285
|
+
#vd.option('disp_help', '')
|
@@ -42,4 +42,4 @@ def addcol_expr(sheet):
|
|
42
42
|
|
43
43
|
|
44
44
|
Sheet.addCommand(None, 'addcol-expr', 'sheet.addcol_expr()', "create new column from Python expression, updating the column's calculated values live")
|
45
|
-
Sheet.addCommand(None, 'addcol-new', 'c=
|
45
|
+
Sheet.addCommand(None, 'addcol-new', 'c=addColumnAtCursor(SettableColumn(name="", width=options.default_width)); draw(sheet._scr); cursorVisibleColIndex=visibleCols.index(c); c.name=editCell(cursorVisibleColIndex, -1); c.width=None', 'append new column, updating the column name live')
|
visidata/features/cmdpalette.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import collections
|
2
|
+
import math
|
2
3
|
from functools import partial
|
3
4
|
from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property, dispwidth
|
4
5
|
|
@@ -58,14 +59,21 @@ def inputPalette(sheet, prompt, items,
|
|
58
59
|
formatter=lambda m, item, trigger_key: f'{trigger_key} {item}',
|
59
60
|
multiple=False,
|
60
61
|
**kwargs):
|
61
|
-
if
|
62
|
+
if not vd.wantsHelp('cmdpalette'):
|
62
63
|
return vd.input(prompt,
|
63
64
|
completer=CompleteKey(sorted(item[value_key] for item in items)),
|
64
65
|
**kwargs)
|
65
66
|
|
66
67
|
bindings = dict()
|
67
68
|
|
69
|
+
#state variables for navigating display of matches
|
70
|
+
prev_value = None
|
68
71
|
tabitem = -1
|
72
|
+
offset = 0
|
73
|
+
def reset_display():
|
74
|
+
nonlocal tabitem, offset
|
75
|
+
tabitem = -1
|
76
|
+
offset = 0
|
69
77
|
|
70
78
|
def tab(n, nitems):
|
71
79
|
nonlocal tabitem
|
@@ -73,7 +81,11 @@ def inputPalette(sheet, prompt, items,
|
|
73
81
|
tabitem = (tabitem + n) % nitems
|
74
82
|
|
75
83
|
def _draw_palette(value):
|
76
|
-
|
84
|
+
nonlocal prev_value
|
85
|
+
words = value.split()
|
86
|
+
if value != prev_value:
|
87
|
+
reset_display()
|
88
|
+
prev_value = value
|
77
89
|
|
78
90
|
if multiple and words:
|
79
91
|
if value.endswith(' '):
|
@@ -83,8 +95,8 @@ def inputPalette(sheet, prompt, items,
|
|
83
95
|
finished_words = words[:-1]
|
84
96
|
unfinished_words = [words[-1]]
|
85
97
|
else:
|
86
|
-
unfinished_words = words
|
87
98
|
finished_words = []
|
99
|
+
unfinished_words = words
|
88
100
|
|
89
101
|
unuseditems = [item for item in items if item[value_key] not in finished_words]
|
90
102
|
|
@@ -92,25 +104,50 @@ def inputPalette(sheet, prompt, items,
|
|
92
104
|
|
93
105
|
h = sheet.windowHeight
|
94
106
|
w = min(100, sheet.windowWidth)
|
95
|
-
nitems = min(h-
|
107
|
+
nitems = min(h-2, sheet.options.disp_cmdpal_max)
|
108
|
+
if nitems <= 0:
|
109
|
+
return None
|
96
110
|
|
97
111
|
useditems = []
|
98
112
|
palrows = []
|
99
|
-
|
100
|
-
|
101
|
-
useditems
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
113
|
+
n_results = 0
|
114
|
+
def read_matches(offset):
|
115
|
+
nonlocal useditems, palrows, value, n_results
|
116
|
+
|
117
|
+
useditems = []
|
118
|
+
palrows = []
|
119
|
+
for m in matches[offset:offset+nitems]:
|
120
|
+
useditems.append(m.match)
|
121
|
+
palrows.append((m, m.match))
|
122
|
+
n_results += len(matches)
|
123
|
+
|
124
|
+
#List matches only, usually. But list the available choices when there's no input,
|
125
|
+
#or (if multiple is True) they've just pressed space after a word.
|
126
|
+
if not unfinished_words:
|
127
|
+
favitems = sorted([item for item in unuseditems if item not in useditems],
|
128
|
+
key=lambda item: -vd.usedInputs.get(item[value_key], 0))
|
129
|
+
for item in favitems[offset-len(palrows):offset+nitems-len(palrows)]:
|
130
|
+
palrows.append((None, item))
|
131
|
+
n_results += len(favitems)
|
132
|
+
read_matches(offset)
|
133
|
+
|
134
|
+
def change_page(dir=+1):
|
135
|
+
nonlocal offset, n_results, nitems
|
136
|
+
new_offset = offset + dir*nitems
|
137
|
+
# constrain offset to be a multiple of nitems
|
138
|
+
new_offset = min(new_offset, ((n_results-1) // nitems)*nitems)
|
139
|
+
new_offset = max(new_offset, 0)
|
140
|
+
if new_offset == offset: return None
|
141
|
+
offset = new_offset
|
109
142
|
|
110
143
|
navailitems = min(len(palrows), nitems)
|
111
144
|
|
112
145
|
bindings['^I'] = lambda *args: tab(1, navailitems) or args
|
113
146
|
bindings['KEY_BTAB'] = lambda *args: tab(-1, navailitems) or args
|
147
|
+
bindings['KEY_PPAGE'] = lambda *args: (change_page(-1) and read_matches(offset)) or args
|
148
|
+
bindings['KEY_NPAGE'] = lambda *args: (change_page(+1) and read_matches(offset)) or args
|
149
|
+
for numkey in '1234567890':
|
150
|
+
bindings.pop(numkey, None)
|
114
151
|
|
115
152
|
for i in range(nitems-len(palrows)):
|
116
153
|
palrows.append((None, None))
|
@@ -129,14 +166,13 @@ def inputPalette(sheet, prompt, items,
|
|
129
166
|
|
130
167
|
if tabitem < 0 and palrows:
|
131
168
|
_ , topitem = palrows[0]
|
132
|
-
if
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
169
|
+
if topitem:
|
170
|
+
if multiple:
|
171
|
+
bindings['^J'] = partial(accept_input_if_subset, value=topitem[value_key])
|
172
|
+
bindings[' '] = partial(add_to_input, value=topitem[value_key])
|
173
|
+
else:
|
174
|
+
bindings['^J'] = partial(accept_input, value=topitem[value_key])
|
138
175
|
elif item and i == tabitem:
|
139
|
-
if not item: return
|
140
176
|
if multiple:
|
141
177
|
bindings['^J'] = partial(accept_input_if_subset, value=item[value_key])
|
142
178
|
bindings[' '] = partial(add_to_input, value=item[value_key])
|
@@ -146,7 +182,10 @@ def inputPalette(sheet, prompt, items,
|
|
146
182
|
|
147
183
|
match_summary = formatter(m, item, trigger_key) if item else ' '
|
148
184
|
|
149
|
-
clipdraw(sheet._scr, h-nitems-
|
185
|
+
clipdraw(sheet._scr, h-nitems-2+i, 0, match_summary, attr, w=w)
|
186
|
+
attr = colors.color_cmdpalette
|
187
|
+
instr = 'Press [:keystrokes]PgUp/PgDn[/] to scroll items, [:keystrokes]Tab/Shift+Tab/Enter[/] to choose, [:keystrokes]Esc[/] to cancel.'
|
188
|
+
clipdraw(sheet._scr, h-2, 0, instr, attr, w=w)
|
150
189
|
|
151
190
|
return None
|
152
191
|
|
visidata/features/expand_cols.py
CHANGED
@@ -143,7 +143,6 @@ def contract_cols(sheet, cols, depth=1): # depth == 0 means contract all the wa
|
|
143
143
|
col.width = sheet.options.default_width
|
144
144
|
|
145
145
|
sheet.columns = [col for col in sheet.columns if getattr(col, 'origCol', None) not in origCols]
|
146
|
-
sheet.calcColLayout()
|
147
146
|
|
148
147
|
|
149
148
|
@Sheet.api
|
visidata/features/go_col.py
CHANGED
@@ -25,8 +25,7 @@ def nextColName(sheet, show_cells=True):
|
|
25
25
|
if show_cells and len(sheet.rows) > 0:
|
26
26
|
dv = c.getDisplayValue(sheet.cursorRow)
|
27
27
|
#the underscore that starts _cursor_cell excludes it from being fuzzy matched
|
28
|
-
item = AttrDict(
|
29
|
-
name=c.name,
|
28
|
+
item = AttrDict(name=c.name,
|
30
29
|
_cursor_cell=dv)
|
31
30
|
colnames.append(item)
|
32
31
|
|
visidata/features/layout.py
CHANGED
@@ -49,7 +49,6 @@ def hide_uniform_cols(sheet):
|
|
49
49
|
if i <= idx:
|
50
50
|
sheet.cursorRight(-1)
|
51
51
|
col.hide()
|
52
|
-
Sheet.clear_all_caches() #2578
|
53
52
|
|
54
53
|
Sheet.addCommand('_', 'resize-col-max', 'if cursorCol: cursorCol.toggleWidth(cursorCol.getMaxWidth(visibleRows))', 'toggle width of current column between full and default width')
|
55
54
|
Sheet.addCommand('z_', 'resize-col-input', 'width = int(input("set width= ", value=cursorCol.width)); cursorCol.setWidth(width)', 'adjust width of current column to N')
|
@@ -57,7 +56,7 @@ Sheet.addCommand('g_', 'resize-cols-max', 'for c in visibleCols: c.toggleWidth(c
|
|
57
56
|
Sheet.addCommand('gz_', 'resize-cols-input', 'width = int(input("set width= ", value=cursorCol.width)); Fanout(visibleCols).setWidth(width)', 'adjust widths of all visible columns to N')
|
58
57
|
|
59
58
|
Sheet.addCommand('-', 'hide-col', 'hide_col(cursorCol)', 'hide the current column')
|
60
|
-
Sheet.addCommand('z-', 'resize-col-half', 'cursorCol.
|
59
|
+
Sheet.addCommand('z-', 'resize-col-half', 'width = cursorCol.width if cursorCol.width is not None else options.default_width; cursorCol.setWidth(width//2)', 'reduce width of current column by half')
|
61
60
|
Sheet.addCommand('g-', 'hide-uniform-cols', 'sheet.hide_uniform_cols()', 'hide any column that has multiple rows but only one distinct value')
|
62
61
|
|
63
62
|
Sheet.addCommand('gv', 'unhide-cols', 'unhide_cols(columns, visibleRows)', 'unhide all hidden columns on current sheet')
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
import time
|
3
3
|
|
4
|
-
from visidata import vd, BaseSheet, Sheet, asyncignore, asyncthread, Path, ScopedSetattr
|
4
|
+
from visidata import vd, BaseSheet, Sheet, asyncignore, asyncthread, asyncsingle_queue, Path, ScopedSetattr
|
5
5
|
|
6
6
|
|
7
7
|
@BaseSheet.api
|
@@ -33,9 +33,9 @@ def reload_modified(sheet):
|
|
33
33
|
|
34
34
|
|
35
35
|
@Sheet.api
|
36
|
-
@
|
36
|
+
@asyncsingle_queue
|
37
37
|
def reload_rows(self):
|
38
|
-
'Reload rows from ``self.source``, keeping current columns intact. Async.'
|
38
|
+
'''Reload rows from ``self.source``, keeping current columns intact. Async. If previous calls are running, waits for them to finish.'''
|
39
39
|
with (ScopedSetattr(self, 'loading', True),
|
40
40
|
ScopedSetattr(self, 'checkCursor', lambda: True),
|
41
41
|
ScopedSetattr(self, 'cursorRowIndex', self.cursorRowIndex)):
|
visidata/form.py
CHANGED
@@ -102,7 +102,7 @@ class FormCanvas(BaseSheet):
|
|
102
102
|
@functools.wraps(VisiData.confirm)
|
103
103
|
@VisiData.api
|
104
104
|
def confirm(vd, prompt, exc=EscapeException):
|
105
|
-
'Display *prompt* on status line and demand input that starts with "Y" or "y" to proceed.
|
105
|
+
'Display *prompt* on status line and demand input that starts with "Y" or "y" to proceed. Return True when proceeding, otherwise raise *exc*, or if *exc* is falsy, return False'
|
106
106
|
if vd.options.batch:
|
107
107
|
return vd.fail('cannot confirm in batch mode: ' + prompt)
|
108
108
|
|
@@ -115,10 +115,11 @@ def confirm(vd, prompt, exc=EscapeException):
|
|
115
115
|
])
|
116
116
|
|
117
117
|
ret = FormCanvas(source=form).run(vd.scrFull)
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
confirmed = False # default is to disconfirm, if user exited the confirmation via: Esc ^C ^Q q
|
119
|
+
if ret:
|
120
|
+
yn = ret['yn'][:1]
|
121
|
+
confirmed = yn and yn in 'Yy'
|
122
|
+
if not confirmed:
|
122
123
|
msg = 'disconfirmed: ' + prompt
|
123
124
|
if exc:
|
124
125
|
raise exc(msg)
|
visidata/freqtbl.py
CHANGED
@@ -61,11 +61,11 @@ Each row on this sheet corresponds to a *bin* of rows on the source sheet that h
|
|
61
61
|
|
62
62
|
def selectRow(self, row):
|
63
63
|
# Does not create an undo-operation for the select on the source rows. The caller should create undo-information itself.
|
64
|
-
self.source.select(row.sourcerows, add_undo=False) # select all entries in the bin on the source sheet
|
64
|
+
self.source.select(row.sourcerows, status=False, add_undo=False) # select all entries in the bin on the source sheet
|
65
65
|
return super().selectRow(row) # then select the bin itself on this sheet
|
66
66
|
|
67
67
|
def unselectRow(self, row):
|
68
|
-
self.source.unselect(row.sourcerows, add_undo=False)
|
68
|
+
self.source.unselect(row.sourcerows, status=False, add_undo=False)
|
69
69
|
return super().unselectRow(row)
|
70
70
|
|
71
71
|
def addUndoSelection(self):
|
visidata/fuzzymatch.py
CHANGED
@@ -366,9 +366,10 @@ CombinedMatch = collections.namedtuple('CombinedMatch', 'score formatted match')
|
|
366
366
|
|
367
367
|
|
368
368
|
@VisiData.api
|
369
|
-
def fuzzymatch(vd, haystack:"list[dict[str, str]]", needles:"list[str]) -> list[CombinedMatch]"):
|
370
|
-
'''Perform case-insensitive
|
371
|
-
|
369
|
+
def fuzzymatch(vd, haystack:"list[dict[str, str]]", needles:"list[str]) -> list[CombinedMatch]", case_sensitive=False):
|
370
|
+
'''Perform matching that is case-insensitive by default. Return sorted list of matching dict values in haystack, augmenting the input dicts with _score:int and _positions:dict[k,set[int]] where k is each non-_ key in the haystack dict. Set *case_sensitive* to match case.'''
|
371
|
+
if not case_sensitive:
|
372
|
+
needles = [ p.lower() for p in needles]
|
372
373
|
matches = []
|
373
374
|
for h in haystack:
|
374
375
|
match = {}
|
@@ -376,9 +377,9 @@ def fuzzymatch(vd, haystack:"list[dict[str, str]]", needles:"list[str]) -> list[
|
|
376
377
|
for k, v in h.items():
|
377
378
|
if k[0] == '_': continue
|
378
379
|
positions = set()
|
379
|
-
|
380
|
+
v_match = v if case_sensitive else v.lower()
|
380
381
|
for p in needles:
|
381
|
-
mr = _fuzzymatch(
|
382
|
+
mr = _fuzzymatch(v_match, p)
|
382
383
|
if mr.score > 0:
|
383
384
|
match.setdefault(k, []).append(mr)
|
384
385
|
positions |= set(mr.positions)
|
visidata/guides/CommandsSheet.md
CHANGED
@@ -8,6 +8,7 @@ Start typing a command longname or keyword in its helpstring.
|
|
8
8
|
|
9
9
|
- [:code]Enter[/] to execute top command.
|
10
10
|
- [:code]Tab[/] to highlight top command and provide a numeric jumplist.
|
11
|
+
- [:code]PgUp[/]/[:code]PgDn[/] to scroll through commands.
|
11
12
|
|
12
13
|
When a command is highlighted:
|
13
14
|
|
visidata/help.py
CHANGED
@@ -4,7 +4,24 @@ import collections
|
|
4
4
|
from visidata import VisiData, MetaSheet, ColumnAttr, Column, BaseSheet, VisiDataMetaSheet, SuspendCurses
|
5
5
|
from visidata import vd, asyncthread, ENTER, drawcache, AttrDict, TextSheet
|
6
6
|
|
7
|
-
|
7
|
+
|
8
|
+
vd.option('disp_help_flags', 'cmdpalette guides help hints inputfield inputkeys nometacols sidebar',
|
9
|
+
'''list of helper features to enable (space-separated):
|
10
|
+
- "cmdpalette": exec-longname suggestions
|
11
|
+
- "guides": guides in sidebar
|
12
|
+
- "help": help sidebar collapsed by default
|
13
|
+
- "hints": context-sensitive hints on menu line
|
14
|
+
- "inputfield": context-sensitive help for each input field
|
15
|
+
- "inputkeys": input quick reference in sidebar
|
16
|
+
- "nometacols": hide expert columns on metasheets
|
17
|
+
- "sidebar": context-sensitive sheet help in sidebar
|
18
|
+
- "all": enable all helper features''')
|
19
|
+
|
20
|
+
|
21
|
+
@VisiData.api
|
22
|
+
def wantsHelp(vd, feat):
|
23
|
+
return feat in vd.options.disp_help_flags or 'all' in vd.options.disp_help_flags
|
24
|
+
|
8
25
|
|
9
26
|
@BaseSheet.api
|
10
27
|
def hint_basichelp(sheet):
|
@@ -99,12 +116,13 @@ class HelpPane:
|
|
99
116
|
|
100
117
|
def draw(self, scr, x=None, y=None, **kwargs):
|
101
118
|
if not scr: return
|
102
|
-
# if vd.
|
119
|
+
# if not vd.wantsHelp('statushelp'):
|
103
120
|
# if self.scr:
|
104
121
|
# self.scr.erase()
|
105
122
|
# self.scr.refresh()
|
106
123
|
# self.scr = None
|
107
124
|
# return
|
125
|
+
|
108
126
|
if y is None: y=0 # show at top of screen by default
|
109
127
|
if x is None: x=0
|
110
128
|
hneeded = self.amgr.maxHeight+3
|