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/clipboard.py
CHANGED
@@ -6,6 +6,7 @@ import sys
|
|
6
6
|
import tempfile
|
7
7
|
import functools
|
8
8
|
import os
|
9
|
+
import itertools
|
9
10
|
|
10
11
|
from visidata import VisiData, vd, asyncthread, SettableColumn
|
11
12
|
from visidata import Sheet, Path, Column
|
@@ -58,6 +59,12 @@ def syscopyValue(sheet, val):
|
|
58
59
|
|
59
60
|
vd.status('copied value to system clipboard')
|
60
61
|
|
62
|
+
@Sheet.api
|
63
|
+
def setColClipboard(sheet):
|
64
|
+
if not vd.memory.clipcells:
|
65
|
+
vd.warning("nothing to paste from clipcells")
|
66
|
+
return
|
67
|
+
sheet.cursorCol.setValuesTyped(sheet.onlySelectedRows, *vd.memory.clipcells)
|
61
68
|
|
62
69
|
@Sheet.api
|
63
70
|
def syscopyCells(sheet, cols, rows, filetype=None):
|
@@ -95,12 +102,12 @@ def sysclipValue(vd):
|
|
95
102
|
@VisiData.api
|
96
103
|
@asyncthread
|
97
104
|
def pasteFromClipboard(vd, cols, rows):
|
98
|
-
text = vd.getLastArgs() or vd.sysclipValue().strip() or vd.fail('system clipboard
|
105
|
+
text = vd.getLastArgs() or vd.sysclipValue().strip() or vd.fail('nothing to paste from system clipboard')
|
99
106
|
|
100
107
|
vd.addUndoSetValues(cols, rows)
|
101
108
|
lines = text.split('\n')
|
102
109
|
if not lines:
|
103
|
-
vd.warning('nothing to paste')
|
110
|
+
vd.warning('nothing to paste from system clipboard')
|
104
111
|
return
|
105
112
|
|
106
113
|
vs = cols[0].sheet
|
@@ -138,7 +145,7 @@ def delete_row(sheet, rowidx):
|
|
138
145
|
def paste_after(sheet, rowidx):
|
139
146
|
'Paste rows from *vd.cliprows* at *rowidx*.'
|
140
147
|
if not vd.memory.cliprows: #1793
|
141
|
-
vd.warning('nothing to paste')
|
148
|
+
vd.warning('nothing to paste from cliprows')
|
142
149
|
return
|
143
150
|
|
144
151
|
for col in vd.memory.clipcols[sheet.nVisibleCols:]:
|
@@ -168,8 +175,8 @@ Sheet.addCommand('P', 'paste-before', 'paste_after(cursorRowIndex-1)', 'paste cl
|
|
168
175
|
|
169
176
|
Sheet.addCommand('gy', 'copy-selected', 'copyRows(onlySelectedRows)', 'yank (copy) selected rows to clipboard')
|
170
177
|
|
171
|
-
Sheet.addCommand('zy', 'copy-cell', 'copyCells(cursorCol, [cursorRow]); vd.
|
172
|
-
Sheet.addCommand('zp', 'paste-cell', 'cursorCol.setValuesTyped([cursorRow], vd.memory.clipval)', 'set contents of current cell to last clipboard value')
|
178
|
+
Sheet.addCommand('zy', 'copy-cell', 'copyCells(cursorCol, [cursorRow]); vd.memoValue("clipval", cursorTypedValue, cursorDisplay)', 'yank (copy) current cell to clipboard')
|
179
|
+
Sheet.addCommand('zp', 'paste-cell', 'cursorCol.setValuesTyped([cursorRow], vd.memory.clipval) if vd.memory.clipval else vd.warning("nothing to paste from clipval")', 'set contents of current cell to last clipboard value')
|
173
180
|
|
174
181
|
Sheet.addCommand('d', 'delete-row', 'delete_row(cursorRowIndex); defer and cursorDown(1)', 'delete current row')
|
175
182
|
Sheet.addCommand('gd', 'delete-selected', 'deleteSelected()', 'delete selected rows')
|
@@ -183,7 +190,7 @@ Sheet.bindkey('zP', 'syspaste-cells')
|
|
183
190
|
Sheet.addCommand('gzP', 'syspaste-cells-selected', 'pasteFromClipboard(visibleCols[cursorVisibleColIndex:], someSelectedRows)', 'paste from system clipboard to selected cells')
|
184
191
|
|
185
192
|
Sheet.addCommand('gzy', 'copy-cells', 'copyCells(cursorCol, onlySelectedRows)', 'yank (copy) contents of current column for selected rows to clipboard')
|
186
|
-
Sheet.addCommand('gzp', 'setcol-clipboard', '
|
193
|
+
Sheet.addCommand('gzp', 'setcol-clipboard', 'setColClipboard()', 'set cells of current column for selected rows to last clipboard value')
|
187
194
|
|
188
195
|
Sheet.addCommand('Y', 'syscopy-row', 'syscopyCells(visibleCols, [cursorRow])', 'yank (copy) current row to system clipboard (using options.clipboard_copy_cmd)')
|
189
196
|
|
visidata/cliptext.py
CHANGED
@@ -71,7 +71,7 @@ def iterchunks(s, literal=False):
|
|
71
71
|
link = attrstack[-1]['link']
|
72
72
|
|
73
73
|
if chunk.startswith('[:onclick '):
|
74
|
-
attrstack.append(dict(link=chunk[
|
74
|
+
attrstack.append(dict(link=chunk[2:-1], cattr=cattr.update(colors.clickable)))
|
75
75
|
continue
|
76
76
|
elif chunk == '[:]': # clear stack, keep origattr
|
77
77
|
if len(attrstack) > 1:
|
@@ -235,11 +235,12 @@ def clipdraw_chunks(scr, y, x, chunks, cattr:ColorAttr=ColorAttr(), w=None, clea
|
|
235
235
|
|
236
236
|
try:
|
237
237
|
for colorstate, chunk in chunks:
|
238
|
-
if
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
238
|
+
if colorstate:
|
239
|
+
if isinstance(colorstate, str):
|
240
|
+
cattr = cattr.update(colors.get_color(colorstate), 100)
|
241
|
+
else:
|
242
|
+
cattr = origattr.update(colorstate['cattr'], 100)
|
243
|
+
link = colorstate['link']
|
243
244
|
|
244
245
|
if not chunk:
|
245
246
|
continue
|
visidata/cmdlog.py
CHANGED
@@ -6,18 +6,18 @@ import visidata
|
|
6
6
|
|
7
7
|
vd.option('replay_wait', 0.0, 'time to wait between replayed commands, in seconds', sheettype=None)
|
8
8
|
vd.theme_option('disp_replay_play', '▶', 'status indicator for active replay')
|
9
|
+
vd.theme_option('disp_replay_record', '⏺', 'status indicator for macro record')
|
9
10
|
vd.theme_option('color_status_replay', 'green', 'color of replay status indicator')
|
10
11
|
|
11
12
|
# prefixes which should not be logged
|
12
13
|
nonLogged = '''forget exec-longname undo redo quit
|
13
14
|
show error errors statuses options threads jump
|
14
15
|
replay cancel save-cmdlog macro cmdlog-sheet menu repeat reload-every
|
15
|
-
|
16
|
+
search scroll prev next page start end zoom visibility sidebar
|
16
17
|
mouse suspend redraw no-op help syscopy sysopen profile toggle'''.split()
|
17
18
|
|
18
19
|
vd.option('rowkey_prefix', 'キ', 'string prefix for rowkey in the cmdlog', sheettype=None)
|
19
20
|
|
20
|
-
vd.activeCommand = UNLOADED
|
21
21
|
vd._nextCommands = [] # list[str|CommandLogRow] for vd.queueCommand
|
22
22
|
|
23
23
|
CommandLogRow = namedlist('CommandLogRow', 'sheet col row longname input keystrokes comment undofuncs'.split())
|
@@ -68,13 +68,14 @@ def indexMatch(L, func):
|
|
68
68
|
if func(x):
|
69
69
|
return i
|
70
70
|
|
71
|
-
def keystr(k):
|
72
|
-
return vd.options.rowkey_prefix+','.join(map(str, k))
|
73
|
-
|
74
71
|
@VisiData.api
|
75
|
-
def isLoggableCommand(vd,
|
72
|
+
def isLoggableCommand(vd, cmd):
|
73
|
+
'Return whether command should be logged to the cmdlog, depending if it has a prefix in nonLogged, or was defined with replay=False.'
|
74
|
+
if not cmd.replayable:
|
75
|
+
return False
|
76
|
+
|
76
77
|
for n in nonLogged:
|
77
|
-
if longname.startswith(n):
|
78
|
+
if cmd.longname.startswith(n):
|
78
79
|
return False
|
79
80
|
return True
|
80
81
|
|
@@ -94,15 +95,22 @@ def moveToRow(vs, rowstr):
|
|
94
95
|
return True
|
95
96
|
|
96
97
|
@Sheet.api
|
97
|
-
def getRowIndexFromStr(vs,
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
def getRowIndexFromStr(vs, row):
|
99
|
+
prefix = vd.options.rowkey_prefix
|
100
|
+
index = None
|
101
|
+
if isinstance(row, int):
|
102
|
+
index = row
|
103
|
+
elif isinstance(row, str) and row.startswith(prefix):
|
104
|
+
rowk = row[len(prefix):]
|
105
|
+
index = indexMatch(vs.rows, lambda r,vs=vs,rowk=rowk: rowk == ','.join(map(str, vs.rowkey(r))))
|
106
|
+
else:
|
107
|
+
try:
|
108
|
+
index = int(row)
|
109
|
+
except ValueError:
|
110
|
+
vd.warning('invalid type for row index')
|
111
|
+
|
112
|
+
return index
|
101
113
|
|
102
|
-
try:
|
103
|
-
return int(rowstr)
|
104
|
-
except ValueError:
|
105
|
-
return None
|
106
114
|
|
107
115
|
@Sheet.api
|
108
116
|
def moveToCol(vs, col):
|
@@ -126,8 +134,7 @@ def commandCursor(sheet, execstr):
|
|
126
134
|
colname, rowname = '', ''
|
127
135
|
contains = lambda s, *substrs: any((a in s) for a in substrs)
|
128
136
|
if contains(execstr, 'cursorTypedValue', 'cursorDisplay', 'cursorValue', 'cursorCell', 'cursorRow') and sheet.nRows > 0:
|
129
|
-
|
130
|
-
rowname = keystr(k) if k else sheet.cursorRowIndex
|
137
|
+
rowname = sheet.cursorRowIndex
|
131
138
|
|
132
139
|
if contains(execstr, 'cursorTypedValue', 'cursorDisplay', 'cursorValue', 'cursorCell', 'cursorCol', 'cursorVisibleCol', 'ColumnAtCursor'):
|
133
140
|
if sheet.cursorCol:
|
@@ -177,11 +184,12 @@ class CommandLogBase:
|
|
177
184
|
comment = vd.currentReplayRow.comment if vd.currentReplayRow else cmd.helpstr
|
178
185
|
vd.activeCommand = self.newRow(sheet=sheetname,
|
179
186
|
col=colname,
|
180
|
-
row=
|
187
|
+
row=rowname,
|
181
188
|
keystrokes=keystrokes,
|
182
189
|
input=args,
|
183
190
|
longname=cmd.longname,
|
184
191
|
comment=comment,
|
192
|
+
replayable=cmd.replayable,
|
185
193
|
undofuncs=[])
|
186
194
|
|
187
195
|
def afterExecSheet(self, sheet, escaped, err):
|
@@ -189,15 +197,12 @@ class CommandLogBase:
|
|
189
197
|
if not vd.activeCommand: # nothing to record
|
190
198
|
return
|
191
199
|
|
192
|
-
if err:
|
193
|
-
vd.activeCommand[-1] += ' [%s]' % err
|
194
|
-
|
195
200
|
if escaped:
|
196
201
|
vd.activeCommand = None
|
197
202
|
return
|
198
203
|
|
199
204
|
# remove user-aborted commands and simple movements (unless first command on the sheet, which created the sheet)
|
200
|
-
if not sheet.cmdlog_sheet.rows or vd.isLoggableCommand(vd.activeCommand
|
205
|
+
if not sheet.cmdlog_sheet.rows or vd.isLoggableCommand(vd.activeCommand):
|
201
206
|
if isLoggableSheet(sheet): # don't record actions from cmdlog or other internal sheets on global cmdlog
|
202
207
|
self.addRow(vd.activeCommand) # add to global cmdlog
|
203
208
|
sheet.cmdlog_sheet.addRow(vd.activeCommand) # add to sheet-specific cmdlog
|
@@ -207,7 +212,7 @@ class CommandLogBase:
|
|
207
212
|
def openHook(self, vs, src):
|
208
213
|
while isinstance(src, BaseSheet):
|
209
214
|
src = src.source
|
210
|
-
r = self.newRow(keystrokes='o', input=str(src), longname='open-file')
|
215
|
+
r = self.newRow(keystrokes='o', input=str(src), longname='open-file', replayable=True)
|
211
216
|
vs.cmdlog_sheet.addRow(r)
|
212
217
|
self.addRow(r)
|
213
218
|
|
@@ -262,6 +267,8 @@ def replayOne(vd, r):
|
|
262
267
|
'Replay the command in one given row.'
|
263
268
|
vd.currentReplayRow = r
|
264
269
|
longname = getattr(r, 'longname', None)
|
270
|
+
if longname is None and getattr(r, 'keystrokes', None) is None:
|
271
|
+
vd.fail('failed to find command to replay')
|
265
272
|
|
266
273
|
if r.sheet and longname not in ['set-option', 'unset-option']:
|
267
274
|
vs = vd.getSheet(r.sheet) or vd.error('no sheet named %s' % r.sheet)
|
@@ -321,6 +328,7 @@ class DisableAsync:
|
|
321
328
|
def replay_sync(vd, cmdlog):
|
322
329
|
'Replay all commands in *cmdlog*.'
|
323
330
|
with vd.DisableAsync():
|
331
|
+
vd.sync() #2352 let cmdlog finish loading
|
324
332
|
cmdlog.cursorRowIndex = 0
|
325
333
|
vd.currentReplay = cmdlog
|
326
334
|
|
@@ -370,15 +378,19 @@ def getLastArgs(vd):
|
|
370
378
|
def setLastArgs(vd, args):
|
371
379
|
'Set user input on last command, if not already set.'
|
372
380
|
# only set if not already set (second input usually confirmation)
|
373
|
-
if
|
381
|
+
if vd.activeCommand:
|
374
382
|
if not vd.activeCommand.input:
|
375
383
|
vd.activeCommand.input = args
|
376
384
|
|
377
385
|
|
378
386
|
@VisiData.property
|
379
387
|
def replayStatus(vd):
|
388
|
+
if vd.macroMode:
|
389
|
+
return f'|[:error] {len(vd.macroMode)} {vd.options.disp_replay_record} [:]'
|
390
|
+
|
380
391
|
if vd._nextCommands:
|
381
|
-
return f'
|
392
|
+
return f'|[:status_replay] {len(vd._nextCommands)} {vd.options.disp_replay_play} [:]'
|
393
|
+
|
382
394
|
return ''
|
383
395
|
|
384
396
|
|
@@ -412,7 +424,7 @@ def shortcut(self):
|
|
412
424
|
pass
|
413
425
|
|
414
426
|
try:
|
415
|
-
return self.cmdlog_sheet.rows[0].keystrokes
|
427
|
+
return self.cmdlog_sheet.rows[0].keystrokes or '' #2293
|
416
428
|
except Exception:
|
417
429
|
pass
|
418
430
|
|
@@ -429,7 +441,7 @@ def cmdlog(vd):
|
|
429
441
|
|
430
442
|
@VisiData.property
|
431
443
|
def modifyCommand(vd):
|
432
|
-
if vd.activeCommand
|
444
|
+
if vd.activeCommand and vd.isLoggableCommand(vd.activeCommand):
|
433
445
|
return vd.activeCommand
|
434
446
|
if not vd.cmdlog.rows:
|
435
447
|
return None
|
@@ -477,6 +489,7 @@ CommandLogJsonl.addCommand('gx', 'replay-all', 'vd.replay(sheet)', 'replay conte
|
|
477
489
|
CommandLog.options.json_sort_keys = False
|
478
490
|
CommandLog.options.encoding = 'utf-8'
|
479
491
|
CommandLogJsonl.options.json_sort_keys = False
|
492
|
+
CommandLogJsonl.options.regex_skip = r'^(//|#).*'
|
480
493
|
|
481
494
|
vd.addGlobals(CommandLogBase=CommandLogBase, CommandLogRow=CommandLogRow)
|
482
495
|
|
visidata/column.py
CHANGED
@@ -20,13 +20,13 @@ class InProgress(Exception):
|
|
20
20
|
|
21
21
|
INPROGRESS = TypedExceptionWrapper(None, exception=InProgress()) # sentinel
|
22
22
|
|
23
|
-
vd.option('col_cache_size', 0, 'max number of cache entries in each cached column'
|
24
|
-
vd.option('disp_formatter', 'generic', 'formatter to create the text in each cell (also used by text savers)', replay=True
|
25
|
-
vd.option('disp_displayer', 'generic', 'displayer to render the text in each cell', replay=False
|
23
|
+
vd.option('col_cache_size', 0, 'max number of cache entries in each cached column')
|
24
|
+
vd.option('disp_formatter', 'generic', 'formatter to create the text in each cell (also used by text savers)', replay=True)
|
25
|
+
vd.option('disp_displayer', 'generic', 'displayer to render the text in each cell', replay=False)
|
26
26
|
|
27
27
|
|
28
28
|
class DisplayWrapper:
|
29
|
-
def __init__(self, value=None, *, typedval=None, text=
|
29
|
+
def __init__(self, value=None, *, typedval=None, text='', note=None, notecolor=None, error=None):
|
30
30
|
self.value = value # actual value (any type)
|
31
31
|
self.typedval = typedval # consistently typed value (or None)
|
32
32
|
self.text = text # displayed string
|
@@ -86,7 +86,7 @@ class Column(Extensible):
|
|
86
86
|
self.formatter = ''
|
87
87
|
self.displayer = ''
|
88
88
|
self.defer = False
|
89
|
-
self.
|
89
|
+
self.disp_expert = 0 # auto-hide if options.disp_expert less than col.disp_expert
|
90
90
|
|
91
91
|
self.setCache(cache)
|
92
92
|
for k, v in kwargs.items():
|
@@ -276,7 +276,7 @@ class Column(Extensible):
|
|
276
276
|
The 'full' displayer allows formatting like [:color].
|
277
277
|
'''
|
278
278
|
if width is not None and width > 1 and vd.isNumeric(self):
|
279
|
-
yield from iterchunks(text.rjust(width-2))
|
279
|
+
yield from iterchunks(dw.text.rjust(width-2))
|
280
280
|
else:
|
281
281
|
yield from iterchunks(dw.text)
|
282
282
|
|
@@ -368,7 +368,7 @@ class Column(Extensible):
|
|
368
368
|
dispval = options.disp_error_val
|
369
369
|
return DisplayWrapper(cellval.val, error=exc.stacktrace,
|
370
370
|
text=dispval,
|
371
|
-
note=options.
|
371
|
+
note=f'[:onclick error-cell]{options.disp_note_getexc}[:]',
|
372
372
|
notecolor='color_error')
|
373
373
|
elif typedval.val is None: # early out for strict None
|
374
374
|
return DisplayWrapper(None, text='', # force empty display for None
|
@@ -377,18 +377,17 @@ class Column(Extensible):
|
|
377
377
|
elif isinstance(typedval, TypedExceptionWrapper): # calc succeeded, type failed
|
378
378
|
return DisplayWrapper(typedval.val, text=str(cellval),
|
379
379
|
error=typedval.stacktrace,
|
380
|
-
note=options.
|
380
|
+
note=f'[:onclick error-cell]{options.disp_note_typeexc}[:]',
|
381
381
|
notecolor='color_warning')
|
382
382
|
else:
|
383
383
|
return DisplayWrapper(typedval.val, text=str(typedval.val),
|
384
|
-
error='unknown',
|
385
|
-
note=options.
|
384
|
+
error=['unknown'],
|
385
|
+
note=f'[:onclick error-cell]{options.disp_note_typeexc}[:]',
|
386
386
|
notecolor='color_warning')
|
387
|
-
|
388
387
|
elif isinstance(typedval, threading.Thread):
|
389
388
|
return DisplayWrapper(None,
|
390
389
|
text=options.disp_pending,
|
391
|
-
note=options.
|
390
|
+
note=options.disp_note_pending,
|
392
391
|
notecolor='color_note_pending')
|
393
392
|
|
394
393
|
dw = DisplayWrapper(cellval)
|
@@ -411,10 +410,10 @@ class Column(Extensible):
|
|
411
410
|
dw.text = str(cellval)
|
412
411
|
except Exception as e:
|
413
412
|
dw.text = str(e)
|
414
|
-
dw.note = options.
|
413
|
+
dw.note = options.disp_note_fmtexc
|
415
414
|
dw.notecolor = 'color_warning'
|
416
415
|
|
417
|
-
# dw.display = self.display(dw) # set during draw()
|
416
|
+
# dw.display = self.display(dw) # set during draw() once colwidth is known
|
418
417
|
return dw
|
419
418
|
|
420
419
|
def getDisplayValue(self, row):
|
@@ -446,6 +445,7 @@ class Column(Extensible):
|
|
446
445
|
self.recalc()
|
447
446
|
return vd.status('set %d cells to %d values' % (len(rows), len(values)))
|
448
447
|
|
448
|
+
@asyncthread
|
449
449
|
def setValuesTyped(self, rows, *values):
|
450
450
|
'Set values on this column for *rows* to *values*, coerced to column type, recycling values as needed to fill *rows*. Abort on type exception.'
|
451
451
|
vd.addUndoSetValues([self], rows)
|
@@ -537,39 +537,6 @@ def SubColumnItem(idx, c, **kwargs):
|
|
537
537
|
kwargs['name'] = c.name
|
538
538
|
return SubColumnFunc(origcol=c, subfunc=getitemdef, expr=idx, **kwargs)
|
539
539
|
|
540
|
-
class ExprColumn(Column):
|
541
|
-
'Column using *expr* to derive the value from each row.'
|
542
|
-
def __init__(self, name, expr=None, **kwargs):
|
543
|
-
super().__init__(name, **kwargs)
|
544
|
-
self.expr = expr or name
|
545
|
-
self.ncalcs = 0
|
546
|
-
self.totaltime = 0
|
547
|
-
self.maxtime = 0
|
548
|
-
|
549
|
-
def calcValue(self, row):
|
550
|
-
t0 = time.perf_counter()
|
551
|
-
r = self.sheet.evalExpr(self.compiledExpr, row, col=self)
|
552
|
-
t1 = time.perf_counter()
|
553
|
-
self.ncalcs += 1
|
554
|
-
self.maxtime = max(self.maxtime, t1-t0)
|
555
|
-
self.totaltime += (t1-t0)
|
556
|
-
return r
|
557
|
-
|
558
|
-
def putValue(self, row, val):
|
559
|
-
a = self.getDisplayValue(row)
|
560
|
-
b = self.format(self.type(val))
|
561
|
-
if a != b:
|
562
|
-
vd.warning("Cannot change value of calculated column. Use `'` to freeze column.")
|
563
|
-
|
564
|
-
@property
|
565
|
-
def expr(self):
|
566
|
-
return self._expr
|
567
|
-
|
568
|
-
@expr.setter
|
569
|
-
def expr(self, expr):
|
570
|
-
self.compiledExpr = compile(expr, '<expr>', 'eval') if expr else None
|
571
|
-
self._expr = expr
|
572
|
-
|
573
540
|
|
574
541
|
class SettableColumn(Column):
|
575
542
|
'Column using rowid to store and retrieve values internally.'
|
@@ -592,7 +559,6 @@ vd.addGlobals(
|
|
592
559
|
getitemdef=getitemdef,
|
593
560
|
AttrColumn=AttrColumn,
|
594
561
|
ItemColumn=ItemColumn,
|
595
|
-
ExprColumn=ExprColumn,
|
596
562
|
SettableColumn=SettableColumn,
|
597
563
|
SubColumnFunc=SubColumnFunc,
|
598
564
|
SubColumnItem=SubColumnItem,
|
@@ -601,6 +567,5 @@ vd.addGlobals(
|
|
601
567
|
# synonyms
|
602
568
|
ColumnItem=ItemColumn,
|
603
569
|
ColumnAttr=AttrColumn,
|
604
|
-
ColumnExpr=ExprColumn,
|
605
570
|
DisplayWrapper=DisplayWrapper
|
606
571
|
)
|
visidata/ddw/regex.ddw
CHANGED
@@ -53,5 +53,6 @@
|
|
53
53
|
{"x": 42, "y": 4, "text": "exactly m", "color": "", "tags": [], "group": ""}
|
54
54
|
{"x": 4, "y": 0, "text": "Character classes", "color": "bold underline", "tags": [], "group": ""}
|
55
55
|
{"x": 35, "y": 0, "text": "Repetition", "color": "bold underline", "tags": [], "group": ""}
|
56
|
-
{"x": 2, "y":
|
57
|
-
{"x": 6, "y":
|
56
|
+
{"x": 2, "y": 15, "text": "For full documentation on Python regular expressions, see", "color": "", "tags": [], "group": ""}
|
57
|
+
{"x": 6, "y": 16, "text": "https://docs.python.org/3/library/re.html", "color": "underline", "tags": [], "group": "", "href": "https://docs.python.org/3/library/re.html"}
|
58
|
+
{"x": 2, "y": 14, "text": "Note: regex operations apply to the displayed value in a cell", "color": "", "tags": [], "group": ""}
|
visidata/deprecated.py
CHANGED
@@ -165,7 +165,7 @@ exceptionCaught = deprecated('2.6', 'vd.exceptionCaught')(vd.exceptionCaught)
|
|
165
165
|
openSource = deprecated('2.6', 'vd.openSource')(vd.openSource)
|
166
166
|
globalCommand = visidata.BaseSheet.addCommand
|
167
167
|
visidata.Sheet.StaticColumn = deprecated('2.11', 'Sheet.freeze_col')(visidata.Sheet.freeze_col)
|
168
|
-
visidata.Path.open_text = deprecated('3.0', 'visidata.Path.open')(visidata.Path.open)
|
168
|
+
#visidata.Path.open_text = deprecated('3.0', 'visidata.Path.open')(visidata.Path.open) # undeprecated in 3.1
|
169
169
|
|
170
170
|
vd.sysclip_value = deprecated('3.0', 'vd.sysclipValue')(vd.sysclipValue)
|
171
171
|
|
@@ -237,4 +237,16 @@ def setValueSafe(self, row, value):
|
|
237
237
|
def checkCursorNoExceptions(sheet):
|
238
238
|
return vd.callNoExceptions(sheet.checkCursor)
|
239
239
|
|
240
|
-
vd.
|
240
|
+
@deprecated('3.1', 'vd.memoValue(name, value, displayvalue)')
|
241
|
+
@VisiData.api
|
242
|
+
def memo(vd, name, col, row):
|
243
|
+
return vd.memoValue(name, col.getTypedValue(row), col.getDisplayValue(row))
|
244
|
+
|
245
|
+
alias('view-cell', 'pyobj-cell')
|
246
|
+
|
247
|
+
vd.optalias('textwrap_cells', 'disp_wrap_max_lines', 3) # wordwrap text for multiline rows
|
248
|
+
|
249
|
+
@deprecated('3.1', 'sheet.rowname(row)')
|
250
|
+
@visidata.TableSheet.api
|
251
|
+
def keystr(sheet, row):
|
252
|
+
return sheet.rowname(row)
|
visidata/editor.py
CHANGED
@@ -37,6 +37,7 @@ def launchEditor(vd, *args):
|
|
37
37
|
def launchBrowser(vd, *args):
|
38
38
|
'Launch $BROWSER with *args* as arguments.'
|
39
39
|
browser = os.environ.get('BROWSER') or vd.fail('no $BROWSER for %s' % args[0])
|
40
|
+
vd.status('opening ' + args[0])
|
40
41
|
args = [browser] + list(args)
|
41
42
|
subprocess.call(args)
|
42
43
|
|
visidata/errors.py
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
from copy import copy
|
2
|
+
from visidata import vd, Sheet, Progress, asyncthread, options, UNLOADED
|
3
|
+
|
4
|
+
@Sheet.api
|
5
|
+
@asyncthread
|
6
|
+
def sortSelected(self, ordering):
|
7
|
+
"""sort the rows that are selected, in place so that each originally-selected row takes the place of another.
|
8
|
+
*ordering* is a list of tuples: (col_name_str, reverse_sort_bool) (string and bool)"""
|
9
|
+
if self.rows is UNLOADED or not self.rows:
|
10
|
+
return
|
11
|
+
if self.nSelectedRows == 0:
|
12
|
+
vd.fail('no rows selected')
|
13
|
+
return
|
14
|
+
|
15
|
+
if options.undo:
|
16
|
+
vd.status('undo added')
|
17
|
+
vd.addUndo(setattr, self, 'rows', copy(self.rows))
|
18
|
+
|
19
|
+
# temporary: save data to test integrity after the sort
|
20
|
+
rows_initial = { self.rowid(r) for r in self.rows }
|
21
|
+
selected_initial = set(self._selectedRows.keys())
|
22
|
+
|
23
|
+
with Progress(gerund='sorting', total=self.nSelectedRows) as prog:
|
24
|
+
selected = list(self._selectedRows.values())
|
25
|
+
selected_rowids = set(self._selectedRows.keys())
|
26
|
+
|
27
|
+
selected_indices = []
|
28
|
+
for i, r in enumerate(self.rows):
|
29
|
+
rowid = self.rowid(r)
|
30
|
+
if rowid in selected_rowids:
|
31
|
+
selected_indices.append(i)
|
32
|
+
selected_rowids.remove(rowid)
|
33
|
+
if len(selected_rowids) == 0:
|
34
|
+
break
|
35
|
+
try:
|
36
|
+
def _sortkey(r):
|
37
|
+
prog.addProgress(1)
|
38
|
+
return self.sortkey(r, ordering=ordering)
|
39
|
+
sorted_selected = sorted(selected, key=_sortkey)
|
40
|
+
except TypeError as e:
|
41
|
+
vd.warning('sort incomplete due to TypeError; change column type')
|
42
|
+
vd.exceptionCaught(e, status=False)
|
43
|
+
return
|
44
|
+
for selected_idx, r in zip(selected_indices, sorted_selected, strict=True):
|
45
|
+
self.rows[selected_idx] = r
|
46
|
+
|
47
|
+
# temporary: make sure we haven't lost any rows in the sheet or the selection
|
48
|
+
rows_final = { self.rowid(r) for r in self.rows }
|
49
|
+
selected_final = set(self._selectedRows.keys())
|
50
|
+
assert(rows_initial == rows_final)
|
51
|
+
assert(selected_initial == selected_final)
|
52
|
+
|
53
|
+
Sheet.addCommand(None, 'sort-selected-asc', 'sortSelected([(cursorCol, False)])', 'sort selected rows by the current column, in ascending order')
|
54
|
+
Sheet.addCommand(None, 'sort-selected-desc', 'sortSelected([(cursorCol, True)])', 'sort selected rows by the current column, in descending order')
|
visidata/expr.py
CHANGED
@@ -1,9 +1,50 @@
|
|
1
|
-
|
1
|
+
import time
|
2
|
+
|
3
|
+
from visidata import Progress, Sheet, Column, asyncthread, vd, Column
|
4
|
+
|
5
|
+
|
6
|
+
class ExprColumn(Column):
|
7
|
+
'Column using *expr* to derive the value from each row.'
|
8
|
+
def __init__(self, name, expr=None, **kwargs):
|
9
|
+
super().__init__(name, **kwargs)
|
10
|
+
self.expr = expr or name
|
11
|
+
self.ncalcs = 0
|
12
|
+
self.totaltime = 0
|
13
|
+
self.maxtime = 0
|
14
|
+
|
15
|
+
def calcValue(self, row):
|
16
|
+
t0 = time.perf_counter()
|
17
|
+
r = self.sheet.evalExpr(self.compiledExpr, row, col=self, curcol=self)
|
18
|
+
t1 = time.perf_counter()
|
19
|
+
self.ncalcs += 1
|
20
|
+
self.maxtime = max(self.maxtime, t1-t0)
|
21
|
+
self.totaltime += (t1-t0)
|
22
|
+
return r
|
23
|
+
|
24
|
+
def putValue(self, row, val):
|
25
|
+
a = self.getDisplayValue(row)
|
26
|
+
b = self.format(self.type(val))
|
27
|
+
if a != b:
|
28
|
+
vd.warning("Cannot change value of calculated column. Use `'` to freeze column.")
|
29
|
+
|
30
|
+
@property
|
31
|
+
def expr(self):
|
32
|
+
return self._expr
|
33
|
+
|
34
|
+
@expr.setter
|
35
|
+
def expr(self, expr):
|
36
|
+
self.compiledExpr = compile(expr, '<expr>', 'eval') if expr else None
|
37
|
+
self._expr = expr
|
2
38
|
|
3
39
|
|
4
40
|
class CompleteExpr:
|
5
41
|
def __init__(self, sheet=None):
|
6
|
-
self.
|
42
|
+
self.varnames = []
|
43
|
+
if sheet:
|
44
|
+
self.varnames.extend(sorted(col.name for col in sheet.columns))
|
45
|
+
self.varnames.extend(sorted(x for x in vd.getGlobals()))
|
46
|
+
for c in vd.contexts:
|
47
|
+
self.varnames.extend(sorted(x for x in dir(c)))
|
7
48
|
|
8
49
|
def __call__(self, val, state):
|
9
50
|
i = len(val)-1
|
@@ -19,29 +60,35 @@ class CompleteExpr:
|
|
19
60
|
base = val[:i+1]
|
20
61
|
partial = val[i+1:]
|
21
62
|
|
22
|
-
|
23
|
-
|
24
|
-
varnames.extend(sorted((base+x) for x in vd.getGlobals() if x.startswith(partial)))
|
25
|
-
|
26
|
-
# Remove duplicate tabbing suggestions
|
27
|
-
varnames_dict = {var:None for var in varnames}
|
63
|
+
# Remove unmatching and duplicate completions
|
64
|
+
varnames_dict = {x:None for x in self.varnames if x.startswith(partial)}
|
28
65
|
varnames = list(varnames_dict.keys())
|
29
|
-
|
66
|
+
|
67
|
+
if not varnames:
|
68
|
+
return val
|
69
|
+
|
70
|
+
return base + varnames[state%len(varnames)]
|
30
71
|
|
31
72
|
|
32
73
|
@Column.api
|
33
74
|
@asyncthread
|
34
|
-
def setValuesFromExpr(self, rows, expr):
|
75
|
+
def setValuesFromExpr(self, rows, expr, **kwargs):
|
35
76
|
'Set values in this column for *rows* to the result of the Python expression *expr* applied to each row.'
|
36
77
|
compiledExpr = compile(expr, '<expr>', 'eval')
|
37
78
|
vd.addUndoSetValues([self], rows)
|
79
|
+
nset = 0
|
38
80
|
for row in Progress(rows, 'setting'):
|
39
81
|
# Note: expressions that are only calculated once, do not need to pass column identity
|
40
82
|
# they can reference their "previous selves" once without causing a recursive problem
|
41
|
-
|
42
|
-
|
83
|
+
try:
|
84
|
+
v = self.sheet.evalExpr(compiledExpr, row, **kwargs)
|
85
|
+
self.setValue(row, v)
|
86
|
+
nset += 1
|
87
|
+
except Exception as e:
|
88
|
+
vd.exceptionCaught(e)
|
89
|
+
|
43
90
|
self.recalc()
|
44
|
-
vd.status('set
|
91
|
+
vd.status(f'set {nset} values = {expr}')
|
45
92
|
|
46
93
|
|
47
94
|
@Sheet.api
|
@@ -49,14 +96,18 @@ def inputExpr(self, prompt, *args, **kwargs):
|
|
49
96
|
return vd.input(prompt, "expr", *args, completer=CompleteExpr(self), **kwargs)
|
50
97
|
|
51
98
|
|
52
|
-
Sheet.addCommand('=', 'addcol-expr', 'addColumnAtCursor(ExprColumn(inputExpr("new column expr="), curcol=cursorCol))', 'create new column from Python expression, with column names as variables')
|
53
|
-
Sheet.addCommand('g=', 'setcol-expr', 'cursorCol.setValuesFromExpr(someSelectedRows, inputExpr("set selected="))', 'set current column for selected rows to result of Python expression')
|
54
|
-
Sheet.addCommand('z=', 'setcell-expr', 'cursorCol.setValues([cursorRow], evalExpr(inputExpr("set expr="), cursorRow,))', 'evaluate Python expression on current row and set current cell with result of Python expression')
|
99
|
+
Sheet.addCommand('=', 'addcol-expr', 'addColumnAtCursor(ExprColumn(inputExpr("new column expr="), col=cursorCol, curcol=cursorCol))', 'create new column from Python expression, with column names as variables')
|
100
|
+
Sheet.addCommand('g=', 'setcol-expr', 'cursorCol.setValuesFromExpr(someSelectedRows, inputExpr("set selected="), curcol=cursorCol)', 'set current column for selected rows to result of Python expression')
|
101
|
+
Sheet.addCommand('z=', 'setcell-expr', 'cursorCol.setValues([cursorRow], evalExpr(inputExpr("set expr="), row=cursorRow, curcol=cursorCol))', 'evaluate Python expression on current row and set current cell with result of Python expression')
|
55
102
|
Sheet.addCommand('gz=', 'setcol-iter', 'cursorCol.setValues(someSelectedRows, *list(itertools.islice(eval(input("set column= ", "expr", completer=CompleteExpr())), len(someSelectedRows))))', 'set current column for selected rows to the items in result of Python sequence expression')
|
56
103
|
|
57
|
-
Sheet.addCommand(None, 'show-expr', 'status(evalExpr(inputExpr("show expr="), cursorRow))', 'evaluate Python expression on current row and show result on status line')
|
104
|
+
Sheet.addCommand(None, 'show-expr', 'status(evalExpr(inputExpr("show expr="), row=cursorRow, curcol=cursorCol))', 'evaluate Python expression on current row and show result on status line')
|
58
105
|
|
59
|
-
vd.addGlobals(
|
106
|
+
vd.addGlobals(
|
107
|
+
ExprColumn=ExprColumn,
|
108
|
+
ColumnExpr=ExprColumn,
|
109
|
+
CompleteExpr=CompleteExpr,
|
110
|
+
)
|
60
111
|
|
61
112
|
vd.addMenuItems('''
|
62
113
|
Edit > Modify > current cell > Python expression > setcell-expr
|