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.
Files changed (151) hide show
  1. visidata/__init__.py +12 -10
  2. visidata/_input.py +208 -199
  3. visidata/_open.py +4 -1
  4. visidata/_types.py +4 -3
  5. visidata/aggregators.py +88 -39
  6. visidata/apps/vdsql/_ibis.py +9 -11
  7. visidata/apps/vdsql/clickhouse.py +2 -2
  8. visidata/apps/vdsql/snowflake.py +1 -1
  9. visidata/apps/vgit/status.py +1 -1
  10. visidata/basesheet.py +11 -4
  11. visidata/canvas.py +66 -24
  12. visidata/clipboard.py +13 -6
  13. visidata/cliptext.py +7 -6
  14. visidata/cmdlog.py +40 -27
  15. visidata/column.py +14 -49
  16. visidata/ddw/regex.ddw +3 -2
  17. visidata/deprecated.py +14 -2
  18. visidata/desktop/visidata.desktop +2 -2
  19. visidata/editor.py +1 -0
  20. visidata/errors.py +1 -1
  21. visidata/experimental/sort_selected.py +54 -0
  22. visidata/expr.py +69 -18
  23. visidata/features/change_precision.py +1 -3
  24. visidata/features/cmdpalette.py +23 -4
  25. visidata/features/colorsheet.py +1 -1
  26. visidata/features/dedupe.py +3 -3
  27. visidata/features/go_col.py +71 -0
  28. visidata/features/graph_seaborn.py +1 -1
  29. visidata/features/join.py +20 -10
  30. visidata/features/layout.py +18 -5
  31. visidata/features/ping.py +16 -12
  32. visidata/features/regex.py +5 -5
  33. visidata/features/slide.py +15 -17
  34. visidata/features/status_source.py +3 -1
  35. visidata/features/sysedit.py +1 -1
  36. visidata/features/transpose.py +2 -1
  37. visidata/features/type_ipaddr.py +2 -4
  38. visidata/features/unfurl.py +1 -0
  39. visidata/form.py +2 -2
  40. visidata/freqtbl.py +16 -11
  41. visidata/fuzzymatch.py +1 -0
  42. visidata/graph.py +173 -12
  43. visidata/guide.py +61 -25
  44. visidata/guides/ClipboardGuide.md +48 -0
  45. visidata/guides/ColumnsGuide.md +52 -0
  46. visidata/guides/CommandsSheet.md +28 -0
  47. visidata/guides/DirSheet.md +34 -0
  48. visidata/guides/ErrorsSheet.md +17 -0
  49. visidata/guides/FrequencyTable.md +42 -0
  50. visidata/guides/GrepSheet.md +28 -0
  51. visidata/guides/JsonSheet.md +38 -0
  52. visidata/guides/MacrosSheet.md +19 -0
  53. visidata/guides/MeltGuide.md +52 -0
  54. visidata/guides/MemorySheet.md +7 -0
  55. visidata/guides/MenuGuide.md +26 -0
  56. visidata/guides/ModifyGuide.md +38 -0
  57. visidata/guides/PivotGuide.md +71 -0
  58. visidata/guides/RegexGuide.md +107 -0
  59. visidata/guides/SelectionGuide.md +44 -0
  60. visidata/guides/SlideGuide.md +26 -0
  61. visidata/guides/SortGuide.md +0 -0
  62. visidata/guides/SplitpaneGuide.md +15 -0
  63. visidata/guides/TypesSheet.md +43 -0
  64. visidata/guides/XsvGuide.md +36 -0
  65. visidata/help.py +6 -6
  66. visidata/hint.py +2 -1
  67. visidata/indexsheet.py +2 -2
  68. visidata/interface.py +13 -14
  69. visidata/keys.py +4 -1
  70. visidata/loaders/api_airtable.py +1 -1
  71. visidata/loaders/archive.py +1 -1
  72. visidata/loaders/csv.py +9 -5
  73. visidata/loaders/eml.py +11 -6
  74. visidata/loaders/f5log.py +1 -0
  75. visidata/loaders/fec.py +18 -42
  76. visidata/loaders/fixed_width.py +19 -3
  77. visidata/loaders/grep.py +121 -0
  78. visidata/loaders/html.py +1 -0
  79. visidata/loaders/http.py +6 -1
  80. visidata/loaders/json.py +22 -4
  81. visidata/loaders/jsonla.py +8 -2
  82. visidata/loaders/mailbox.py +1 -0
  83. visidata/loaders/markdown.py +25 -6
  84. visidata/loaders/msgpack.py +19 -0
  85. visidata/loaders/npy.py +0 -1
  86. visidata/loaders/odf.py +18 -4
  87. visidata/loaders/orgmode.py +1 -1
  88. visidata/loaders/rec.py +6 -4
  89. visidata/loaders/sas.py +11 -4
  90. visidata/loaders/scrape.py +0 -1
  91. visidata/loaders/texttables.py +2 -0
  92. visidata/loaders/tsv.py +24 -7
  93. visidata/loaders/unzip_http.py +127 -3
  94. visidata/loaders/vds.py +4 -0
  95. visidata/loaders/vdx.py +1 -1
  96. visidata/loaders/xlsx.py +5 -0
  97. visidata/loaders/xml.py +2 -1
  98. visidata/macros.py +14 -31
  99. visidata/main.py +20 -15
  100. visidata/mainloop.py +17 -6
  101. visidata/man/vd.1 +74 -39
  102. visidata/man/vd.txt +73 -41
  103. visidata/memory.py +16 -5
  104. visidata/menu.py +14 -3
  105. visidata/metasheets.py +5 -6
  106. visidata/modify.py +4 -4
  107. visidata/mouse.py +2 -0
  108. visidata/movement.py +14 -28
  109. visidata/optionssheet.py +3 -5
  110. visidata/path.py +59 -37
  111. visidata/pivot.py +8 -5
  112. visidata/pyobj.py +63 -9
  113. visidata/rename_col.py +18 -1
  114. visidata/save.py +16 -9
  115. visidata/search.py +4 -4
  116. visidata/selection.py +10 -56
  117. visidata/settings.py +37 -35
  118. visidata/sheets.py +189 -118
  119. visidata/shell.py +23 -14
  120. visidata/sidebar.py +71 -16
  121. visidata/sort.py +21 -6
  122. visidata/statusbar.py +42 -5
  123. visidata/stored_list.py +5 -2
  124. visidata/tests/conftest.py +1 -0
  125. visidata/tests/test_commands.py +9 -1
  126. visidata/tests/test_completer.py +18 -0
  127. visidata/tests/test_edittext.py +3 -2
  128. visidata/text_source.py +7 -4
  129. visidata/textsheet.py +20 -6
  130. visidata/themes/ascii8.py +9 -6
  131. visidata/themes/asciimono.py +14 -4
  132. visidata/threads.py +13 -3
  133. visidata/tuiwin.py +5 -1
  134. visidata/type_currency.py +1 -2
  135. visidata/type_date.py +6 -1
  136. visidata/undo.py +10 -13
  137. visidata/utils.py +9 -3
  138. visidata/vdobj.py +21 -1
  139. visidata/wrappers.py +9 -1
  140. {visidata-3.0.1.data → visidata-3.1.data}/data/share/applications/visidata.desktop +2 -2
  141. {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/vd.1 +74 -39
  142. {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/visidata.1 +74 -39
  143. {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/METADATA +33 -5
  144. visidata-3.1.dist-info/RECORD +284 -0
  145. visidata-3.0.1.dist-info/RECORD +0 -258
  146. {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd +0 -0
  147. {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd2to3.vdx +0 -0
  148. {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/LICENSE.gpl3 +0 -0
  149. {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/WHEEL +0 -0
  150. {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/entry_points.txt +0 -0
  151. {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 is empty')
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.memo("clipval", cursorCol, cursorRow)', 'yank (copy) current cell to clipboard')
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', 'for r, v in zip(onlySelectedRows, itertools.cycle(vd.memory.clipcells or [None])): cursorCol.setValuesTyped([r], v)', 'set cells of current column for selected rows to last clipboard value')
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[10:-1], cattr=cattr.update(colors.clickable)))
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 isinstance(colorstate, str):
239
- cattr = cattr.update(colors.get_color(colorstate))
240
- else:
241
- cattr = origattr.update(colorstate['cattr'])
242
- link = colorstate['link']
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
- go- search scroll prev next page start end zoom resize visibility sidebar
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, longname):
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, rowstr):
98
- index = indexMatch(vs.rows, lambda r,vs=vs,rowstr=rowstr: keystr(vs.rowkey(r)) == rowstr)
99
- if index is not None:
100
- return index
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
- k = sheet.rowkey(sheet.cursorRow)
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=str(rowname),
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.longname):
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 (vd.activeCommand is not None) and (vd.activeCommand is not UNLOADED):
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' | [:status_replay] {len(vd._nextCommands)} {vd.options.disp_replay_play}[:]'
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 is not None and vd.isLoggableCommand(vd.activeCommand.longname):
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', max_help=-1)
24
- vd.option('disp_formatter', 'generic', 'formatter to create the text in each cell (also used by text savers)', replay=True, max_help=0)
25
- vd.option('disp_displayer', 'generic', 'displayer to render the text in each cell', replay=False, max_help=0)
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=None, note=None, notecolor=None, error=None):
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.max_help = 10 # auto-hide above this disp_help level
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.note_getter_exc,
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.note_type_exc,
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.note_type_exc,
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.note_pending,
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.note_format_exc
413
+ dw.note = options.disp_note_fmtexc
415
414
  dw.notecolor = 'color_warning'
416
415
 
417
- # dw.display = self.display(dw) # set during draw() when colwidth is known
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": 14, "text": "For full documentation on Python regular expressions, see", "color": "", "tags": [], "group": ""}
57
- {"x": 6, "y": 15, "text": "https://docs.python.org/3/library/re.html", "color": "underline", "tags": [], "group": "", "href": "https://docs.python.org/3/library/re.html"}
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.addGlobals(globals())
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)
@@ -3,5 +3,5 @@ Type=Application
3
3
  Terminal=true
4
4
  Name=VisiData
5
5
  Icon=utilities-terminal
6
- Exec=bash -i -c 'vd %f'
7
- Categories=Application;
6
+ Exec=vd %F
7
+ Categories=Utility;TextTools
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
@@ -2,7 +2,7 @@ import traceback
2
2
 
3
3
  from visidata import vd, VisiData
4
4
 
5
- vd.option('debug', False, 'exit on error and display stacktrace', max_help=0)
5
+ vd.option('debug', False, 'exit on error and display stacktrace')
6
6
 
7
7
 
8
8
  class ExpectedException(Exception):
@@ -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
- from visidata import Progress, Sheet, Column, asyncthread, vd, ExprColumn
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.sheet = sheet
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
- varnames = []
23
- varnames.extend(sorted((base+col.name) for col in self.sheet.columns if col.name.startswith(partial)))
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
- return varnames[state%len(varnames)]
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
- v = vd.callNoExceptions(self.sheet.evalExpr, compiledExpr, row)
42
- vd.callNoExceptions(self.setValue, row, v)
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 %d values = %s' % (len(rows), expr))
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({'CompleteExpr': CompleteExpr})
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