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/graph.py CHANGED
@@ -1,10 +1,16 @@
1
1
  import math
2
2
 
3
- from visidata import VisiData, Canvas, Sheet, Progress, BoundingBox, Point
4
- from visidata import vd, asyncthread, dispwidth, colors, clipstr
3
+ from visidata import VisiData, Canvas, Sheet, Progress, BoundingBox, Point, ColumnsSheet
4
+ from visidata import vd, asyncthread, dispwidth, colors, clipstr, ColorAttr, update_attr
5
+ from visidata.type_date import date
6
+ from statistics import median
5
7
 
6
8
  vd.theme_option('color_graph_axis', 'bold', 'color for graph axis labels')
7
9
  vd.theme_option('disp_graph_tick_x', '╵', 'character for graph x-axis ticks')
10
+ vd.theme_option('color_graph_refline', '', 'color for graph reference value lines')
11
+ vd.theme_option('disp_graph_reflines_x_charset', '▏││▕', 'charset to render vertical reference lines on graph')
12
+ vd.theme_option('disp_graph_reflines_y_charset', '▔──▁', 'charset to render horizontal reference lines on graph')
13
+ vd.theme_option('disp_graph_multiple_reflines_char', '▒', 'char to render multiple parallel reflines')
8
14
 
9
15
 
10
16
  @VisiData.api
@@ -21,14 +27,18 @@ class InvertedCanvas(Canvas):
21
27
  'adjust visibleBox.xymin so that canvasPoint is plotted at plotterPoint'
22
28
  self.visibleBox.xmin = canvasPoint.x - self.canvasW(plotterPoint.x-self.plotviewBox.xmin)
23
29
  self.visibleBox.ymin = canvasPoint.y - self.canvasH(self.plotviewBox.ymax-plotterPoint.y)
24
- self.refresh()
30
+ self.resetBounds()
31
+
32
+ def rowsWithin(self, plotter_bbox):
33
+ return super().rowsWithin(plotter_bbox, invert_y=True)
25
34
 
26
35
  def zoomTo(self, bbox):
27
36
  super().zoomTo(bbox)
28
37
  self.fixPoint(Point(self.plotviewBox.xmin, self.plotviewBox.ymin),
29
- Point(bbox.xmin, bbox.ymax + 1/4*self.canvasCharHeight))
38
+ Point(bbox.xmin, bbox.ymax))
39
+ self.resetBounds()
30
40
 
31
- def scaleY(self, canvasY):
41
+ def scaleY(self, canvasY) -> int:
32
42
  'returns a plotter y coordinate for a canvas y coordinate, with the y direction inverted'
33
43
  return self.plotviewBox.ymax-round((canvasY-self.visibleBox.ymin)*self.yScaler)
34
44
 
@@ -66,6 +76,11 @@ class GraphSheet(InvertedCanvas):
66
76
  self.ylabel_maxw = 0
67
77
  super().__init__(*names, **kwargs)
68
78
 
79
+ self.reflines_x = []
80
+ self.reflines_y = []
81
+ self.reflines_char_x = {} # { x value in character coordinates -> character to use to draw that vertical line }
82
+ self.reflines_char_y = {} # { y value in character coordinates -> character to use to draw that horizontal line }
83
+
69
84
  vd.numericCols(self.xcols) or vd.fail('at least one numeric key col necessary for x-axis')
70
85
  self.ycols or vd.fail('%s is non-numeric' % '/'.join(yc.name for yc in kwargs.get('ycols')))
71
86
 
@@ -80,6 +95,7 @@ class GraphSheet(InvertedCanvas):
80
95
  nplotted = 0
81
96
 
82
97
  self.reset()
98
+ self.row_order = {}
83
99
 
84
100
  vd.status('loading data points')
85
101
  catcols = [c for c in self.xcols if not vd.isNumeric(c)]
@@ -95,6 +111,7 @@ class GraphSheet(InvertedCanvas):
95
111
 
96
112
  attr = self.plotColor(k)
97
113
  self.point(graph_x, graph_y, attr, row)
114
+ self.row_order[self.source.rowid(row)] = rownum
98
115
  nplotted += 1
99
116
  except Exception as e:
100
117
  nerrors += 1
@@ -106,11 +123,45 @@ class GraphSheet(InvertedCanvas):
106
123
 
107
124
  self.xzoomlevel=self.yzoomlevel=1.0
108
125
  self.resetBounds()
