visidata 3.1.1__py3-none-any.whl → 3.2__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 (96) hide show
  1. visidata/__init__.py +2 -2
  2. visidata/_input.py +70 -36
  3. visidata/_open.py +9 -6
  4. visidata/_types.py +2 -2
  5. visidata/aggregators.py +125 -16
  6. visidata/apps/vdsql/_ibis.py +8 -13
  7. visidata/basesheet.py +4 -1
  8. visidata/canvas.py +11 -7
  9. visidata/clipboard.py +11 -2
  10. visidata/cliptext.py +65 -23
  11. visidata/cmdlog.py +5 -1
  12. visidata/column.py +6 -2
  13. visidata/ddwplay.py +2 -2
  14. visidata/deprecated.py +91 -63
  15. visidata/errors.py +41 -5
  16. visidata/{features → experimental}/helloworld.py +1 -1
  17. visidata/expr.py +1 -0
  18. visidata/extensible.py +4 -0
  19. visidata/features/cmdpalette.py +3 -3
  20. visidata/features/describe.py +2 -2
  21. visidata/features/expand_cols.py +8 -5
  22. visidata/features/freeze.py +14 -2
  23. visidata/features/go_col.py +2 -1
  24. visidata/features/graph_zoom_y.py +47 -0
  25. visidata/features/incr.py +7 -3
  26. visidata/features/join.py +23 -12
  27. visidata/features/layout.py +8 -3
  28. visidata/features/melt.py +1 -0
  29. visidata/features/rank.py +103 -0
  30. visidata/features/reload_every.py +9 -6
  31. visidata/features/sysedit.py +14 -4
  32. visidata/features/transpose.py +1 -0
  33. visidata/features/window.py +12 -0
  34. visidata/form.py +4 -4
  35. visidata/freqtbl.py +47 -3
  36. visidata/fuzzymatch.py +8 -5
  37. visidata/graph.py +5 -3
  38. visidata/guides/AggregatorsSheet.md +84 -0
  39. visidata/guides/MacrosSheet.md +1 -1
  40. visidata/guides/RankGuide.md +51 -0
  41. visidata/guides/TypesSheet.md +1 -1
  42. visidata/guides/WindowFunctionGuide.md +49 -0
  43. visidata/help.py +3 -4
  44. visidata/indexsheet.py +1 -1
  45. visidata/loaders/_pandas.py +3 -1
  46. visidata/loaders/archive.py +6 -3
  47. visidata/loaders/csv.py +5 -1
  48. visidata/loaders/eml.py +2 -0
  49. visidata/loaders/f5log.py +2 -2
  50. visidata/loaders/fec.py +6 -9
  51. visidata/loaders/fixed_width.py +2 -0
  52. visidata/loaders/hdf5.py +34 -10
  53. visidata/loaders/npy.py +54 -23
  54. visidata/loaders/orgmode.py +3 -2
  55. visidata/loaders/pandas_freqtbl.py +4 -0
  56. visidata/loaders/psv.py +13 -0
  57. visidata/loaders/sqlite.py +1 -1
  58. visidata/loaders/vds.py +3 -4
  59. visidata/macros.py +4 -3
  60. visidata/main.py +11 -5
  61. visidata/mainloop.py +7 -4
  62. visidata/man/parse_options.py +3 -2
  63. visidata/man/vd.1 +26 -14
  64. visidata/man/vd.txt +25 -14
  65. visidata/menu.py +9 -9
  66. visidata/metasheets.py +3 -3
  67. visidata/mouse.py +1 -0
  68. visidata/pyobj.py +17 -9
  69. visidata/save.py +5 -1
  70. visidata/selection.py +29 -18
  71. visidata/settings.py +2 -2
  72. visidata/sheets.py +52 -24
  73. visidata/shell.py +2 -2
  74. visidata/sidebar.py +4 -2
  75. visidata/sort.py +89 -11
  76. visidata/statusbar.py +10 -9
  77. visidata/tests/test_cliptext.py +151 -0
  78. visidata/tests/test_commands.py +5 -2
  79. visidata/tests/test_menu.py +1 -1
  80. visidata/textsheet.py +34 -8
  81. visidata/themes/ascii8.py +2 -2
  82. visidata/themes/light.py +5 -0
  83. visidata/threads.py +16 -8
  84. visidata/undo.py +1 -1
  85. visidata/vendor/__init__.py +0 -0
  86. {visidata-3.1.1.data → visidata-3.2.data}/data/share/man/man1/vd.1 +26 -14
  87. {visidata-3.1.1.data → visidata-3.2.data}/data/share/man/man1/visidata.1 +26 -14
  88. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/METADATA +62 -15
  89. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/RECORD +95 -89
  90. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/WHEEL +1 -1
  91. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/entry_points.txt +1 -0
  92. visidata-3.1.1.data/scripts/vd +0 -6
  93. {visidata-3.1.1.data → visidata-3.2.data}/data/share/applications/visidata.desktop +0 -0
  94. {visidata-3.1.1.data → visidata-3.2.data}/scripts/vd2to3.vdx +0 -0
  95. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/LICENSE.gpl3 +0 -0
  96. {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/top_level.txt +0 -0
visidata/clipboard.py CHANGED
@@ -7,13 +7,21 @@ import tempfile
7
7
  import functools
8
8
  import os
9
9
  import itertools
10
+ import platform
10
11
 
11
12
  from visidata import VisiData, vd, asyncthread, SettableColumn
12
13
  from visidata import Sheet, Path, Column
13
14
 
14
- if sys.platform == 'win32':
15
+ if (
16
+ # Windows
17
+ sys.platform == 'win32'
18
+ # WSL 2
19
+ or "microsoft-standard-WSL2" in platform.uname().release
20
+ # WSL 1
21
+ or sys.platform == 'linux' and platform.uname().release.endswith("-Microsoft")
22
+ ):
15
23
  syscopy_cmd_default = 'clip.exe'
16
- syspaste_cmd_default = 'powershell -command Get-Clipboard'
24
+ syspaste_cmd_default = 'powershell.exe -noprofile -command Get-Clipboard'
17
25
  elif sys.platform == 'darwin':
18
26
  syscopy_cmd_default = 'pbcopy w'
19
27
  syspaste_cmd_default = 'pbpaste'
@@ -196,6 +204,7 @@ Sheet.addCommand('Y', 'syscopy-row', 'syscopyCells(visibleCols, [cursorRow])', '
196
204
 
197
205
  Sheet.addCommand('gY', 'syscopy-selected', 'syscopyCells(visibleCols, onlySelectedRows)', 'yank (copy) selected rows to system clipboard (using options.clipboard_copy_cmd)')
198
206
  Sheet.addCommand('zY', 'syscopy-cell', 'syscopyValue(cursorDisplay)', 'yank (copy) current cell to system clipboard (using options.clipboard_copy_cmd)')
207
+ Sheet.addCommand('', 'syscopy-colname', 'syscopyValue(cursorCol.name)', 'yank (copy) current column header to system clipboard (using options.clipboard_copy_cmd)')
199
208
  Sheet.addCommand('gzY', 'syscopy-cells', 'syscopyCells([cursorCol], onlySelectedRows, filetype="txt")', 'yank (copy) contents of current column from selected rows to system clipboard (using options.clipboard_copy_cmd')
200
209
 
201
210
  Sheet.addCommand('x', 'cut-row', 'copyRows([sheet.delete_row(cursorRowIndex)]); defer and cursorDown(1)', 'delete (cut) current row and move it to clipboard')
visidata/cliptext.py CHANGED
@@ -142,42 +142,52 @@ def iterchars(x):
142
142
 
143
143
  @functools.lru_cache(maxsize=100000)
144
144
  def _clipstr(s, dispw, trunch='', oddspacech='', combch='', modch=''):
145
- '''Return clipped string and width in terminal display characters.
146
- Note: width may differ from len(s) if East Asian chars are 'fullwidth'.'''
147
- if not s:
145
+ ''' *s* is a string or an iterator that contains characters.
146
+ *dispw* is the integer screen width that the clipped string will fit inside, or None.
147
+ Return clipped string and width in terminal display characters.
148
+ Note: width may differ from len(s) if chars are 'fullwidth'.
149
+ If *dispw* is None, no clipping occurs.
150
+ If *trunch* has a width greater than *dispw*, the empty string
151
+ will be used as a truncator instead.'''
152
+ if not s or (dispw is not None and dispw < 1): #iterator s would be truthy
148
153
  return '', 0
149
154
 
150
- if dispw == 1:
151
- return s[0], 1
152
-
153
155
  w = 0
154
156
  ret = ''
157
+ trunc_i = 0
158
+ w_truncated = 0
155
159
 
156
160
  trunchlen = dispwidth(trunch)
161
+ if dispw is None:
162
+ s = ''.join(s)
163
+ return s, dispwidth(s)
164
+ if trunchlen > dispw: #if the truncator cannot fit, use a truncator of ''
165
+ return _clipstr(s, dispw, trunch='', oddspacech=oddspacech, combch=combch, modch=modch)
157
166
  for c in s:
158
167
  newc, chlen = _dispch(c, oddspacech=oddspacech, combch=combch, modch=modch)
159
168
  if not newc:
160
169
  newc = c
161
170
  chlen = dispwidth(c)
162
171
 
163
- if dispw and w+chlen > dispw:
164
- if trunchlen and dispw > trunchlen:
165
- lastchlen = _dispch(ret[-1])[1]
166
- if w+trunchlen > dispw:
167
- ret = ret[:-1]
168
- w -= lastchlen
169
- ret += trunch # replace final char with ellipsis
170
- w += trunchlen
171
- break
172
-
173
- w += chlen
174
- ret += newc
172
+ #if the next character will fit
173
+ if w+chlen <= dispw:
174
+ ret += c
175
+ w += chlen
176
+ #move the truncation spot forward only when the truncation character can fit
177
+ if w+trunchlen <= dispw:
178
+ trunc_i += 1
179
+ w_truncated += chlen
180
+ continue
181
+ # if we reach this line, a character did not fit, and the result needs truncation
182
+ return ret[:trunc_i] + trunch, w_truncated+trunchlen
175
183
 
176
184
  return ret, w
177
185
 
178
186
 
179
187
  @drawcache
180
188
  def clipstr(s, dispw, truncator=None, oddspace=None):
189
+ ''' *s* is a string or an iterator that contains characters.
190
+ *dispw* is the integer screen width that the clipped string will fit inside, or None.'''
181
191
  if options.visibility:
182
192
  return _clipstr(s, dispw,
183
193
  trunch=options.disp_truncator if truncator is None else truncator,
@@ -205,8 +215,6 @@ def clipdraw(scr, y, x, s, attr, w=None, clear=True, literal=False, **kwargs):
205
215
 
206
216
  x = max(0, x)
207
217
  y = max(0, y)
208
- assert x >= 0, x
209
- assert y >= 0, y
210
218
 
211
219
  return clipdraw_chunks(scr, y, x, chunks, attr, w=w, clear=clear, **kwargs)
212
220
 
@@ -310,6 +318,7 @@ def wraptext(text, width=80, indent=''):
310
318
  line = _markdown_to_internal(line)
311
319
  chunks = re.split(internal_markup_re, line)
312
320
  textchunks = [x for x in chunks if not is_vdcode(x)]
321
+ # textwrap.wrap does not handle variable-width characters #2416
313
322
  for linenum, textline in enumerate(textwrap.wrap(''.join(textchunks), width=width, drop_whitespace=False)):
314
323
  txt = textline
315
324
  r = ''
@@ -345,8 +354,39 @@ def clipbox(scr, lines, attr, title=''):
345
354
  for i, line in enumerate(lines):
346
355
  clipdraw(scr, i+1, 2, line, attr)
347
356
 
348
- clipdraw(scr, 0, w-len(title)-6, f"| {title} |", attr)
349
-
357
+ clipdraw(scr, 0, w-dispwidth(title)-6, f"| {title} |", attr)
358
+
359
+ def clipstr_start(dispval, w, truncator=''):
360
+ '''Return a tuple (frag, dw), where *frag* is the longest ending substring
361
+ of *dispval* that will fit in a space *w* terminal display characters wide,
362
+ and *dw* is the substring's display width as an int.'''
363
+ # Note: this implementation is likely incorrect for unusual Unicode
364
+ # strings or encodings, where trimming an initial character produces
365
+ # an invalid string or does not make the string shorter.
366
+ if w <= 0: return '', 0
367
+ j = len(dispval)
368
+ while j >= 1:
369
+ if dispwidth((truncator if j > 1 else '') + dispval[j-1:]) <= w:
370
+ j -= 1
371
+ else:
372
+ break
373
+ frag = (truncator if j > 0 else '') + dispval[j:]
374
+ return frag, dispwidth(frag)
375
+
376
+ def clipstr_middle(s, n=10, truncator='…'):
377
+ '''Return a string having a display width <= *n*. Excess characters are
378
+ trimmed from the middle of the string, and replaced by a single
379
+ instance of *truncator*.'''
380
+ if n == 0: return '', 0
381
+ if dispwidth(s) > n:
382
+ #for even widths, give the leftover 1 space to the right fragment
383
+ l_space = n//2 if n%2 == 1 else max(n//2-1, 0)
384
+ l_frag, l_w = _clipstr(s, l_space)
385
+ #if left fragment did not fill its space, give the unused space to the right fragment
386
+ r_frag = clipstr_start(s, n//2+(l_space-l_w))[0]
387
+ res = l_frag + truncator + r_frag
388
+ return res, dispwidth(res)
389
+ return s, dispwidth(s)
350
390
 
351
391
  vd.addGlobals(clipstr=clipstr,
352
392
  clipdraw=clipdraw,
@@ -355,4 +395,6 @@ vd.addGlobals(clipstr=clipstr,
355
395
  dispwidth=dispwidth,
356
396
  iterchars=iterchars,
357
397
  iterchunks=iterchunks,
358
- wraptext=wraptext)
398
+ wraptext=wraptext,
399
+ clipstr_start=clipstr_start,
400
+ clipstr_middle=clipstr_middle)
visidata/cmdlog.py CHANGED
@@ -330,6 +330,9 @@ def replay_sync(vd, cmdlog):
330
330
  with vd.DisableAsync():
331
331
  vd.sync() #2352 let cmdlog finish loading
332
332
  cmdlog.cursorRowIndex = 0
333
+ # save current replay, for cmdlogs that replay other cmdlogs, such as a macro executing another macro
334
+ prev_replay = vd.currentReplay
335
+ prev_replay_row = vd.currentReplayRow
333
336
  vd.currentReplay = cmdlog
334
337
 
335
338
  with Progress(total=len(cmdlog.rows)) as prog:
@@ -356,7 +359,8 @@ def replay_sync(vd, cmdlog):
356
359
  vd.activeSheet.ensureLoaded()
357
360
 
358
361
  vd.status('replay complete')
359
- vd.currentReplay = None
362
+ vd.currentReplay = prev_replay
363
+ vd.currentReplayRow = prev_replay_row
360
364
 
361
365
 
362
366
  @VisiData.api
visidata/column.py CHANGED
@@ -249,6 +249,8 @@ class Column(Extensible):
249
249
 
250
250
  if self.type is anytype:
251
251
  if isinstance(typedval, (dict, list, tuple)):
252
+ if width is None:
253
+ return ''.join(iterchars(typedval))
252
254
  dispval, dispw = clipstr(iterchars(typedval), width)
253
255
  return dispval
254
256
 
@@ -264,7 +266,9 @@ class Column(Extensible):
264
266
 
265
267
  The 'generic' displayer does not do any formatting.
266
268
  '''
267
- if width is not None and width > 1 and vd.isNumeric(self):
269
+ if width is not None and width > 1 and \
270
+ vd.isNumeric(self) and \
271
+ isinstance(dw.typedval, (int, float)):
268
272
  yield ('', dw.text.rjust(width-2))
269
273
  else:
270
274
  yield ('', dw.text)
@@ -470,7 +474,7 @@ class Column(Extensible):
470
474
  break #1747 early out to speed up wide columns
471
475
  w = w_max
472
476
  w = max(w, nlen)+2
473
- w = min(w, self.sheet.windowWidth)
477
+ w = min(w, self.sheet.windowWidth-1)
474
478
  return w
475
479
 
476
480
 
visidata/ddwplay.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from collections import defaultdict
2
2
  import json
3
3
  import time
4
- from visidata import colors, vd, clipdraw, ColorAttr
4
+ from visidata import colors, vd, clipdraw, ColorAttr, dispwidth
5
5
 
6
6
  __all__ = ['Animation', 'AnimationMgr']
7
7
 
@@ -77,7 +77,7 @@ class Animation:
77
77
  self.total_ms = sum(f.duration_ms or 0 for f in self.frames.values())
78
78
  for f in self.frames.values():
79
79
  for r, x, y, _ in self.iterdeep(f.rows):
80
- self.width = max(self.width, x+len(r.text))
80
+ self.width = max(self.width, x+dispwidth(r.text))
81
81
  self.height = max(self.height, y)
82
82
 
83
83
  def draw(self, scr, *, t=0, x=0, y=0, loop=False, attr=ColorAttr(), **kwargs):
visidata/deprecated.py CHANGED
@@ -3,12 +3,16 @@ import functools
3
3
  from visidata import VisiData, vd
4
4
  import visidata
5
5
 
6
- alias = visidata.BaseSheet.bindkey
6
+ def deprecated_alias(depver, *args, **kwargs):
7
+ # expand this to create cmd
8
+ # with cmd.deprecated=depver
9
+ return visidata.BaseSheet.bindkey(*args, **kwargs)
7
10
 
8
- def deprecated_warn(func, ver, instead):
11
+ @VisiData.api
12
+ def deprecated_warn(vd, funcname, ver, instead):
9
13
  import traceback
10
14
 
11
- msg = f'{func.__name__} deprecated since v{ver}'
15
+ msg = f'{funcname} deprecated since v{ver}'
12
16
  if instead:
13
17
  msg += f'; use {instead}'
14
18
 
@@ -20,18 +24,27 @@ def deprecated_warn(func, ver, instead):
20
24
  vd.warning(f'Deprecated call traceback (most recent last):')
21
25
 
22
26
 
23
- def deprecated(ver, instead=''):
27
+ def deprecated(ver, instead='', check=True):
24
28
  def decorator(func):
25
29
  @functools.wraps(func)
26
30
  def wrapper(*args, **kwargs):
27
- deprecated_warn(func, ver, instead)
31
+ vd.deprecated_warn(wrapper.__name__, ver, instead)
28
32
  return func(*args, **kwargs)
33
+
34
+ if check and hasattr(func, '_extensible_api'):
35
+ vd.error(f"{func.__name__}: @deprecated applied in wrong order") #2623
36
+
29
37
  return wrapper
30
38
  return decorator
31
39
 
32
40
 
33
- @deprecated('1.6', 'vd instead of vd()')
41
+ def _deprecated_api(ver, instead=''):
42
+ 'Decorator to deliberately wrap non-deprecated .api functions as a deprecated global function. Use @deprecated instead, except in deprecated.py.'
43
+ return deprecated(ver, instead, check=False)
44
+
45
+
34
46
  @VisiData.api
47
+ @deprecated('1.6', 'vd instead of vd()')
35
48
  def __call__(vd):
36
49
  'Deprecated; use plain "vd"'
37
50
  return vd
@@ -39,7 +52,7 @@ def __call__(vd):
39
52
 
40
53
  @deprecated('1.6')
41
54
  def copyToClipboard(value):
42
- vd.error("copyToClipboard longer implemented")
55
+ vd.error("copyToClipboard no longer implemented")
43
56
  return visidata.clipboard_copy(value)
44
57
 
45
58
 
@@ -62,32 +75,32 @@ def bindkey_override(keystrokes, longname):
62
75
  bindkey = visidata.BaseSheet.bindkey
63
76
  unbindkey = visidata.BaseSheet.unbindkey
64
77
 
65
- @deprecated('2.0')
66
78
  @visidata.Sheet.api
79
+ @deprecated('2.0')
67
80
  def exec_keystrokes(self, keystrokes, vdglobals=None):
68
81
  return self.execCommand(self.getCommand(keystrokes), vdglobals, keystrokes=keystrokes)
69
82
 
70
83
  visidata.Sheet.exec_command = deprecated('2.0')(visidata.Sheet.execCommand)
71
84
 
72
- @deprecated('2.0', 'def open_<filetype> instead')
73
85
  @VisiData.api
86
+ @deprecated('2.0', 'def open_<filetype> instead')
74
87
  def filetype(vd, ext, constructor):
75
88
  'Add constructor to handle the given file type/extension.'
76
89
  globals().setdefault('open_'+ext, lambda p,ext=ext: constructor(p.base_stem, source=p, filetype=ext))
77
90
 
78
- @deprecated('2.0', 'Sheet(namepart1, namepart2, ...)')
79
91
  @VisiData.global_api
92
+ @deprecated('2.0', 'Sheet(namepart1, namepart2, ...)')
80
93
  def joinSheetnames(vd, *sheetnames):
81
94
  'Concatenate sheet names in a standard way'
82
95
  return visidata.options.name_joiner.join(str(x) for x in sheetnames)
83
96
 
84
- @deprecated('2.0', 'PyobjSheet')
85
97
  @VisiData.global_api
98
+ @deprecated('2.0', 'PyobjSheet')
86
99
  def load_pyobj(*names, **kwargs):
87
100
  return visidata.PyobjSheet(*names, **kwargs)
88
101
 
89
- @deprecated('2.0', 'PyobjSheet')
90
102
  @VisiData.global_api
103
+ @deprecated('2.0', 'PyobjSheet')
91
104
  def push_pyobj(name, pyobj):
92
105
  vs = visidata.PyobjSheet(name, source=pyobj)
93
106
  if vs:
@@ -103,33 +116,33 @@ visidata.addGlobals({'load_pyobj': load_pyobj, 'isNumeric': isNumeric})
103
116
 
104
117
  # The longnames on the left are deprecated for 2.0
105
118
 
106
- alias('edit-cells', 'setcol-input')
107
- alias('fill-nulls', 'setcol-fill')
108
- alias('paste-cells', 'setcol-clipboard')
109
- alias('frequency-rows', 'frequency-summary')
110
- alias('dup-cell', 'dive-cell')
111
- alias('dup-row', 'dive-row')
112
- alias('next-search', 'search-next')
113
- alias('prev-search', 'search-prev')
114
- alias('search-prev', 'searchr-next')
115
- alias('prev-sheet', 'jump-prev')
116
- alias('prev-value', 'go-prev-value')
117
- alias('next-value', 'go-next-value')
118
- alias('prev-selected', 'go-prev-selected')
119
- alias('next-selected', 'go-next-selected')
120
- alias('prev-null', 'go-prev-null')
121
- alias('next-null', 'go-next-null')
122
- alias('page-right', 'go-right-page')
123
- alias('page-left', 'go-left-page')
124
- alias('dive-cell', 'open-cell')
125
- alias('dive-row', 'open-row')
126
- alias('add-sheet', 'open-new')
127
- alias('save-sheets-selected', 'save-selected')
128
- alias('join-sheets', 'join-selected')
129
- alias('dive-rows', 'dive-selected')
119
+ deprecated_alias('2.0', 'edit-cells', 'setcol-input')
120
+ deprecated_alias('2.0', 'fill-nulls', 'setcol-fill')
121
+ deprecated_alias('2.0', 'paste-cells', 'setcol-clipboard')
122
+ deprecated_alias('2.0', 'frequency-rows', 'frequency-summary')
123
+ deprecated_alias('2.0', 'dup-cell', 'dive-cell')
124
+ deprecated_alias('2.0', 'dup-row', 'dive-row')
125
+ deprecated_alias('2.0', 'next-search', 'search-next')
126
+ deprecated_alias('2.0', 'prev-search', 'search-prev')
127
+ deprecated_alias('2.0', 'search-prev', 'searchr-next')
128
+ deprecated_alias('2.0', 'prev-sheet', 'jump-prev')
129
+ deprecated_alias('2.0', 'prev-value', 'go-prev-value')
130
+ deprecated_alias('2.0', 'next-value', 'go-next-value')
131
+ deprecated_alias('2.0', 'prev-selected', 'go-prev-selected')
132
+ deprecated_alias('2.0', 'next-selected', 'go-next-selected')
133
+ deprecated_alias('2.0', 'prev-null', 'go-prev-null')
134
+ deprecated_alias('2.0', 'next-null', 'go-next-null')
135
+ deprecated_alias('2.0', 'page-right', 'go-right-page')
136
+ deprecated_alias('2.0', 'page-left', 'go-left-page')
137
+ deprecated_alias('2.0', 'dive-cell', 'open-cell')
138
+ deprecated_alias('2.0', 'dive-row', 'open-row')
139
+ deprecated_alias('2.0', 'add-sheet', 'open-new')
140
+ deprecated_alias('2.0', 'save-sheets-selected', 'save-selected')
141
+ deprecated_alias('2.0', 'join-sheets', 'join-selected')
142
+ deprecated_alias('2.0', 'dive-rows', 'dive-selected')
130
143
 
131
144
  # v2.3
132
- alias('show-aggregate', 'memo-aggregate')
145
+ deprecated_alias('2.3', 'show-aggregate', 'memo-aggregate')
133
146
  #theme('use_default_colors', True, 'curses use default terminal colors')
134
147
  #option('expand_col_scanrows', 1000, 'number of rows to check when expanding columns (0 = all)')
135
148
 
@@ -149,25 +162,25 @@ def load_tsv(fn):
149
162
 
150
163
  # NOTE: you cannot use deprecated() with nonfuncs
151
164
 
152
- cancelThread = deprecated('2.6', 'vd.cancelThread')(vd.cancelThread)
153
- status = deprecated('2.6', 'vd.status')(vd.status)
154
- warning = deprecated('2.6', 'vd.warning')(vd.warning)
155
- error = deprecated('2.6', 'vd.error')(vd.error)
156
- debug = deprecated('2.6', 'vd.debug')(vd.debug)
157
- fail = deprecated('2.6', 'vd.fail')(vd.fail)
165
+ cancelThread = _deprecated_api('2.6', 'vd.cancelThread')(vd.cancelThread)
166
+ status = _deprecated_api('2.6', 'vd.status')(vd.status)
167
+ warning = _deprecated_api('2.6', 'vd.warning')(vd.warning)
168
+ error = _deprecated_api('2.6', 'vd.error')(vd.error)
169
+ debug = _deprecated_api('2.6', 'vd.debug')(vd.debug)
170
+ fail = _deprecated_api('2.6', 'vd.fail')(vd.fail)
158
171
 
159
172
  option = theme = vd.option # deprecated('2.6', 'vd.option')(vd.option)
160
173
  jointypes = vd.jointypes # deprecated('2.6', 'vd.jointypes')(vd.jointypes)
161
- confirm = deprecated('2.6', 'vd.confirm')(vd.confirm)
162
- launchExternalEditor = deprecated('2.6', 'vd.launchExternalEditor')(vd.launchExternalEditor)
163
- launchEditor = deprecated('2.6', 'vd.launchEditor')(vd.launchEditor)
164
- exceptionCaught = deprecated('2.6', 'vd.exceptionCaught')(vd.exceptionCaught)
165
- openSource = deprecated('2.6', 'vd.openSource')(vd.openSource)
174
+ confirm = _deprecated_api('2.6', 'vd.confirm')(vd.confirm)
175
+ launchExternalEditor = _deprecated_api('2.6', 'vd.launchExternalEditor')(vd.launchExternalEditor)
176
+ launchEditor = _deprecated_api('2.6', 'vd.launchEditor')(vd.launchEditor)
177
+ exceptionCaught = _deprecated_api('2.6', 'vd.exceptionCaught')(vd.exceptionCaught)
178
+ openSource = _deprecated_api('2.6', 'vd.openSource')(vd.openSource)
166
179
  globalCommand = visidata.BaseSheet.addCommand
167
- visidata.Sheet.StaticColumn = deprecated('2.11', 'Sheet.freeze_col')(visidata.Sheet.freeze_col)
180
+ visidata.Sheet.StaticColumn = _deprecated_api('2.11', 'Sheet.freeze_col')(visidata.Sheet.freeze_col)
168
181
  #visidata.Path.open_text = deprecated('3.0', 'visidata.Path.open')(visidata.Path.open) # undeprecated in 3.1
169
182
 
170
- vd.sysclip_value = deprecated('3.0', 'vd.sysclipValue')(vd.sysclipValue)
183
+ vd.sysclip_value = _deprecated_api('3.0', 'vd.sysclipValue')(vd.sysclipValue)
171
184
 
172
185
  def itemsetter(i):
173
186
  def g(obj, v):
@@ -181,8 +194,8 @@ vd.optalias('confirm_overwrite', 'overwrite', 'confirm')
181
194
  vd.optalias('show_graph_labels', 'disp_graph_labels')
182
195
  vd.optalias('zoom_incr', 'disp_zoom_incr')
183
196
 
184
- alias('visibility-sheet', 'toggle-multiline')
185
- alias('visibility-col', 'toggle-multiline')
197
+ deprecated_alias('3.0', 'visibility-sheet', 'toggle-multiline')
198
+ deprecated_alias('3.0', 'visibility-col', 'toggle-multiline')
186
199
 
187
200
  def clean_to_id(s):
188
201
  return visidata.vd.cleanName(s)
@@ -204,7 +217,7 @@ class OnExit:
204
217
  except Exception as e:
205
218
  vd.exceptionCaught(e)
206
219
 
207
- alias('open-inputs', 'open-input-history')
220
+ deprecated_alias('3.0', 'open-inputs', 'open-input-history')
208
221
 
209
222
  #vd.option('plugins_url', 'https://visidata.org/plugins/plugins.jsonl', 'source of plugins sheet')
210
223
 
@@ -216,37 +229,52 @@ def inputRegexSubstOld(vd, prompt):
216
229
  return dict(before=before, after=after)
217
230
 
218
231
 
219
- visidata.Sheet.addCommand('', 'addcol-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, **inputRegexSubstOld("transform column by regex: "))))', 'add column derived from current column, replacing regex with subst (may include \1 backrefs)', deprecated=True)
220
- visidata.Sheet.addCommand('', 'setcol-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubstOld("transform column by regex: "))', 'regex/subst - modify selected rows in current column, replacing regex with subst, (may include backreferences \\1 etc)', deprecated=True)
221
- visidata.Sheet.addCommand('', 'setcol-subst-all', 'setValuesFromRegex(visibleCols, someSelectedRows, **inputRegexSubstOld(f"transform {nVisibleCols} columns by regex: "))', 'modify selected rows in all visible columns, replacing regex with subst (may include \\1 backrefs)', deprecated=True)
232
+ visidata.Sheet.addCommand('', 'addcol-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, **inputRegexSubstOld("transform column by regex: "))))', 'add column derived from current column, replacing regex with subst (may include \1 backrefs)', deprecated='3.0')
233
+ visidata.Sheet.addCommand('', 'setcol-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubstOld("transform column by regex: "))', 'regex/subst - modify selected rows in current column, replacing regex with subst, (may include backreferences \\1 etc)', deprecated='3.0')
234
+ visidata.Sheet.addCommand('', 'setcol-subst-all', 'setValuesFromRegex(visibleCols, someSelectedRows, **inputRegexSubstOld(f"transform {nVisibleCols} columns by regex: "))', 'modify selected rows in all visible columns, replacing regex with subst (may include \\1 backrefs)', deprecated='3.0')
222
235
 
223
- visidata.Sheet.addCommand('', 'split-col', 'addRegexColumns(makeRegexSplitter, cursorCol, inputRegex("split regex: ", type="regex-split"))', 'Add new columns from regex split', deprecated=True)
224
- visidata.Sheet.addCommand('', 'capture-col', 'addRegexColumns(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture"))', 'add new column from capture groups of regex; requires example row', deprecated=True)
236
+ visidata.Sheet.addCommand('', 'split-col', 'addRegexColumns(makeRegexSplitter, cursorCol, inputRegex("split regex: ", type="regex-split"))', 'Add new columns from regex split', deprecated='3.0')
237
+ visidata.Sheet.addCommand('', 'capture-col', 'addRegexColumns(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture"))', 'add new column from capture groups of regex; requires example row', deprecated='3.0')
225
238
 
226
239
  #vd.option('cmdlog_histfile', '', 'file to autorecord each cmdlog action to', sheettype=None)
227
240
  #BaseSheet.bindkey('KEY_BACKSPACE', 'menu-help')
228
241
 
229
- @deprecated('3.0', 'vd.callNoExceptions(col.setValue, row, value)')
230
242
  @visidata.Column.api
243
+ @deprecated('3.0', 'vd.callNoExceptions(col.setValue, row, value)')
231
244
  def setValueSafe(self, row, value):
232
245
  'setValue and ignore exceptions.'
233
246
  return vd.callNoExceptions(self.setValue, row, value)
234
247
 
235
- @deprecated('3.0', 'vd.callNoExceptions(sheet.checkCursor)')
236
248
  @visidata.BaseSheet.api
249
+ @deprecated('3.0', 'vd.callNoExceptions(sheet.checkCursor)')
237
250
  def checkCursorNoExceptions(sheet):
238
251
  return vd.callNoExceptions(sheet.checkCursor)
239
252
 
240
- @deprecated('3.1', 'vd.memoValue(name, value, displayvalue)')
241
253
  @VisiData.api
254
+ @deprecated('3.1', 'vd.memoValue(name, value, displayvalue)')
242
255
  def memo(vd, name, col, row):
243
256
  return vd.memoValue(name, col.getTypedValue(row), col.getDisplayValue(row))
244
257
 
245
- alias('view-cell', 'pyobj-cell')
258
+ deprecated_alias('3.1', 'view-cell', 'pyobj-cell')
246
259
 
247
260
  vd.optalias('textwrap_cells', 'disp_wrap_max_lines', 3) # wordwrap text for multiline rows
248
261
 
249
- @deprecated('3.1', 'sheet.rowname(row)')
250
262
  @visidata.TableSheet.api
263
+ @deprecated('3.1', 'sheet.rowname(row)')
251
264
  def keystr(sheet, row):
252
265
  return sheet.rowname(row)
266
+
267
+ vd.optalias('color_refline', 'color_graph_refline') # color_refline was used in v3.1 by mistake
268
+
269
+ @visidata.TableSheet.api
270
+ @deprecated('3.2', '[self.unsetKeys([c]) if c.keycol else self.setKeys([c]) for c in cols]')
271
+ def toggleKeys(self, cols):
272
+ for col in cols:
273
+ if col.keycol:
274
+ self.unsetKeys([col])
275
+ else:
276
+ self.setKeys([col])
277
+
278
+ vd.optalias('disp_pixel_random', 'disp_graph_pixel_random') #2661
279
+
280
+ vd.addGlobals(deprecated_warn=deprecated_warn)
visidata/errors.py CHANGED
@@ -1,4 +1,6 @@
1
1
  import traceback
2
+ import sys
3
+ import re
2
4
 
3
5
  from visidata import vd, VisiData
4
6
 
@@ -10,10 +12,43 @@ class ExpectedException(Exception):
10
12
  pass
11
13
 
12
14
 
13
- def stacktrace(e=None):
14
- if not e:
15
- return traceback.format_exc().strip().splitlines()
16
- return traceback.format_exception_only(type(e), e)
15
+ def stacktrace(e=None, exclude_caller=False):
16
+ '''Return a list of strings for the stack trace, without newlines
17
+ at the end. If an exception handler is executing, and *e* is none,
18
+ the stack trace includes extra levels of callers beyond the level
19
+ where the exception was caught. If *exclude_caller* is True, the
20
+ trace will exclude the function that called stacktrace(). The
21
+ trace will exclude several uninformative levels that are run
22
+ in interactive visidata.'''
23
+
24
+ if e:
25
+ return traceback.format_exception_only(type(e), e)
26
+ #in Python 3.11 we can replace sys.exc_info() with sys.exception()
27
+ handling = (sys.exc_info() != (None, None, None))
28
+
29
+ stack = ''.join(traceback.format_stack()).strip().splitlines()
30
+
31
+ if handling:
32
+ trim_levels = 2 # remove levels for stacktrace() -> format_stack()
33
+ if exclude_caller:
34
+ trim_levels += 1
35
+ trace_above = stack[:-2*trim_levels]
36
+ else:
37
+ trace_above = stack
38
+ if trace_above:
39
+ trace_above[0] = ' ' + trace_above[0] #fix indent level of first line
40
+ try:
41
+ # remove several levels of uninformative stacktrace in typical interactive vd
42
+ idx = trace_above.index(' ret = vd.mainloop(scr)')
43
+ trace_above = trace_above[idx+1:]
44
+ except ValueError:
45
+ pass
46
+ if not handling:
47
+ return trace_above
48
+ # remove lines that mark error columns with carets and sometimes tildes
49
+ trace_below = [ line for line in traceback.format_exc().strip().splitlines() if not re.match('^ *~*\\^+$', line) ]
50
+ # move the "Traceback (most recent call last) header to the top of the output
51
+ return [trace_below[0]] + trace_above + trace_below[1:]
17
52
 
18
53
 
19
54
  @VisiData.api
@@ -21,7 +56,8 @@ def exceptionCaught(vd, exc=None, status=True, **kwargs):
21
56
  'Add *exc* to list of last errors and add to status history. Show on left status bar if *status* is True. Reraise exception if options.debug is True.'
22
57
  if isinstance(exc, ExpectedException): # already reported, don't log
23
58
  return
24
- vd.lastErrors.append(stacktrace())
59
+ # save a stack trace that does not include this function
60
+ vd.lastErrors.append(stacktrace(exclude_caller=True))
25
61
  if status:
26
62
  vd.status(f'{type(exc).__name__}: {exc}', priority=2)
27
63
  else:
@@ -7,4 +7,4 @@ from visidata import vd, BaseSheet
7
7
 
8
8
  vd.option('hello_world', '¡Hola mundo!', 'shown by the hello-world command')
9
9
 
10
- BaseSheet.addCommand('KEY_F(2)', 'hello-world', 'status(options.hello_world)', 'print greeting to status')
10
+ BaseSheet.addCommand('F2', 'hello-world', 'status(options.hello_world)', 'print greeting to status')
visidata/expr.py CHANGED
@@ -100,6 +100,7 @@ Sheet.addCommand('=', 'addcol-expr', 'addColumnAtCursor(ExprColumn(inputExpr("ne
100
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
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')
102
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')
103
+ Sheet.addCommand('', 'addcol-iter', 'iter_expr=inputExpr("new column iterator expr: "); it = eval(iter_expr, getGlobals()); c=SettableColumn(); addColumnAtCursor(c); c.setValues(rows, *it)', 'add column with values from a Python sequence expression, repeating it if needed to fill')
103
104
 
104
105
  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')
105
106
 
visidata/extensible.py CHANGED
@@ -50,6 +50,7 @@ class Extensible:
50
50
  from visidata import vd
51
51
  func.importingModule = vd.importingModule
52
52
  setattr(cls, func.__name__, func)
53
+ func._extensible_api = True
53
54
  return func
54
55
 
55
56
  @classmethod
@@ -97,6 +98,7 @@ class Extensible:
97
98
  oldfunc = getattr(cls, name, None)
98
99
  if oldfunc:
99
100
  func = wraps(oldfunc)(func)
101
+ func._extensible_api = True
100
102
  setattr(cls, name, func)
101
103
  return func
102
104
 
@@ -107,6 +109,7 @@ class Extensible:
107
109
  def dofunc(self):
108
110
  return func(self)
109
111
  setattr(cls, func.__name__, dofunc)
112
+ func._extensible_api = True
110
113
  return dofunc
111
114
 
112
115
  @classmethod
@@ -121,6 +124,7 @@ class Extensible:
121
124
  setattr(self, name, func(self))
122
125
  return getattr(self, name)
123
126
  setattr(cls, func.__name__, get_if_not)
127
+ func._extensible_api = True
124
128
  return get_if_not
125
129
 
126
130
  @classmethod
@@ -1,6 +1,6 @@
1
1
  import collections
2
2
  from functools import partial
3
- from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property
3
+ from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property, dispwidth
4
4
 
5
5
 
6
6
  vd.theme_option('color_cmdpalette', 'black on 72', 'base color of command palette')
@@ -180,7 +180,7 @@ def inputLongname(sheet):
180
180
  formatted_name = f'[:bold][:onclick {row.longname}]{formatted_longname}[/][/]'
181
181
  if vd.options.debug and match:
182
182
  keystrokes = f'[{match.score}]'
183
- r = f' [:keystrokes]{keystrokes.rjust(len(prompt)-5)}[/] '
183
+ r = f' [:keystrokes]{keystrokes.rjust(dispwidth(prompt)-5)}[/] '
184
184
  if trigger_key:
185
185
  r += f'[:keystrokes]{trigger_key}[/]'
186
186
  else:
@@ -213,4 +213,4 @@ def exec_longname(sheet, longname):
213
213
 
214
214
 
215
215
  vd.addCommand('Space', 'exec-longname', 'exec_longname(inputLongname())', 'execute command by its longname')
216
- vd.addCommand('zSpace', 'exec-longname-simple', 'exec_longname(inputLongnameSimple())', 'execute command by its longname')
216
+ vd.addCommand('zSpace', 'exec-longname-simple', 'exec_longname(inputLongnameSimple())', 'execute command by its longname (without command palette)')