visidata 3.1.1__py3-none-any.whl → 3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. visidata/__init__.py +2 -2
  2. visidata/_input.py +106 -58
  3. visidata/_open.py +10 -7
  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 -3
  8. visidata/canvas.py +11 -7
  9. visidata/clipboard.py +11 -2
  10. visidata/cliptext.py +68 -23
  11. visidata/cmdlog.py +5 -1
  12. visidata/column.py +48 -33
  13. visidata/ddwplay.py +2 -2
  14. visidata/deprecated.py +96 -63
  15. visidata/errors.py +41 -5
  16. visidata/{features → experimental}/helloworld.py +1 -1
  17. visidata/experimental/liveupdate.py +1 -1
  18. visidata/expr.py +1 -0
  19. visidata/extensible.py +4 -0
  20. visidata/features/cmdpalette.py +64 -25
  21. visidata/features/describe.py +2 -2
  22. visidata/features/expand_cols.py +7 -5
  23. visidata/features/freeze.py +14 -2
  24. visidata/features/go_col.py +3 -3
  25. visidata/features/graph_zoom_y.py +47 -0
  26. visidata/features/incr.py +7 -3
  27. visidata/features/join.py +23 -12
  28. visidata/features/layout.py +8 -4
  29. visidata/features/melt.py +1 -0
  30. visidata/features/rank.py +103 -0
  31. visidata/features/reload_every.py +11 -8
  32. visidata/features/sysedit.py +14 -4
  33. visidata/features/transpose.py +1 -0
  34. visidata/features/window.py +12 -0
  35. visidata/form.py +10 -9
  36. visidata/freqtbl.py +47 -3
  37. visidata/fuzzymatch.py +11 -7
  38. visidata/graph.py +5 -3
  39. visidata/guides/AggregatorsSheet.md +84 -0
  40. visidata/guides/CommandsSheet.md +1 -0
  41. visidata/guides/MacrosSheet.md +1 -1
  42. visidata/guides/RankGuide.md +51 -0
  43. visidata/guides/TypesSheet.md +1 -1
  44. visidata/guides/WindowFunctionGuide.md +49 -0
  45. visidata/help.py +23 -6
  46. visidata/indexsheet.py +1 -1
  47. visidata/loaders/_pandas.py +3 -1
  48. visidata/loaders/archive.py +33 -6
  49. visidata/loaders/csv.py +12 -1
  50. visidata/loaders/eml.py +2 -0
  51. visidata/loaders/f5log.py +2 -2
  52. visidata/loaders/fec.py +6 -9
  53. visidata/loaders/fixed_width.py +2 -0
  54. visidata/loaders/hdf5.py +34 -10
  55. visidata/loaders/npy.py +54 -23
  56. visidata/loaders/orgmode.py +3 -2
  57. visidata/loaders/pandas_freqtbl.py +4 -0
  58. visidata/loaders/psv.py +13 -0
  59. visidata/loaders/sqlite.py +1 -1
  60. visidata/loaders/vds.py +3 -4
  61. visidata/macros.py +5 -4
  62. visidata/main.py +21 -11
  63. visidata/mainloop.py +8 -5
  64. visidata/man/parse_options.py +3 -2
  65. visidata/man/vd.1 +38 -17
  66. visidata/man/vd.txt +47 -17
  67. visidata/menu.py +10 -10
  68. visidata/metasheets.py +3 -3
  69. visidata/mouse.py +3 -0
  70. visidata/movement.py +6 -3
  71. visidata/pyobj.py +17 -9
  72. visidata/save.py +10 -2
  73. visidata/selection.py +29 -18
  74. visidata/settings.py +9 -5
  75. visidata/sheets.py +124 -48
  76. visidata/shell.py +2 -2
  77. visidata/sidebar.py +11 -8
  78. visidata/sort.py +89 -11
  79. visidata/statusbar.py +10 -9
  80. visidata/tests/test_cliptext.py +164 -0
  81. visidata/tests/test_commands.py +6 -2
  82. visidata/tests/test_menu.py +1 -1
  83. visidata/textsheet.py +34 -8
  84. visidata/themes/ascii8.py +2 -2
  85. visidata/themes/light.py +5 -0
  86. visidata/threads.py +38 -8
  87. visidata/utils.py +15 -1
  88. visidata/vendor/__init__.py +0 -0
  89. {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/vd.1 +38 -17
  90. {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/visidata.1 +38 -17
  91. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/METADATA +62 -15
  92. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/RECORD +98 -92
  93. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/WHEEL +1 -1
  94. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/entry_points.txt +1 -0
  95. visidata-3.1.1.data/scripts/vd +0 -6
  96. {visidata-3.1.1.data → visidata-3.3.data}/data/share/applications/visidata.desktop +0 -0
  97. {visidata-3.1.1.data → visidata-3.3.data}/scripts/vd2to3.vdx +0 -0
  98. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/LICENSE.gpl3 +0 -0
  99. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/top_level.txt +0 -0
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,57 @@ 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)
281
+
282
+ # v3.3
283
+
284
+ #vd.option('disp_expert', 'max level of options and columns to include')
285
+ #vd.option('disp_help', '')
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')
@@ -42,4 +42,4 @@ def addcol_expr(sheet):
42
42
 
43
43
 
44
44
  Sheet.addCommand(None, 'addcol-expr', 'sheet.addcol_expr()', "create new column from Python expression, updating the column's calculated values live")
45
- Sheet.addCommand(None, 'addcol-new', 'c=addColumnAtIndex(SettableColumn(width=options.default_width)); draw(sheet._scr); cursorVisibleColIndex=visibleCols.index(c); c.name=editCell(cursorVisibleColIndex, -1); c.width=None', 'append new column, updating the column name live')
45
+ Sheet.addCommand(None, 'addcol-new', 'c=addColumnAtCursor(SettableColumn(name="", width=options.default_width)); draw(sheet._scr); cursorVisibleColIndex=visibleCols.index(c); c.name=editCell(cursorVisibleColIndex, -1); c.width=None', 'append new column, updating the column name live')
visidata/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,7 @@
1
1
  import collections
2
+ import math
2
3
  from functools import partial
3
- from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property
4
+ from visidata import DrawablePane, BaseSheet, vd, VisiData, CompleteKey, clipdraw, HelpSheet, colors, AcceptInput, AttrDict, drawcache_property, dispwidth
4
5
 
5
6
 
6
7
  vd.theme_option('color_cmdpalette', 'black on 72', 'base color of command palette')
@@ -58,14 +59,21 @@ def inputPalette(sheet, prompt, items,
58
59
  formatter=lambda m, item, trigger_key: f'{trigger_key} {item}',
59
60
  multiple=False,
60
61
  **kwargs):
61
- if sheet.options.disp_expert >= 5:
62
+ if not vd.wantsHelp('cmdpalette'):
62
63
  return vd.input(prompt,
63
64
  completer=CompleteKey(sorted(item[value_key] for item in items)),
64
65
  **kwargs)
65
66
 
66
67
  bindings = dict()
67
68
 
69
+ #state variables for navigating display of matches
70
+ prev_value = None
68
71
  tabitem = -1
72
+ offset = 0
73
+ def reset_display():
74
+ nonlocal tabitem, offset
75
+ tabitem = -1
76
+ offset = 0
69
77
 
70
78
  def tab(n, nitems):
71
79
  nonlocal tabitem
@@ -73,7 +81,11 @@ def inputPalette(sheet, prompt, items,
73
81
  tabitem = (tabitem + n) % nitems
74
82
 
75
83
  def _draw_palette(value):
76
- words = value.lower().split()
84
+ nonlocal prev_value
85
+ words = value.split()
86
+ if value != prev_value:
87
+ reset_display()
88
+ prev_value = value
77
89
 
78
90
  if multiple and words:
79
91
  if value.endswith(' '):
@@ -83,8 +95,8 @@ def inputPalette(sheet, prompt, items,
83
95
  finished_words = words[:-1]
84
96
  unfinished_words = [words[-1]]
85
97
  else:
86
- unfinished_words = words
87
98
  finished_words = []
99
+ unfinished_words = words
88
100
 
89
101
  unuseditems = [item for item in items if item[value_key] not in finished_words]
90
102
 
@@ -92,25 +104,50 @@ def inputPalette(sheet, prompt, items,
92
104
 
93
105
  h = sheet.windowHeight
94
106
  w = min(100, sheet.windowWidth)
95
- nitems = min(h-1, sheet.options.disp_cmdpal_max)
107
+ nitems = min(h-2, sheet.options.disp_cmdpal_max)
108
+ if nitems <= 0:
109
+ return None
96
110
 
97
111
  useditems = []
98
112
  palrows = []
99
-
100
- for m in matches[:nitems]:
101
- useditems.append(m.match)
102
- palrows.append((m, m.match))
103
-
104
- favitems = sorted([item for item in unuseditems if item not in useditems],
105
- key=lambda item: -vd.usedInputs.get(item[value_key], 0))
106
-
107
- for item in favitems[:nitems-len(palrows)]:
108
- palrows.append((None, item))
113
+ n_results = 0
114
+ def read_matches(offset):
115
+ nonlocal useditems, palrows, value, n_results
116
+
117
+ useditems = []
118
+ palrows = []
119
+ for m in matches[offset:offset+nitems]:
120
+ useditems.append(m.match)
121
+ palrows.append((m, m.match))
122
+ n_results += len(matches)
123
+
124
+ #List matches only, usually. But list the available choices when there's no input,
125
+ #or (if multiple is True) they've just pressed space after a word.
126
+ if not unfinished_words:
127
+ favitems = sorted([item for item in unuseditems if item not in useditems],
128
+ key=lambda item: -vd.usedInputs.get(item[value_key], 0))
129
+ for item in favitems[offset-len(palrows):offset+nitems-len(palrows)]:
130
+ palrows.append((None, item))
131
+ n_results += len(favitems)
132
+ read_matches(offset)
133
+
134
+ def change_page(dir=+1):
135
+ nonlocal offset, n_results, nitems
136
+ new_offset = offset + dir*nitems
137
+ # constrain offset to be a multiple of nitems
138
+ new_offset = min(new_offset, ((n_results-1) // nitems)*nitems)
139
+ new_offset = max(new_offset, 0)
140
+ if new_offset == offset: return None
141
+ offset = new_offset
109
142
 
110
143
  navailitems = min(len(palrows), nitems)
111
144
 
112
145
  bindings['^I'] = lambda *args: tab(1, navailitems) or args
113
146
  bindings['KEY_BTAB'] = lambda *args: tab(-1, navailitems) or args
147
+ bindings['KEY_PPAGE'] = lambda *args: (change_page(-1) and read_matches(offset)) or args
148
+ bindings['KEY_NPAGE'] = lambda *args: (change_page(+1) and read_matches(offset)) or args
149
+ for numkey in '1234567890':
150
+ bindings.pop(numkey, None)
114
151
 
115
152
  for i in range(nitems-len(palrows)):
116
153
  palrows.append((None, None))
@@ -129,14 +166,13 @@ def inputPalette(sheet, prompt, items,
129
166
 
130
167
  if tabitem < 0 and palrows:
131
168
  _ , topitem = palrows[0]
132
- if not topitem: return
133
- if multiple:
134
- bindings[' '] = partial(add_to_input, value=topitem[value_key])
135
- bindings['^J'] = partial(accept_input_if_subset, value=topitem[value_key])
136
- else:
137
- bindings['^J'] = partial(accept_input, value=topitem[value_key])
169
+ if topitem:
170
+ if multiple:
171
+ bindings['^J'] = partial(accept_input_if_subset, value=topitem[value_key])
172
+ bindings[' '] = partial(add_to_input, value=topitem[value_key])
173
+ else:
174
+ bindings['^J'] = partial(accept_input, value=topitem[value_key])
138
175
  elif item and i == tabitem:
139
- if not item: return
140
176
  if multiple:
141
177
  bindings['^J'] = partial(accept_input_if_subset, value=item[value_key])
142
178
  bindings[' '] = partial(add_to_input, value=item[value_key])
@@ -146,7 +182,10 @@ def inputPalette(sheet, prompt, items,
146
182
 
147
183
  match_summary = formatter(m, item, trigger_key) if item else ' '
148
184
 
149
- clipdraw(sheet._scr, h-nitems-1+i, 0, match_summary, attr, w=w)
185
+ clipdraw(sheet._scr, h-nitems-2+i, 0, match_summary, attr, w=w)
186
+ attr = colors.color_cmdpalette
187
+ instr = 'Press [:keystrokes]PgUp/PgDn[/] to scroll items, [:keystrokes]Tab/Shift+Tab/Enter[/] to choose, [:keystrokes]Esc[/] to cancel.'
188
+ clipdraw(sheet._scr, h-2, 0, instr, attr, w=w)
150
189
 
151
190
  return None
152
191
 
@@ -180,7 +219,7 @@ def inputLongname(sheet):
180
219
  formatted_name = f'[:bold][:onclick {row.longname}]{formatted_longname}[/][/]'
181
220
  if vd.options.debug and match:
182
221
  keystrokes = f'[{match.score}]'
183
- r = f' [:keystrokes]{keystrokes.rjust(len(prompt)-5)}[/] '
222
+ r = f' [:keystrokes]{keystrokes.rjust(dispwidth(prompt)-5)}[/] '
184
223
  if trigger_key:
185
224
  r += f'[:keystrokes]{trigger_key}[/]'
186
225
  else:
@@ -213,4 +252,4 @@ def exec_longname(sheet, longname):
213
252
 
214
253
 
215
254
  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')
255
+ vd.addCommand('zSpace', 'exec-longname-simple', 'exec_longname(inputLongnameSimple())', 'execute command by its longname (without command palette)')
@@ -2,7 +2,7 @@ from copy import copy
2
2
  from statistics import mode, median, mean, stdev
3
3
 
4
4
  from visidata import vd, Column, ColumnAttr, vlen, RowColorizer, asyncthread, Progress, wrapply
5
- from visidata import BaseSheet, TableSheet, ColumnsSheet, SheetsSheet
5
+ from visidata import BaseSheet, TableSheet, ColumnsSheet, IndexSheet
6
6
 
7
7
 
8
8
  vd.option('describe_aggrs', 'mean stdev', 'numeric aggregators to calculate on Describe sheet', help=vd.help_aggregators)
@@ -114,7 +114,7 @@ class DescribeSheet(ColumnsSheet):
114
114
 
115
115
  TableSheet.addCommand('I', 'describe-sheet', 'vd.push(DescribeSheet(sheet.name+"_describe", source=[sheet]))', 'open Describe Sheet with descriptive statistics for all visible columns')
116
116
  BaseSheet.addCommand('gI', 'describe-all', 'vd.push(DescribeSheet("describe_all", source=vd.stackedSheets))', 'open Describe Sheet with description statistics for all visible columns from all sheets')
117
- SheetsSheet.addCommand('gI', 'describe-selected', 'vd.push(DescribeSheet("describe_all", source=selectedRows))', 'open Describe Sheet with all visible columns from selected sheets')
117
+ IndexSheet.addCommand('gI', 'describe-selected', 'vd.push(DescribeSheet("describe_all", source=selectedRows))', 'open Describe Sheet with all visible columns from selected sheets')
118
118
 
119
119
  DescribeSheet.addCommand('zs', 'select-cell', 'cursorRow.sheet.select(cursorValue)', 'select rows on source sheet which are being described in current cell')
120
120
  DescribeSheet.addCommand('zu', 'unselect-cell', 'cursorRow.sheet.unselect(cursorValue)', 'unselect rows on source sheet which are being described in current cell')
@@ -121,8 +121,10 @@ class ExpandedColumn(Column):
121
121
  def calcValue(self, row):
122
122
  return getitemdef(self.origCol.getValue(row), self.expr)
123
123
 
124
- def setValue(self, row, value):
124
+ def setValue(self, row, value, setModified=True):
125
125
  self.origCol.getValue(row)[self.expr] = value
126
+ if setModified:
127
+ self.origCol.sheet.setModified()
126
128
 
127
129
 
128
130
  @Sheet.api
@@ -181,10 +183,10 @@ Sheet.addCommand('g(', 'expand-cols', 'expand_cols_deep(visibleCols, depth=1)',
181
183
  Sheet.addCommand('z(', 'expand-col-depth', 'expand_cols_deep([cursorCol], depth=int(input("expand depth=", value=0)))', 'expand current column of containers to given depth (0=fully)')
182
184
  Sheet.addCommand('gz(', 'expand-cols-depth', 'expand_cols_deep(visibleCols, depth=int(input("expand depth=", value=0)))', 'expand all visible columns of containers to given depth (0=fully)')
183
185
 
184
- Sheet.addCommand(')', 'contract-col', 'contract_cols([cursorCol])', 'remove current column and siblings from sheet columns and unhide parent')
185
- Sheet.addCommand('g)', 'contract-cols', 'contract_cols(visibleCols)', 'remove all child columns and unhide toplevel parents')
186
- Sheet.addCommand('z)', 'contract-col-depth', 'contract_cols([cursorCol], depth=int(input("contract depth=", value=0)))', 'remove current column and siblings from sheet columns and unhide parent')
187
- Sheet.addCommand('gz)', 'contract-cols-depth', 'contract_cols(visibleCols, depth=int(input("contract depth=", value=0)))', 'remove all child columns and unhide toplevel parents')
186
+ Sheet.addCommand(')', 'contract-col', 'contract_cols([cursorCol])', 'remove current column and siblings from sheet columns and unhide parent (1 level)')
187
+ Sheet.addCommand('g)', 'contract-cols', 'contract_cols(visibleCols)', 'remove all child columns and unhide toplevel parents (1 level)')
188
+ Sheet.addCommand('z)', 'contract-col-depth', 'contract_cols([cursorCol], depth=int(input("contract depth=", value=0)))', 'remove current column and siblings from sheet columns and unhide parent, prompting for depth')
189
+ Sheet.addCommand('gz)', 'contract-cols-depth', 'contract_cols(visibleCols, depth=int(input("contract depth=", value=0)))', 'remove all child columns and unhide toplevel parents, prompting for depth')
188
190
 
189
191
  ColumnsSheet.addCommand(')', 'contract-source-cols', 'source[0].addColumn(contract_source_cols(someSelectedRows), index=cursorRowIndex)', 'contract selected columns into column group') #1702
190
192
 
@@ -60,10 +60,22 @@ class StaticSheet(Sheet):
60
60
  row.append(val)
61
61
 
62
62
 
63
+ @Sheet.api
64
+ def setcol_freeze(sheet, unfrozen): #2660 Contributed by @midichef
65
+ frozen = sheet.freeze_col(unfrozen)
66
+ frozen.name = unfrozen.name
67
+ unfrozen.hide()
68
+ vd.addUndoColNames([unfrozen])
69
+ unfrozen.name = frozen.name + '_unfrozen'
70
+ sheet.addColumnAtCursor(frozen)
71
+ vd.status(f'replaced {frozen.name} with frozen copy')
72
+
73
+
74
+ Sheet.addCommand("z'", 'setcol-freeze', 'setcol_freeze(cursorCol)', 'replace current column with a frozen copy, with all cells evaluated')
63
75
  Sheet.addCommand("'", 'freeze-col', 'sheet.addColumnAtCursor(freeze_col(cursorCol))', 'add a frozen copy of current column with all cells evaluated')
64
76
  Sheet.addCommand("g'", 'freeze-sheet', 'vd.push(StaticSheet(sheet)); status("pushed frozen copy of "+name)', 'open a frozen copy of current sheet with all visible columns evaluated')
65
- Sheet.addCommand("z'", 'cache-col', 'cursorCol.resetCache()', 'add/reset cache for current column')
77
+ Sheet.addCommand(None, 'cache-col', 'cursorCol.resetCache()', 'add/reset cache for current column')
66
78
  Sheet.addCommand("gz'", 'cache-cols', 'for c in visibleCols: c.resetCache()', 'add/reset cache for all visible columns')
67
79
 
68
- vd.addMenuItem('Column', 'Freeze', 'freeze-col')
80
+ vd.addMenuItem('Column', 'Freeze', 'setcol-freeze')
69
81
  vd.addMenuItem('File', 'Freeze', 'freeze-sheet')