109
- self.refresh()
110
126
 
111
- def resetBounds(self):
112
- super().resetBounds()
127
+ def draw(self, scr):
128
+ windowHeight, windowWidth = scr.getmaxyx()
129
+ if self.needsRefresh:
130
+ self.render(windowHeight, windowWidth)
131
+
132
+ # required because we use clear_empty_squares=False for draw_pixels()
133
+ self.draw_empty(scr)
134
+ # draw reflines first so pixels draw over them
135
+ self.draw_reflines(scr)
136
+ # use clear_empty_squares to keep reflines
137
+ self.draw_pixels(scr, clear_empty_squares=False)
138
+ self.draw_labels(scr)
139
+
140
+ def draw_reflines(self, scr):
141
+ cursorBBox = self.plotterCursorBox
142
+ # draws only on character cells that have reflines, leaves other cells unaffected
143
+ for char_y in range(0, self.plotheight//4):
144
+ has_y_line = char_y in self.reflines_char_y.keys()
145
+ for char_x in range(0, self.plotwidth//2):
146
+ has_x_line = char_x in self.reflines_char_x.keys()
147
+ if has_x_line or has_y_line:
148
+ cattr = colors.color_refline
149
+ if has_x_line:
150
+ ch = self.reflines_char_x[char_x]
151
+ # where two lines cross, draw the vertical line, not the horizontal one
152
+ elif has_y_line:
153
+ ch = self.reflines_char_y[char_y]
154
+ # draw cursor
155
+ if cursorBBox.contains(char_x*2, char_y*4) or \
156
+ cursorBBox.contains(char_x*2+1, char_y*4+3):
157
+ cattr = update_attr(cattr, colors.color_current_row)
158
+ scr.addstr(char_y, char_x, ch, cattr.attr)
159
+
160
+ def resetBounds(self, refresh=True):
161
+ super().resetBounds(refresh=False)
113
162
  self.createLabels()
163
+ if refresh:
164
+ self.refresh()
114
165
 
115
166
  def moveToRow(self, rowstr):
116
167
  ymin, ymax = map(float, map(self.parseY, rowstr.split()))
@@ -118,6 +169,43 @@ class GraphSheet(InvertedCanvas):
118
169
  self.cursorBox.h = ymax-ymin
119
170
  return True
120
171
 
172
+ def plot_elements(self, invert_y=True):
173
+ self.plot_reflines()
174
+ super().plot_elements(invert_y=True)
175
+
176
+ def plot_reflines(self):
177
+ self.reflines_char_x = {}
178
+ self.reflines_char_y = {}
179
+
180
+ bb = self.visibleBox
181
+ xmin, ymin, xmax, ymax = bb.xmin, bb.ymin, bb.xmax, bb.ymax
182
+
183
+ for data_y in self.reflines_y:
184
+ data_y = float(data_y)
185
+ if data_y >= ymin and data_y <= ymax:
186
+ char_y, offset = divmod(self.scaleY(data_y), 4)
187
+ chars = self.options.disp_graph_reflines_y_charset
188
+ # if we're drawing two different reflines in the same square, fill it with a different char
189
+ if char_y in self.reflines_char_y and self.reflines_char_y[char_y] != chars[offset]:
190
+ self.reflines_char_y[char_y] = vd.options.disp_graph_multiple_reflines_char
191
+ else:
192
+ self.reflines_char_y[char_y] = chars[offset]
193
+
194
+ for data_x in self.reflines_x:
195
+ data_x = float(data_x)
196
+ if data_x >= xmin and data_x <= xmax:
197
+ plot_x = self.scaleX(data_x)
198
+ # plot_x is an integer count of plotter pixels, and each character box has 2 plotter pixels
199
+ char_x = plot_x // 2
200
+ # To subdivide the 2 plotter pixels per square into 4 zones, we have to first multiply by 2.
201
+ offset = 2*plot_x % 4
202
+ chars = self.options.disp_graph_reflines_x_charset
203
+ # if we're drawing two different reflines in the same square, fill it with a different char
204
+ if char_x in self.reflines_char_x and self.reflines_char_x[char_x] != chars[offset]:
205
+ self.reflines_char_y[char_x] = vd.options.disp_graph_multiple_reflines_char
206
+ else:
207
+ self.reflines_char_x[char_x] = chars[offset]
208
+
121
209
  def moveToCol(self, colstr):
122
210
  xmin, xmax = map(float, map(self.parseX, colstr.split()))
123
211
  self.cursorBox.xmin = xmin
@@ -158,20 +246,23 @@ class GraphSheet(InvertedCanvas):
158
246
  return self.ycols[0].type(txt)
159
247
 
160
248
  def add_y_axis_label(self, frac):
161
- txt = self.formatYLabel(self.visibleBox.ymin + frac*self.visibleBox.h)
249
+ label_data_y = self.visibleBox.ymin + frac*self.visibleBox.h
250
+ txt = self.formatYLabel(label_data_y)
162
251
  w = (dispwidth(txt)+1)*2
163
252
  if self.ylabel_maxw < w:
164
253
  self.ylabel_maxw = w
254
+ y = self.scaleY(label_data_y)
165
255
 
166
256
  # plot y-axis labels on the far left of the canvas, but within the plotview height-wise
167
- self.plotlabel(0, self.plotviewBox.ymin + (1.0-frac)*self.plotviewBox.h, txt, 'graph_axis')
257
+ self.plotlabel(0, y, txt, 'graph_axis')
168
258
 
169
259
  def add_x_axis_label(self, frac):
170
- txt = self.formatXLabel(self.visibleBox.xmin + frac*self.visibleBox.w)
260
+ label_data_x = self.visibleBox.xmin + frac*self.visibleBox.w
261
+ txt = self.formatXLabel(label_data_x)
171
262
  tick = vd.options.disp_graph_tick_x or ''
172
263
 
173
264
  # plot x-axis labels below the plotviewBox.ymax, but within the plotview width-wise
174
- x = self.plotviewBox.xmin + frac*self.plotviewBox.w
265
+ x = self.scaleX(label_data_x)
175
266
 
176
267
  if frac < 1.0:
177
268
  txt = tick + txt
@@ -213,9 +304,67 @@ class GraphSheet(InvertedCanvas):
213
304
  xname, _ = clipstr(xname, self.left_margin//2-2)
214
305
  self.plotlabel(0, self.plotviewBox.ymax+4, xname+'»', 'graph_axis')
215
306
 
307
+ def rowsWithin(self, plotter_bbox):
308
+ 'return list of deduped rows within plotter_bbox'
309
+ rows = super().rowsWithin(plotter_bbox)
310
+ return sorted(rows, key=lambda r: self.row_order[self.source.rowid(r)])
311
+
312
+ def draw_refline_x(self):
313
+ xcol = vd.numericCols(self.xcols)[0]
314
+ xtype = xcol.type
315
+ val = median(xcol.getValues(self.sourceRows))
316
+ suggested = format_input_value(val, xtype)
317
+ xstrs = vd.input("add line(s) at x = ", type="reflinex", value=suggested, defaultLast=True).split()
318
+
319
+ for xstr in xstrs:
320
+ vals = [ v.strip() for v in xstr.split(',') ]
321
+ if len(vals) != len(self.xcols):
322
+ vd.fail(f'must have {len(self.xcols)} x values, had {len(vals)} values: {xstr}')
323
+ self.reflines_x += [xtype(val) for xcol, val in zip(self.xcols, vals) if xtype(val) not in self.reflines_x ]
324
+ self.refresh()
325
+
326
+ def draw_refline_y(self):
327
+ ytype = self.ycols[0].type
328
+ val = median(self.ycols[0].getValues(self.sourceRows))
329
+ suggested = format_input_value(val, ytype)
330
+ ystrs = vd.input("add line(s) at y = ", type="refliney", value=suggested, defaultLast=True).split()
331
+
332
+ self.reflines_y += [ ytype(y) for y in ystrs if ytype(y) not in self.reflines_y ]
333
+ self.refresh()
334
+
335
+ def erase_refline_x(self):
336
+ if len(self.reflines_x) == 0:
337
+ vd.fail(f'no x refline to erase')
338
+ xtype = vd.numericCols(self.xcols)[0].type
339
+ suggested = format_input_value(self.reflines_x[0], xtype)
340
+
341
+ xstrs = vd.input('remove line(s) at x = ', value=suggested, type='reflinex', defaultLast=True).split()
342
+ for input_x in xstrs:
343
+ self.reflines_x.remove(xtype(input_x))
344
+ self.refresh()
345
+
346
+ def erase_refline_y(self):
347
+ ytype = self.ycols[0].type
348
+ suggested = format_input_value(self.reflines_y[0], ytype) if self.reflines_y else ''
349
+ ystrs = vd.input('remove line(s) at y = ', value=suggested, type='refliney', defaultLast=True).split()
350
+ for y in ystrs:
351
+ try:
352
+ self.reflines_y.remove(ytype(y))
353
+ except ValueError:
354
+ vd.fail(f'value {y} not in reflines_y')
355
+ self.refresh()
356
+
357
+ def format_input_value(val, type):
358
+ '''format a value for entry into vd.input(), so its representation has no spaces and no commas'''
359
+ if type is date:
360
+ return val.strftime('%Y-%m-%d')
361
+ else:
362
+ return str(val)
363
+
216
364
 
217
365
  Sheet.addCommand('.', 'plot-column', 'vd.push(GraphSheet(sheet.name, "graph", source=sheet, sourceRows=rows, xcols=keyCols, ycols=numericCols([cursorCol])))', 'plot current numeric column vs key columns; numeric key column is used for x-axis, while categorical key columns determine color')
218
366
  Sheet.addCommand('g.', 'plot-numerics', 'vd.push(GraphSheet(sheet.name, "graph", source=sheet, sourceRows=rows, xcols=keyCols, ycols=numericCols(nonKeyVisibleCols)))', 'plot a graph of all visible numeric columns vs key columns')
367
+ ColumnsSheet.addCommand('g.', 'plot-source-selected', 'vd.push(GraphSheet(sheet.source[0].name, "graph", source=source[0], sourceRows=source[0].rows, xcols=source[0].keyCols, ycols=numericCols(selectedRows)))', 'plot a graph of all selected columns vs key columns on source sheet')
219
368
 
220
369
  # swap directions of up/down
221
370
  InvertedCanvas.addCommand(None, 'go-up', 'if cursorBox: sheet.cursorBox.ymin += cursorBox.h', 'move cursor up by its height')
@@ -247,6 +396,12 @@ def set_x(sheet, s):
247
396
  Canvas.addCommand('y', 'resize-y-input', 'sheet.set_y(input("set ymin ymax="))', 'set ymin/ymax on graph axes')
248
397
  Canvas.addCommand('x', 'resize-x-input', 'sheet.set_x(input("set xmin xmax="))', 'set xmin/xmax on graph axes')
249
398
 
399
+ GraphSheet.addCommand('gx', 'draw-refline-x', 'sheet.draw_refline_x()', 'draw a vertical line at x-values (space-separated)')
400
+ GraphSheet.addCommand('gy', 'draw-refline-y', 'sheet.draw_refline_y()', 'draw a horizontal line at y-values (space-separated)')
401
+ GraphSheet.addCommand('zx', 'erase-refline-x', 'sheet.erase_refline_x()', 'remove a horizontal line at x-values (space-separated)')
402
+ GraphSheet.addCommand('zy', 'erase-refline-y', 'sheet.erase_refline_y()', 'remove a vertical line at y-values (space-separated)')
403
+ GraphSheet.addCommand('gzx', 'erase-reflines-x', 'sheet.reflines_x = []; sheet.refresh()', 'erase all vertical x-value lines')
404
+ GraphSheet.addCommand('gzy', 'erase-reflines-y', 'sheet.reflines_y = []; sheet.refresh()', 'erase any horizontal y-value lines')
250
405
 
251
406
  vd.addGlobals({
252
407
  'GraphSheet': GraphSheet,
@@ -256,4 +411,10 @@ vd.addGlobals({
256
411
  vd.addMenuItems('''
257
412
  Plot > Graph > current column > plot-column
258
413
  Plot > Graph > all numeric columns > plot-numerics
414
+ Plot > Refline > draw at x coord (vert) > draw-refline-x
415
+ Plot > Refline > draw at y coord (horiz) > draw-refline-y
416
+ Plot > Refline > erase at x coord (vert) > erase-refline-x
417
+ Plot > Refline > erase at y coord (horiz) > erase-refline-y
418
+ Plot > Refline > erase all x (vert) > erase-reflines-x
419
+ Plot > Refline > erase all y (horiz) > erase-reflines-y
259
420
  ''')
visidata/guide.py CHANGED
@@ -8,7 +8,8 @@ Each guide shows you how to use a particular feature in VisiData. Gray guides ha
8
8
  import re
9
9
 
10
10
  from visidata import vd, BaseSheet, Sheet, ItemColumn, Column, VisiData, ENTER, RowColorizer, AttrDict, MissingAttrFormatter
11
- from visidata import wraptext
11
+ from visidata import wraptext, Path, CellColorizer
12
+ import visidata
12
13
 
13
14
  guides_list = '''
14
15
  GuideIndex ("A Guide to VisiData Guides (you are here)")
@@ -17,10 +18,11 @@ MenuGuide ("The VisiData Menu System")
17
18
  CommandsSheet ("How to find the command you want run")
18
19
 
19
20
  # real barebones basics
20
- MovementGuide ("Movement and Search")
21
+ MovementGuide ("Movement and search")
21
22
  InputGuide ("Input keystrokes")
22
23
  SortGuide ("Sorting")
23
- TypesSheet ("The basic type system")
24
+ ColumnsGuide ("Resize, hide, and rename columns")
25
+ TypesSheet ("Column types")
24
26
  CommandLog ("Undo and Replay")
25
27
 
26
28
  # rev this thing up
@@ -45,7 +47,7 @@ AggregatorsSheet ("Aggregations like sum, mean, and distinct")
45
47
  FrequencyTable ("Frequency Tables are how you GROUP BY")
46
48
  PivotGuide ("Pivot Tables are just Frequency Tables with more columns")
47
49
  MeltGuide ("Melt is just Unpivot")
48
- JsonGuide ("Some special features for JSON") # with expand/contract, unfurl
50
+ JsonSheet ("Some special features for JSON") # with expand/contract, unfurl
49
51
  RegexGuide ("Matching and Transforming Strings with Regex")
50
52
  GraphSheet ("Basic scatterplots and other graphs")
51
53
  WindowFunctionGuide ("Perform operations on groups of rows")
@@ -61,10 +63,15 @@ ColorSheet ("See available colors")
61
63
  MacrosSheet ("Recording macros")
62
64
  MemorySheet ("Making note of certain values")
63
65
 
66
+ # Specific use cases
67
+
68
+ XsvGuide ("CSV/TSV and other text-delimited formats")
69
+ GrepSheet ("Load output of grep-like tools")
70
+
64
71
  # advanced usage and developers
65
72
 
66
73
  ThreadsSheet ("Threads past and present")
67
- PyobjSheet ("Inspecting internal Python objects")
74
+ DeveloperGuide ("Inspecting internal Python objects")
68
75
 
69
76
  # appendices
70
77
 
@@ -74,8 +81,10 @@ InputEditorGuide ("Using the builtin line editor")
74
81
  vd.guides = {} # name -> guidecls
75
82
 
76
83
  @VisiData.api
77
- def addGuide(vd, name, guidecls):
78
- vd.guides[name] = guidecls
84
+ def addGuide(vd, name):
85
+ guideSource = Path(vd.pkg_resources_files(visidata)/f'guides/{name}.md')
86
+ if guideSource.exists():
87
+ vd.guides[name] = GuideSheet(name, source=guideSource)
79
88
 
80
89
  @VisiData.api
81
90
  class GuideIndex(Sheet):
@@ -83,7 +92,7 @@ class GuideIndex(Sheet):
83
92
 
84
93
  rowtype = 'guides' # rowdef: list(guide number, guide name, topic description, points, max_points)
85
94
  columns = [
86
- ItemColumn('n', 0, type=int),
95
+ ItemColumn('n', 0, width=0, type=int),
87
96
  ItemColumn('name', 1, width=0),
88
97
  ItemColumn('topic', 2, width=60),
89
98
  ]
@@ -95,7 +104,9 @@ class GuideIndex(Sheet):
95
104
  for line in guides_list.splitlines():
96
105
  m = re.search(r'(\w+?) \("(.*)"\)', line)
97
106
  if m:
98
- yield [i] + list(m.groups())
107
+ guidename, description = list(m.groups())
108
+ vd.addGuide(guidename)
109
+ yield [i, guidename, description]
99
110
  i += 1
100
111
 
101
112
  def openRow(self, row):
@@ -103,25 +114,25 @@ class GuideIndex(Sheet):
103
114
  return vd.getGuide(name)
104
115
 
105
116
  class OptionHelpGetter:
106
- 'For easy and consistent formatting in sidebars and helpstrings, use {vd.options.help.opt_name}.'
117
+ 'For easy and consistent formatting in sidebars and helpstrings, use {help.options.opt_name}.'
107
118
  def __getattr__(self, optname):
108
119
  opt = vd.options._get(optname, 'default')
109
- return f'[:onclick options-sheet {optname}]`{optname}`[/]: to {opt.helpstr} (default: {opt.value})'
120
+ return f'[:onclick options-sheet {optname}][:longname_guide]{optname}[/][/]: {opt.helpstr} (default: {opt.value})'
110
121
 
111
122
 
112
123
  class CommandHelpGetter:
113
- 'For easy and consistent formatting in sidebars and helpstrings, use {vd.commands.help.long_name}.'
124
+ 'For easy and consistent formatting in sidebars and helpstrings, use {help.commands.long_name}.'
114
125
  def __init__(self, cls):
115
126
  self.cls = cls
116
127
  self.helpsheet = vd.HelpSheet()
117
- self.helpsheet.ensureLoaded()
128
+ vd.sync(self.helpsheet.ensureLoaded())
118
129
 
119
130
  def __getattr__(self, k):
120
131
  return self.__getitem__(k)
121
132
 
122
133
  def __getitem__(self, k):
123
134
  longname = k.replace('_', '-')
124
- binding = self.helpsheet.revbinds.get(longname, [None])[0]
135
+ binding = self.helpsheet.revbinds.get(longname, [None])[0] or '<unbound>'
125
136
  # cmddict has a SheetClass associated with each command
126
137
  # go through all the parents of the Sheet type, to look for the command
127
138
  for cls in self.cls.superclasses():
@@ -136,11 +147,11 @@ class CommandHelpGetter:
136
147
  if not m:
137
148
  m = re.search(r'input(\w*)\("', cmd.execstr, re.IGNORECASE)
138
149
  if m:
139
- inputtype = m.groups()[0].lower()
140
- binding += f'[:45] {inputtype}[/]'
150
+ inputtype = m.groups()[0].lower() or 'input'
151
+ binding += f' <{inputtype}>'
141
152
 
142
153
  helpstr = cmd.helpstr
143
- return f'`{binding}` (`{longname}`) to {helpstr}'
154
+ return f'[:code]{binding}[/] ([:longname_guide]{longname}[/]) to {helpstr}'
144
155
 
145
156
 
146
157
  class GuideSheet(Sheet):
@@ -151,14 +162,31 @@ class GuideSheet(Sheet):
151
162
  ItemColumn('guide', 1, width=80, displayer='full'),
152
163
  ]
153
164
  precious = False
154
- guide_text = ''
155
- sheettype = Sheet
165
+ colorizers = [CellColorizer(4, 'color_sidebar', lambda s,c,r,v: True)]
156
166
 
157
167
  def iterload(self):
168
+ self.metadata = AttrDict(sheettype='Sheet')
169
+ text = self.source.open(mode='r').read()
158
170
  winWidth = 78
159
- helper = AttrDict(commands=CommandHelpGetter(self.sheettype),
171
+
172
+ # parsing front matter
173
+ sections = text.split('---\n', maxsplit=2)
174
+ for section in sections[:-1]:
175
+ for config in section.splitlines():
176
+ config = config.strip()
177
+ if config:
178
+ try:
179
+ key, val = config.split(': ', maxsplit=1)
180
+ except ValueError:
181
+ vd.fail('incorrect front matter syntax')
182
+ self.metadata[key] = val
183
+
184
+ # formatting text
185
+ helper = AttrDict(commands=CommandHelpGetter(vd.getGlobals()[self.metadata.sheettype]),
160
186
  options=OptionHelpGetter())
161
- guidetext = MissingAttrFormatter().format(self.guide_text, help=helper, vd=vd)
187
+ guidetext = MissingAttrFormatter().format(sections[-1], help=helper, vd=vd)
188
+
189
+ # parsing guide
162
190
  for startingLine, text in enumerate(guidetext.splitlines()):
163
191
  text = text.strip()
164
192
  if text:
@@ -168,11 +196,10 @@ class GuideSheet(Sheet):
168
196
  yield [startingLine+1, text]
169
197
 
170
198
 
171
-
172
199
  @VisiData.api
173
200
  def getGuide(vd, name): # -> GuideSheet()
174
201
  if name in vd.guides:
175
- return vd.guides[name]()
202
+ return vd.guides[name]
176
203
  vd.warning(f'no guide named {name}')
177
204
 
178
205
  BaseSheet.addCommand('', 'open-guide-index', 'vd.push(GuideIndex("VisiData_Guide"))', 'open VisiData guides table of contents')
@@ -188,8 +215,15 @@ def inputKeys(vd, prompt):
188
215
 
189
216
  @BaseSheet.api
190
217
  def getCommandInfo(sheet, keys):
218
+ if not keys:
219
+ return ''
191
220
  cmd = sheet.getCommand(keys)
192
- return CommandHelpGetter(type(sheet))[cmd.longname]
221
+ if cmd:
222
+ return CommandHelpGetter(type(sheet))[cmd.longname]
223
+ else:
224
+ vd.warning(f'no command bound to {keys} on {sheet}')
225
+
226
+ GuideSheet.options.color_current_row = "underline"
193
227
 
194
228
  vd.addCommand('', 'show-command-info', 'status(getCommandInfo(inputKeys("get command for keystrokes: ")))', 'show longname and helpstring for keybinding')
195
229
 
@@ -197,4 +231,6 @@ vd.addMenuItems('''
197
231
  Help > VisiData Feature Guides > open-guide-index
198
232
  ''')
199
233
 
200
- vd.addGlobals({'GuideSheet':GuideSheet, "CommandHelpGetter": CommandHelpGetter, "OptionHelpGetter": OptionHelpGetter})
234
+ vd.optalias('guides', 'preplay', 'open-guide-index')
235
+
236
+ vd.addGlobals({"CommandHelpGetter": CommandHelpGetter, "OptionHelpGetter": OptionHelpGetter})
@@ -0,0 +1,48 @@
1
+ # Copy, Cut and Paste...
2
+
3
+ ## ...rows to/from the internal clipboard
4
+
5
+ - {help.commands.copy_row}
6
+ - {help.commands.copy_selected}
7
+
8
+ - {help.commands.cut_row}
9
+ - {help.commands.cut_selected}
10
+
11
+ - {help.commands.paste_after}
12
+ - {help.commands.paste_before}
13
+
14
+ ## ...cells to/from the internal clipboard
15
+
16
+ - {help.commands.copy_cell}
17
+ - {help.commands.cut_cell}
18
+ - {help.commands.paste_cell}
19
+
20
+ ## ...columns from the intenral clipboard
21
+
22
+ - {help.commands.copy_cells}
23
+ - {help.commands.cut_cells}
24
+ - {help.commands.setcol_clipboard}
25
+
26
+ If there are fewer values in the clipboard than in the selected target column, the clipboard values will be repeated in sequence.
27
+
28
+ ## ....to/from the system clipboard
29
+
30
+ - {help.commands.syscopy_cell}
31
+ - {help.commands.syscopy_cells}
32
+
33
+ - {help.commands.syscopy_row}
34
+ - {help.commands.syscopy_selected}
35
+
36
+ - {help.commands.syspaste_cells}
37
+ - {help.commands.syspaste_cells_selected}
38
+
39
+ ## Paste a new sheet from the system clipboard
40
+
41
+ - {help.commands.open_syspaste}
42
+
43
+ The paste command will prompt for the filetype, so the input data (from the clipboard) can be in any supported format.
44
+
45
+ ## Relevant options
46
+
47
+ - {help.options.clipboard_copy_cmd}
48
+ - {help.options.clipboard_paste_cmd}
@@ -0,0 +1,52 @@
1
+ # Resize, hide, and rename columns
2
+
3
+ ## Resize the current column
4
+
5
+ - {help.commands.resize_col_max}
6
+ - {help.commands.resize_col_input}
7
+ - {help.commands.resize_col_half}
8
+
9
+ ## Resize multiple columns
10
+
11
+ - {help.commands.resize_cols_input}
12
+
13
+ ## Hide and Unhide columns
14
+
15
+ Clean up the sheet by hiding unwanted columns. Hidden columns are still present, but not visible in the main set of columns.
16
+ (They are minimized on the right side and can be unhidden with {help.commands.resize_col_max}.
17
+
18
+ - {help.commands.hide_col}
19
+ - {help.commands.unhide_cols}
20
+
21
+ Use {help.commands.columns_sheet} to control visibility as well.
22
+
23
+ ## Rename columns
24
+
25
+ - {help.commands.rename_col}
26
+
27
+ ## Rename one or more columns using data
28
+
29
+ Rename one or more columns using data from the sheet:
30
+
31
+ - {help.commands.rename_col_selected}
32
+ - {help.commands.rename_cols_row}
33
+ - {help.commands.rename_cols_selected}
34
+
35
+ ## Clean column names
36
+
37
+ Rename columns to be valid Python identifiers that can be used in column
38
+ expressions.
39
+
40
+ - {help.commands.normalize_col_names}
41
+ - {help.commands.clean_names}
42
+
43
+ ## Change row height
44
+
45
+ Sometimes the data for a cell will not fit within the current width. If the
46
+ data is large, or spans multiple lines, change the row height to show more for
47
+ the current column width. Row height adjustments can also be combined with
48
+ column width changes.
49
+
50
+ - {help.commands.toggle_multiline}
51
+ - {help.commands.resize_height_input}
52
+ - {help.commands.resize_height_max}
@@ -0,0 +1,28 @@
1
+ # How to find commands
2
+
3
+ ## Command Palette
4
+
5
+ - {help.commands.exec_longname}
6
+
7
+ Start typing a command longname or keyword in its helpstring.
8
+
9
+ - [:code]Enter[/] to execute top command.
10
+ - [:code]Tab[/] to highlight top command and provide a numeric jumplist.
11
+
12
+ When a command is highlighted:
13
+
14
+ - [:code]Tab[/]/[:code]Shift+Tab[/] to cycle highlighted command.
15
+ - [:code]Enter[/] to execute highlighted command.
16
+ - [:code]0-9[/] to execute numbered command.
17
+
18
+ ## Command Sheet
19
+
20
+ The Command Sheet lists all of the available commands, their keyboard bindings, function names, and other information.
21
+
22
+ - {help.commands.help_commands_all}
23
+ - {help.commands.help_commands}
24
+
25
+ 'description' and 'longname' column: useful to find rows that share a longname suffix or prefix. E.g, `save` or `selected`.
26
+
27
+ 'keystrokes' and 'all_bindings' column: useful to search for keybindings. E.g, `Ctrl` or `F`.
28
+
@@ -0,0 +1,34 @@
1
+ ---
2
+ sheettype: DirSheet
3
+ ---
4
+ # Directory Sheet
5
+
6
+ The **DirSheet** is a display of files and folders in the *source* directory.
7
+
8
+ To load a **DirSheet**:
9
+
10
+ - provide a directory as a source on the CLI (e.g. [:code]vd sample_data/[/])
11
+ - {help.commands.open_dir_current} (equivalent to [:code]vd .[/])
12
+
13
+ Use the **DirSheet** to find and open files:
14
+
15
+ - {help.commands.open_row_file}
16
+ - {help.commands.open_rows}
17
+ - {help.commands.open_dir_parent}
18
+ - {help.commands.sysopen_row}
19
+
20
+ Use the **DirSheet** as a file manager:
21
+
22
+ - {help.commands.copy_row}
23
+ - {help.commands.copy_selected}
24
+ - [:keys]d[/] ([:longname]delete-row[/]) to delete file from filesystem
25
+ - [:keys]e[/] ([:longname]edit-cell[/]) to change file metadata (all of the file metadata, except for *filetype*, is modifiable)
26
+
27
+ Modifications on the **DirSheet** are deferred - they do not take effect on the filesystem itself until they are confirmed.
28
+ - {help.commands.commit_sheet}
29
+
30
+ ## Options of Interest (must reload to take effect)
31
+
32
+ - pass the flag [:code]-r[/] ([:code]--recursive[/]) on the CLI to display all of the files in all subfolders
33
+ - {help.options.dir_depth}
34
+ - {help.options.dir_hidden}
@@ -0,0 +1,17 @@
1
+ # What was that error?
2
+
3
+ Status messages include [:warning]warnings[/] and [:error]errors[/].
4
+
5
+ A command may issue a [:warning]warning[/] status and continue running.
6
+ A command that [:warning]fails[/] or [:error]errors[/] is aborted.
7
+
8
+ ## Investigating errors further
9
+
10
+ If a Python Exception like [:error]RuntimeError[/] appears in the sidebar:
11
+
12
+ - {help.commands.error_recent}
13
+ - {help.commands.errors_all}
14
+
15
+ If [:note_type]{vd.options.disp_note_fmtexc}[/] or [:error]{vd.options.disp_note_getexc}[/] appear inside a cell, it indicates an error happened during calculation, type-conversion, or formatting. When the cursor is on an error cell:
16
+
17
+ - {help.commands.error_cell}