visidata 2.11.dev0__py3-none-any.whl → 3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +72 -91
- visidata/_input.py +263 -44
- visidata/_open.py +84 -29
- visidata/_types.py +22 -4
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +65 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- visidata/apps/vgit/__main__.py +3 -0
- visidata/apps/vgit/abort.py +23 -0
- visidata/apps/vgit/blame.py +76 -0
- visidata/apps/vgit/branch.py +153 -0
- visidata/apps/vgit/config.py +95 -0
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- visidata/apps/vgit/grep.py +37 -0
- visidata/apps/vgit/log.py +81 -0
- visidata/apps/vgit/main.py +55 -0
- visidata/apps/vgit/remote.py +57 -0
- visidata/apps/vgit/repos.py +71 -0
- visidata/apps/vgit/setup.py +37 -0
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- visidata/apps/vgit/statusbar.py +34 -0
- visidata/basesheet.py +59 -50
- visidata/canvas.py +251 -99
- visidata/choose.py +15 -11
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +84 -18
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +89 -114
- visidata/color.py +142 -56
- visidata/column.py +134 -131
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +5 -1
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +32 -6
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +163 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +4 -2
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +33 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +75 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +180 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +17 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +3 -5
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +48 -10
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macos.py +1 -1
- visidata/macros.py +130 -41
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -154
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -147
- visidata/man/vd.txt +291 -151
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +79 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +131 -43
- visidata/pivot.py +74 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +50 -201
- visidata/rename_col.py +20 -0
- visidata/save.py +42 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -24
- visidata/sheets.py +229 -257
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +113 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +62 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +17 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +87 -39
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +18 -6
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.dev0.dist-info/RECORD +0 -142
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/graph.py
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
|
1
|
+
import math
|
2
2
|
|
3
|
-
|
3
|
+
from visidata import VisiData, Canvas, Sheet, Progress, BoundingBox, Point
|
4
|
+
from visidata import vd, asyncthread, dispwidth, colors, clipstr
|
5
|
+
|
6
|
+
vd.theme_option('color_graph_axis', 'bold', 'color for graph axis labels')
|
7
|
+
vd.theme_option('disp_graph_tick_x', '╵', 'character for graph x-axis ticks')
|
4
8
|
|
5
9
|
|
6
10
|
@VisiData.api
|
@@ -9,37 +13,67 @@ def numericCols(vd, cols):
|
|
9
13
|
|
10
14
|
|
11
15
|
class InvertedCanvas(Canvas):
|
16
|
+
@asyncthread
|
17
|
+
def render_async(self):
|
18
|
+
self.plot_elements(invert_y=True)
|
19
|
+
|
20
|
+
def fixPoint(self, plotterPoint, canvasPoint):
|
21
|
+
'adjust visibleBox.xymin so that canvasPoint is plotted at plotterPoint'
|
22
|
+
self.visibleBox.xmin = canvasPoint.x - self.canvasW(plotterPoint.x-self.plotviewBox.xmin)
|
23
|
+
self.visibleBox.ymin = canvasPoint.y - self.canvasH(self.plotviewBox.ymax-plotterPoint.y)
|
24
|
+
self.refresh()
|
25
|
+
|
12
26
|
def zoomTo(self, bbox):
|
13
27
|
super().zoomTo(bbox)
|
14
|
-
self.fixPoint(Point(self.plotviewBox.xmin, self.plotviewBox.
|
15
|
-
|
16
|
-
def plotpixel(self, x, y, attr, row=None):
|
17
|
-
y = self.plotviewBox.ymax-y
|
18
|
-
self.pixels[y][x][attr].append(row)
|
28
|
+
self.fixPoint(Point(self.plotviewBox.xmin, self.plotviewBox.ymin),
|
29
|
+
Point(bbox.xmin, bbox.ymax + 1/4*self.canvasCharHeight))
|
19
30
|
|
20
31
|
def scaleY(self, canvasY):
|
21
|
-
'returns plotter y coordinate, with y
|
22
|
-
|
23
|
-
return (self.plotviewBox.ymax-plotterY+4)
|
32
|
+
'returns a plotter y coordinate for a canvas y coordinate, with the y direction inverted'
|
33
|
+
return self.plotviewBox.ymax-round((canvasY-self.visibleBox.ymin)*self.yScaler)
|
24
34
|
|
25
|
-
def
|
26
|
-
|
35
|
+
def unscaleY(self, plotterY_inverted):
|
36
|
+
'performs the inverse of scaleY, returns a canvas y coordinate'
|
37
|
+
return (self.plotviewBox.ymax-plotterY_inverted)/self.yScaler + self.visibleBox.ymin
|
27
38
|
|
28
39
|
@property
|
29
40
|
def canvasMouse(self):
|
30
41
|
p = super().canvasMouse
|
31
|
-
|
42
|
+
if not p: return None
|
43
|
+
p.y = self.unscaleY(self.plotterMouse.y)
|
32
44
|
return p
|
33
45
|
|
46
|
+
def calcTopCursorY(self):
|
47
|
+
'ymin for the cursor that will align its top with the top edge of the graph'
|
48
|
+
return self.visibleBox.ymax - self.cursorBox.h
|
49
|
+
|
50
|
+
def calcBottomCursorY(self):
|
51
|
+
# Shift by 1 plotter pixel, like with goTopCursorY for Canvas. But shift in the
|
52
|
+
# opposite direction, because the y-coordinate system is inverted.
|
53
|
+
'ymin for the cursor that will align its bottom with the bottom edge of the graph'
|
54
|
+
return self.visibleBox.ymin - (1/4 * self.canvasCharHeight)
|
55
|
+
|
56
|
+
def startCursor(self):
|
57
|
+
res = super().startCursor()
|
58
|
+
if not res: return None
|
59
|
+
# Since the y coordinates for plotting increase in the opposite
|
60
|
+
# direction from Canvas, the cursor has to be shifted.
|
61
|
+
self.cursorBox.ymin -= self.canvasCharHeight
|
34
62
|
|
35
63
|
# provides axis labels, legend
|
36
64
|
class GraphSheet(InvertedCanvas):
|
37
65
|
def __init__(self, *names, **kwargs):
|
66
|
+
self.ylabel_maxw = 0
|
38
67
|
super().__init__(*names, **kwargs)
|
39
68
|
|
40
69
|
vd.numericCols(self.xcols) or vd.fail('at least one numeric key col necessary for x-axis')
|
41
70
|
self.ycols or vd.fail('%s is non-numeric' % '/'.join(yc.name for yc in kwargs.get('ycols')))
|
42
71
|
|
72
|
+
def resetCanvasDimensions(self, windowHeight, windowWidth):
|
73
|
+
if self.left_margin < self.ylabel_maxw:
|
74
|
+
self.left_margin = self.ylabel_maxw
|
75
|
+
super().resetCanvasDimensions(windowHeight, windowWidth)
|
76
|
+
|
43
77
|
@asyncthread
|
44
78
|
def reload(self):
|
45
79
|
nerrors = 0
|
@@ -62,10 +96,10 @@ class GraphSheet(InvertedCanvas):
|
|
62
96
|
attr = self.plotColor(k)
|
63
97
|
self.point(graph_x, graph_y, attr, row)
|
64
98
|
nplotted += 1
|
65
|
-
except Exception:
|
99
|
+
except Exception as e:
|
66
100
|
nerrors += 1
|
67
|
-
if options.debug:
|
68
|
-
|
101
|
+
if vd.options.debug:
|
102
|
+
vd.exceptionCaught(e)
|
69
103
|
|
70
104
|
|
71
105
|
vd.status('loaded %d points (%d errors)' % (nplotted, nerrors))
|
@@ -97,6 +131,26 @@ class GraphSheet(InvertedCanvas):
|
|
97
131
|
srccol = self.ycols[0]
|
98
132
|
return srccol.format(srccol.type(amt))
|
99
133
|
|
134
|
+
def formatXLabel(self, amt):
|
135
|
+
if self.xzoomlevel < 1:
|
136
|
+
labels = []
|
137
|
+
for xcol in self.xcols:
|
138
|
+
if vd.isNumeric(xcol):
|
139
|
+
col_amt = float(amt) if xcol.type is int else xcol.type(amt)
|
140
|
+
else:
|
141
|
+
continue
|
142
|
+
labels.append(xcol.format(col_amt))
|
143
|
+
return ','.join(labels)
|
144
|
+
else:
|
145
|
+
return self.formatX(amt)
|
146
|
+
|
147
|
+
def formatYLabel(self, amt):
|
148
|
+
srccol = self.ycols[0]
|
149
|
+
if srccol.type is int and self.yzoomlevel < 1:
|
150
|
+
return srccol.format(float(amt))
|
151
|
+
else:
|
152
|
+
return self.formatY(amt)
|
153
|
+
|
100
154
|
def parseX(self, txt):
|
101
155
|
return self.xcols[0].type(txt)
|
102
156
|
|
@@ -104,26 +158,39 @@ class GraphSheet(InvertedCanvas):
|
|
104
158
|
return self.ycols[0].type(txt)
|
105
159
|
|
106
160
|
def add_y_axis_label(self, frac):
|
107
|
-
txt = self.
|
161
|
+
txt = self.formatYLabel(self.visibleBox.ymin + frac*self.visibleBox.h)
|
162
|
+
w = (dispwidth(txt)+1)*2
|
163
|
+
if self.ylabel_maxw < w:
|
164
|
+
self.ylabel_maxw = w
|
108
165
|
|
109
166
|
# plot y-axis labels on the far left of the canvas, but within the plotview height-wise
|
110
|
-
|
111
|
-
self.plotlabel(0, self.plotviewBox.ymin + (1.0-frac)*self.plotviewBox.h, txt, attr)
|
167
|
+
self.plotlabel(0, self.plotviewBox.ymin + (1.0-frac)*self.plotviewBox.h, txt, 'graph_axis')
|
112
168
|
|
113
169
|
def add_x_axis_label(self, frac):
|
114
|
-
txt = self.
|
170
|
+
txt = self.formatXLabel(self.visibleBox.xmin + frac*self.visibleBox.w)
|
171
|
+
tick = vd.options.disp_graph_tick_x or ''
|
115
172
|
|
116
173
|
# plot x-axis labels below the plotviewBox.ymax, but within the plotview width-wise
|
117
|
-
|
118
|
-
|
119
|
-
if frac
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
174
|
+
x = self.plotviewBox.xmin + frac*self.plotviewBox.w
|
175
|
+
|
176
|
+
if frac < 1.0:
|
177
|
+
txt = tick + txt
|
178
|
+
else:
|
179
|
+
right_margin = self.plotwidth - 1 - self.plotviewBox.xmax
|
180
|
+
if (len(txt)+len(tick))*2 <= right_margin:
|
181
|
+
txt = tick + txt
|
182
|
+
else:
|
183
|
+
# shift rightmost label to be left of its tick
|
184
|
+
x -= len(txt)*2
|
185
|
+
if len(tick) == 0:
|
186
|
+
x += 1
|
187
|
+
txt = txt + tick
|
188
|
+
|
189
|
+
self.plotlabel(x, self.plotviewBox.ymax+4, txt, 'graph_axis')
|
124
190
|
|
125
191
|
def createLabels(self):
|
126
192
|
self.gridlabels = []
|
193
|
+
self.ylabel_maxw = self.leftMarginPixels
|
127
194
|
|
128
195
|
# y-axis
|
129
196
|
self.add_y_axis_label(1.00)
|
@@ -143,18 +210,18 @@ class GraphSheet(InvertedCanvas):
|
|
143
210
|
# TODO: grid lines corresponding to axis labels
|
144
211
|
|
145
212
|
xname = ','.join(xcol.name for xcol in self.xcols if vd.isNumeric(xcol)) or 'row#'
|
146
|
-
xname, _ = clipstr(xname, self.
|
147
|
-
self.plotlabel(0, self.plotviewBox.ymax+4, xname+'»',
|
213
|
+
xname, _ = clipstr(xname, self.left_margin//2-2)
|
214
|
+
self.plotlabel(0, self.plotviewBox.ymax+4, xname+'»', 'graph_axis')
|
148
215
|
|
149
216
|
|
150
217
|
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')
|
151
218
|
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')
|
152
219
|
|
153
220
|
# swap directions of up/down
|
154
|
-
InvertedCanvas.addCommand(None, 'go-up', 'sheet.cursorBox.ymin += cursorBox.h', 'move cursor up by its height')
|
155
|
-
InvertedCanvas.addCommand(None, 'go-down', 'sheet.cursorBox.ymin -= cursorBox.h', 'move cursor down by its height')
|
156
|
-
InvertedCanvas.addCommand(None, 'go-top',
|
157
|
-
InvertedCanvas.addCommand(None, 'go-bottom', 'sheet.cursorBox.ymin =
|
221
|
+
InvertedCanvas.addCommand(None, 'go-up', 'if cursorBox: sheet.cursorBox.ymin += cursorBox.h', 'move cursor up by its height')
|
222
|
+
InvertedCanvas.addCommand(None, 'go-down', 'if cursorBox: sheet.cursorBox.ymin -= cursorBox.h', 'move cursor down by its height')
|
223
|
+
InvertedCanvas.addCommand(None, 'go-top', 'if cursorBox: sheet.cursorBox.ymin = sheet.calcTopCursorY()', 'move cursor to top edge of visible canvas')
|
224
|
+
InvertedCanvas.addCommand(None, 'go-bottom', 'if cursorBox: sheet.cursorBox.ymin = sheet.calcBottomCursorY()', 'move cursor to bottom edge of visible canvas')
|
158
225
|
InvertedCanvas.addCommand(None, 'go-pagedown', 't=(visibleBox.ymax-visibleBox.ymin); sheet.cursorBox.ymin -= t; sheet.visibleBox.ymin -= t; sheet.refresh()', 'move cursor down to next visible page')
|
159
226
|
InvertedCanvas.addCommand(None, 'go-pageup', 't=(visibleBox.ymax-visibleBox.ymin); sheet.cursorBox.ymin += t; sheet.visibleBox.ymin += t; sheet.refresh()', 'move cursor up to previous visible page')
|
160
227
|
|
@@ -185,3 +252,8 @@ vd.addGlobals({
|
|
185
252
|
'GraphSheet': GraphSheet,
|
186
253
|
'InvertedCanvas': InvertedCanvas,
|
187
254
|
})
|
255
|
+
|
256
|
+
vd.addMenuItems('''
|
257
|
+
Plot > Graph > current column > plot-column
|
258
|
+
Plot > Graph > all numeric columns > plot-numerics
|
259
|
+
''')
|
visidata/guide.py
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
'''
|
2
|
+
# A Guide to VisiData Guides
|
3
|
+
Each guide shows you how to use a particular feature in VisiData. Gray guides have not been written yet. We love contributions: [:onclick https://visidata.org/docs/api/guides]https://visidata.org/docs/api/guides[/].
|
4
|
+
|
5
|
+
- [:keystrokes]Up/Down[/] to move the row cursor
|
6
|
+
- [:keystrokes]Enter[/] to view a topic
|
7
|
+
'''
|
8
|
+
import re
|
9
|
+
|
10
|
+
from visidata import vd, BaseSheet, Sheet, ItemColumn, Column, VisiData, ENTER, RowColorizer, AttrDict, MissingAttrFormatter
|
11
|
+
from visidata import wraptext
|
12
|
+
|
13
|
+
guides_list = '''
|
14
|
+
GuideIndex ("A Guide to VisiData Guides (you are here)")
|
15
|
+
HelpGuide ("Where to Start and How to Quit") # manpage; ask for patreon
|
16
|
+
MenuGuide ("The VisiData Menu System")
|
17
|
+
CommandsSheet ("How to find the command you want run")
|
18
|
+
|
19
|
+
# real barebones basics
|
20
|
+
MovementGuide ("Movement and Search")
|
21
|
+
SortGuide ("Sorting")
|
22
|
+
TypesSheet ("The basic type system")
|
23
|
+
CommandLog ("Undo and Replay")
|
24
|
+
|
25
|
+
# rev this thing up
|
26
|
+
|
27
|
+
SelectionGuide ("Selecting and filtering") # stu|, and variants; filtering with dup-sheet; g prefix often refers to selected rowset
|
28
|
+
SheetsSheet ("The Sheet Stack")
|
29
|
+
ColumnsSheet ("Columns: the only way to fly")
|
30
|
+
StatusesSheet ("Revisit old status messages")
|
31
|
+
SidebarSheet ("Dive into the sidebar")
|
32
|
+
SaversGuide ("Saving Data") # talk about options.overwrite + ro here
|
33
|
+
|
34
|
+
ErrorsSheet ("What was that error?")
|
35
|
+
ModifyGuide ("Adding, Editing, Deleting Rows")
|
36
|
+
|
37
|
+
# the varieties of data experience
|
38
|
+
|
39
|
+
SlideGuide ("Sliding rows and columns around")
|
40
|
+
ExprGuide ("Compute Python over every row")
|
41
|
+
JoinGuide ("Joining multiple sheets together")
|
42
|
+
DescribeSheet ("Basic Statistics (min/max/mode/median/mean)")
|
43
|
+
AggregatorsSheet ("Aggregations like sum, mean, and distinct")
|
44
|
+
FrequencyTable ("Frequency Tables are how you GROUP BY")
|
45
|
+
PivotGuide ("Pivot Tables are just Frequency Tables with more columns")
|
46
|
+
MeltGuide ("Melt is just Unpivot")
|
47
|
+
JsonGuide ("Some special features for JSON") # with expand/contract, unfurl
|
48
|
+
RegexGuide ("Matching and Transforming Strings with Regex")
|
49
|
+
GraphSheet ("Basic scatterplots and other graphs")
|
50
|
+
WindowFunctionGuide ("Perform operations on groups of rows")
|
51
|
+
|
52
|
+
# for the frequent user
|
53
|
+
OptionsSheet ("Options and Settings")
|
54
|
+
ClipboardGuide ("Copy and Paste Data via the Clipboard")
|
55
|
+
DirSheet ("Browsing the local filesystem")
|
56
|
+
FormatsSheet ("What can you open with VisiData?")
|
57
|
+
SplitpaneGuide ("Split VisiData into two panes")
|
58
|
+
ThemesSheet ("Change Interface Theme")
|
59
|
+
ColorSheet ("See available colors")
|
60
|
+
MacrosSheet ("Recording macros")
|
61
|
+
MemorySheet ("Making note of certain values")
|
62
|
+
|
63
|
+
# advanced usage and developers
|
64
|
+
|
65
|
+
ThreadsSheet ("Threads past and present")
|
66
|
+
PyobjSheet ("Inspecting internal Python objects")
|
67
|
+
|
68
|
+
# appendices
|
69
|
+
|
70
|
+
InputEditorGuide ("Using the builtin line editor")
|
71
|
+
'''
|
72
|
+
|
73
|
+
vd.guides = {} # name -> guidecls
|
74
|
+
|
75
|
+
@VisiData.api
|
76
|
+
def addGuide(vd, name, guidecls):
|
77
|
+
vd.guides[name] = guidecls
|
78
|
+
|
79
|
+
@VisiData.api
|
80
|
+
class GuideIndex(Sheet):
|
81
|
+
guide = __doc__
|
82
|
+
|
83
|
+
rowtype = 'guides' # rowdef: list(guide number, guide name, topic description, points, max_points)
|
84
|
+
columns = [
|
85
|
+
ItemColumn('n', 0, type=int),
|
86
|
+
ItemColumn('name', 1, width=0),
|
87
|
+
ItemColumn('topic', 2, width=60),
|
88
|
+
]
|
89
|
+
colorizers = [
|
90
|
+
RowColorizer(7, 'color_guide_unwritten', lambda s,c,r,v: r and r[1] not in vd.guides)
|
91
|
+
]
|
92
|
+
def iterload(self):
|
93
|
+
i = 0
|
94
|
+
for line in guides_list.splitlines():
|
95
|
+
m = re.search(r'(\w+?) \("(.*)"\)', line)
|
96
|
+
if m:
|
97
|
+
yield [i] + list(m.groups())
|
98
|
+
i += 1
|
99
|
+
|
100
|
+
def openRow(self, row):
|
101
|
+
name = row[1]
|
102
|
+
return vd.getGuide(name)
|
103
|
+
|
104
|
+
class OptionHelpGetter:
|
105
|
+
'For easy and consistent formatting in sidebars and helpstrings, use {vd.options.help.opt_name}.'
|
106
|
+
def __getattr__(self, optname):
|
107
|
+
opt = vd.options._get(optname, 'default')
|
108
|
+
return f'[:onclick options-sheet {optname}]`{optname}`[/]: to {opt.helpstr} (default: {opt.value})'
|
109
|
+
|
110
|
+
|
111
|
+
class CommandHelpGetter:
|
112
|
+
'For easy and consistent formatting in sidebars and helpstrings, use {vd.commands.help.long_name}.'
|
113
|
+
def __init__(self, cls):
|
114
|
+
self.cls = cls
|
115
|
+
self.helpsheet = vd.HelpSheet()
|
116
|
+
self.helpsheet.ensureLoaded()
|
117
|
+
|
118
|
+
def __getattr__(self, k):
|
119
|
+
longname = k.replace('_', '-')
|
120
|
+
binding = self.helpsheet.revbinds.get(longname, [None])[0]
|
121
|
+
# cmddict has a SheetClass associated with each command
|
122
|
+
# go through all the parents of the Sheet type, to look for the command
|
123
|
+
for cls in self.cls.superclasses():
|
124
|
+
cmd = self.helpsheet.cmddict.get((cls.__name__, longname), None)
|
125
|
+
if cmd:
|
126
|
+
break
|
127
|
+
if not cmd:
|
128
|
+
return ''
|
129
|
+
if 'input' in cmd.execstr.lower():
|
130
|
+
inputtype = 'input'
|
131
|
+
m = re.search(r'type="(\w*)"', cmd.execstr, re.IGNORECASE)
|
132
|
+
if not m:
|
133
|
+
m = re.search(r'input(\w*)\("', cmd.execstr, re.IGNORECASE)
|
134
|
+
if m:
|
135
|
+
inputtype = m.groups()[0].lower()
|
136
|
+
binding += f'[:45] {inputtype}[/]'
|
137
|
+
|
138
|
+
helpstr = cmd.helpstr
|
139
|
+
return f'`{binding}` (`{longname}`) to {helpstr}'
|
140
|
+
|
141
|
+
|
142
|
+
class GuideSheet(Sheet):
|
143
|
+
rowtype = 'lines'
|
144
|
+
filetype = 'guide'
|
145
|
+
columns = [
|
146
|
+
ItemColumn('linenum', 0, type=int, width=0),
|
147
|
+
ItemColumn('guide', 1, width=80, displayer='full'),
|
148
|
+
]
|
149
|
+
precious = False
|
150
|
+
guide_text = ''
|
151
|
+
sheettype = Sheet
|
152
|
+
|
153
|
+
def iterload(self):
|
154
|
+
winWidth = 78
|
155
|
+
helper = AttrDict(commands=CommandHelpGetter(self.sheettype),
|
156
|
+
options=OptionHelpGetter())
|
157
|
+
guidetext = MissingAttrFormatter().format(self.guide_text, help=helper, vd=vd)
|
158
|
+
for startingLine, text in enumerate(guidetext.splitlines()):
|
159
|
+
text = text.strip()
|
160
|
+
if text:
|
161
|
+
for i, (L, _) in enumerate(wraptext(str(text), width=winWidth)):
|
162
|
+
yield [startingLine+i+1, L]
|
163
|
+
else:
|
164
|
+
yield [startingLine+1, text]
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
@VisiData.api
|
169
|
+
def getGuide(vd, name): # -> GuideSheet()
|
170
|
+
if name in vd.guides:
|
171
|
+
return vd.guides[name]()
|
172
|
+
vd.warning(f'no guide named {name}')
|
173
|
+
|
174
|
+
BaseSheet.addCommand('', 'open-guide-index', 'vd.push(GuideIndex("VisiData_Guide"))', 'open VisiData guides table of contents')
|
175
|
+
|
176
|
+
vd.addMenuItems('''
|
177
|
+
Help > VisiData Feature Guides > open-guide-index
|
178
|
+
''')
|
179
|
+
|
180
|
+
vd.addGlobals({'GuideSheet':GuideSheet, "CommandHelpGetter": CommandHelpGetter, "OptionHelpGetter": OptionHelpGetter})
|
visidata/help.py
CHANGED
@@ -1,10 +1,35 @@
|
|
1
1
|
import functools
|
2
2
|
import collections
|
3
|
-
from pkg_resources import resource_filename
|
4
3
|
|
5
|
-
from visidata import
|
4
|
+
from visidata import VisiData, MetaSheet, ColumnAttr, Column, BaseSheet, VisiDataMetaSheet, SuspendCurses
|
5
|
+
from visidata import vd, asyncthread, ENTER, drawcache, AttrDict
|
6
6
|
|
7
|
-
vd.
|
7
|
+
vd.option('disp_help', 2, 'show help panel during input')
|
8
|
+
|
9
|
+
@BaseSheet.api
|
10
|
+
def hint_basichelp(sheet):
|
11
|
+
return 0, '`Alt+[:underline]H[/]` to open the [:underline]H[/]elp menu'
|
12
|
+
|
13
|
+
|
14
|
+
@VisiData.api
|
15
|
+
def iterMenuPaths(vd, item=None, menupath=[]):
|
16
|
+
'Generate (longname, menupath).'
|
17
|
+
if item is None:
|
18
|
+
item = vd.menus
|
19
|
+
|
20
|
+
if isinstance(item, (list, tuple)):
|
21
|
+
for m in item:
|
22
|
+
yield from vd.iterMenuPaths(m, menupath)
|
23
|
+
elif item.longname:
|
24
|
+
yield item.longname, ' > '.join(menupath+[item.title])
|
25
|
+
else:
|
26
|
+
yield from vd.iterMenuPaths(item.menus, menupath+[item.title])
|
27
|
+
|
28
|
+
|
29
|
+
@VisiData.property
|
30
|
+
@drawcache
|
31
|
+
def menuPathsByLongname(vd):
|
32
|
+
return dict(vd.iterMenuPaths())
|
8
33
|
|
9
34
|
|
10
35
|
@VisiData.api
|
@@ -16,10 +41,12 @@ class HelpSheet(MetaSheet):
|
|
16
41
|
|
17
42
|
columns = [
|
18
43
|
ColumnAttr('sheet'),
|
44
|
+
ColumnAttr('module'),
|
19
45
|
ColumnAttr('longname'),
|
46
|
+
Column('menupath', width=0, cache=True, getter=lambda col,row: vd.menuPathsByLongname[row.longname]),
|
20
47
|
Column('keystrokes', getter=lambda col,row: col.sheet.revbinds.get(row.longname, [None])[0]),
|
21
|
-
Column('all_bindings', width=0, getter=lambda col,row: list(set(col.sheet.revbinds.get(row.longname, [])))),
|
22
|
-
Column('description', getter=lambda col,row: col.sheet.cmddict[(row.sheet, row.longname)].helpstr),
|
48
|
+
Column('all_bindings', width=0, cache=True, getter=lambda col,row: list(set(col.sheet.revbinds.get(row.longname, [])))),
|
49
|
+
Column('description', width=40, getter=lambda col,row: col.sheet.cmddict[(row.sheet, row.longname)].helpstr),
|
23
50
|
ColumnAttr('execstr', width=0),
|
24
51
|
Column('logged', width=0, getter=lambda col,row: vd.isLoggableCommand(row.longname)),
|
25
52
|
]
|
@@ -54,73 +81,69 @@ class HelpSheet(MetaSheet):
|
|
54
81
|
return revbinds
|
55
82
|
|
56
83
|
|
57
|
-
@VisiData.api
|
58
|
-
@asyncthread
|
59
|
-
def help_search(vd, sheet, regex):
|
60
|
-
vs = HelpSheet(source=None)
|
61
|
-
vs.rows = [] # do not trigger push reload
|
62
|
-
vd.push(vs) # push first, then reload
|
63
|
-
vd.sync(vs.reload())
|
64
|
-
|
65
|
-
# find rows matching regex on original HelpSheet
|
66
|
-
rowidxs = list(vd.searchRegex(vs, regex=regex, columns="visibleCols"))
|
67
|
-
|
68
|
-
# add only matching rows
|
69
|
-
allrows = vs.rows
|
70
|
-
vs.rows = []
|
71
|
-
for rowidx in rowidxs:
|
72
|
-
vs.addRow(allrows[rowidx])
|
73
|
-
|
74
|
-
|
75
84
|
class HelpPane:
|
76
85
|
def __init__(self, name):
|
86
|
+
import visidata
|
77
87
|
self.name = name
|
78
88
|
self.scr = None
|
79
89
|
self.parentscr = None
|
80
90
|
self.amgr = visidata.AnimationMgr()
|
81
91
|
|
82
|
-
|
92
|
+
@property
|
93
|
+
def width(self):
|
94
|
+
return self.amgr.maxWidth
|
95
|
+
|
96
|
+
@property
|
97
|
+
def height(self):
|
98
|
+
return self.amgr.maxHeight
|
99
|
+
|
100
|
+
def draw(self, scr, x=None, y=None, **kwargs):
|
83
101
|
if not scr: return
|
84
|
-
if
|
85
|
-
if self.scr:
|
86
|
-
self.scr.erase()
|
87
|
-
self.scr.refresh()
|
88
|
-
self.scr = None
|
89
|
-
return
|
102
|
+
# if vd.options.disp_help <= 0:
|
103
|
+
# if self.scr:
|
104
|
+
# self.scr.erase()
|
105
|
+
# self.scr.refresh()
|
106
|
+
# self.scr = None
|
107
|
+
# return
|
90
108
|
if y is None: y=0 # show at top of screen by default
|
91
109
|
if x is None: x=0
|
110
|
+
hneeded = self.amgr.maxHeight+3
|
111
|
+
wneeded = self.amgr.maxWidth+4
|
112
|
+
scrh, scrw = scr.getmaxyx()
|
92
113
|
if not self.scr or scr is not self.parentscr: # (re)allocate help pane scr
|
93
114
|
if y >= 0:
|
94
|
-
if y+
|
115
|
+
if y+hneeded < scrh:
|
95
116
|
yhelp = y+1
|
96
117
|
else:
|
97
|
-
|
118
|
+
hneeded = max(0, min(hneeded, y-1))
|
119
|
+
yhelp = y-hneeded
|
98
120
|
else: # y<0
|
99
|
-
yhelp =
|
121
|
+
yhelp = max(0, scrh-hneeded-1)
|
100
122
|
|
101
123
|
if x >= 0:
|
102
|
-
if x+
|
124
|
+
if x+wneeded < scrw:
|
103
125
|
xhelp = x+1
|
104
126
|
else:
|
105
|
-
|
127
|
+
wneeded = max(0, min(wneeded, x-1))
|
128
|
+
xhelp = x-wneeded
|
106
129
|
else: # x<0
|
107
|
-
xhelp =
|
130
|
+
xhelp = max(0, scrh-wneeded-1)
|
108
131
|
|
109
|
-
self.scr =
|
132
|
+
self.scr = vd.subwindow(scr, xhelp, yhelp, wneeded, hneeded)
|
110
133
|
self.parentscr = scr
|
111
134
|
|
112
135
|
self.scr.erase()
|
113
136
|
self.scr.box()
|
114
|
-
self.amgr.draw(self.scr, y=1, x=2)
|
137
|
+
self.amgr.draw(self.scr, y=1, x=2, **kwargs)
|
115
138
|
self.scr.refresh()
|
116
139
|
|
117
140
|
|
118
141
|
@VisiData.api
|
119
142
|
@functools.lru_cache(maxsize=None)
|
120
|
-
def getHelpPane(vd, name, module='
|
143
|
+
def getHelpPane(vd, name, module='visidata') -> HelpPane:
|
121
144
|
ret = HelpPane(name)
|
122
145
|
try:
|
123
|
-
ret.amgr.load(name,
|
146
|
+
ret.amgr.load(name, (vd.pkg_resources_files(module)/f'ddw/{name}.ddw').open(encoding='utf-8'))
|
124
147
|
ret.amgr.trigger(name, loop=True)
|
125
148
|
except FileNotFoundError as e:
|
126
149
|
vd.debug(str(e))
|
@@ -135,23 +158,31 @@ def getHelpPane(vd, name, module='vdplus'):
|
|
135
158
|
def openManPage(vd):
|
136
159
|
import os
|
137
160
|
with SuspendCurses():
|
138
|
-
|
139
|
-
|
161
|
+
module_path = vd.pkg_resources_files(__name__.split('.')[0])
|
162
|
+
if os.system(' '.join(['man', str(module_path/'man/vd.1')])) != 0:
|
163
|
+
vd.push(TextSheet('man_vd', source=module_path/'man/vd.txt'))
|
140
164
|
|
141
165
|
|
142
166
|
# in VisiData, g^H refers to the man page
|
143
167
|
BaseSheet.addCommand('g^H', 'sysopen-help', 'openManPage()', 'Show the UNIX man page for VisiData')
|
144
168
|
BaseSheet.addCommand('z^H', 'help-commands', 'vd.push(HelpSheet(name + "_commands", source=sheet, revbinds={}))', 'list commands and keybindings available on current sheet')
|
145
169
|
BaseSheet.addCommand('gz^H', 'help-commands-all', 'vd.push(HelpSheet("all_commands", source=None, revbinds={}))', 'list commands and keybindings for all sheet types')
|
146
|
-
BaseSheet.addCommand(None, 'help-search', 'help_search(sheet, input("help: "))', 'search through command longnames with search terms')
|
147
170
|
|
148
171
|
BaseSheet.bindkey('KEY_F(1)', 'sysopen-help')
|
149
172
|
BaseSheet.bindkey('zKEY_F(1)', 'help-commands')
|
150
173
|
BaseSheet.bindkey('zKEY_BACKSPACE', 'help-commands')
|
174
|
+
BaseSheet.bindkey('gKEY_BACKSPACE', 'sysopen-help')
|
151
175
|
|
152
|
-
HelpSheet.addCommand(
|
176
|
+
HelpSheet.addCommand(None, 'exec-command', 'quit(sheet); draw_all(); activeStack[0].execCommand(cursorRow.longname)', 'execute command on undersheet')
|
153
177
|
BaseSheet.addCommand(None, 'open-tutorial-visidata', 'launchBrowser("https://jsvine.github.io/intro-to-visidata/")', 'open https://jsvine.github.io/intro-to-visidata/')
|
154
178
|
|
155
179
|
vd.addMenuItem("Help", "VisiData tutorial", 'open-tutorial-visidata')
|
156
180
|
vd.addMenuItem("Help", 'Sheet commands', 'help-commands')
|
157
181
|
vd.addMenuItem("Help", 'All commands', 'help-commands-all')
|
182
|
+
|
183
|
+
vd.addGlobals(HelpSheet=HelpSheet)
|
184
|
+
|
185
|
+
vd.addMenuItems('''
|
186
|
+
Help > Quick reference > sysopen-help
|
187
|
+
Help > Command list > help-commands
|
188
|
+
''')
|
visidata/hint.py
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
import collections
|
2
|
+
|
3
|
+
from visidata import vd, BaseSheet
|
4
|
+
|
5
|
+
|
6
|
+
@BaseSheet.lazy_property
|
7
|
+
def prevHints(sheet):
|
8
|
+
return collections.defaultdict(int)
|
9
|
+
|
10
|
+
|
11
|
+
@BaseSheet.api
|
12
|
+
def getHint(sheet, *args, **kwargs) -> str:
|
13
|
+
funcs = [getattr(sheet, x) for x in dir(sheet) if x.startswith('hint_')]
|
14
|
+
results = []
|
15
|
+
hints = sheet.prevHints
|
16
|
+
for f in funcs:
|
17
|
+
try:
|
18
|
+
r = f(*args, **kwargs)
|
19
|
+
if r:
|
20
|
+
if isinstance(r, dict):
|
21
|
+
n = r.get('_relevance', 1)
|
22
|
+
v = r
|
23
|
+
elif isinstance(r, tuple):
|
24
|
+
n, v = r
|
25
|
+
else:
|
26
|
+
n = 1
|
27
|
+
v = r
|
28
|
+
|
29
|
+
if v not in sheet.prevHints:
|
30
|
+
results.append((n, v))
|
31
|
+
sheet.prevHints[v] += 1
|
32
|
+
except Exception as e:
|
33
|
+
vd.debug(f'{f.__name__}: {e}')
|
34
|
+
|
35
|
+
if results:
|
36
|
+
return sorted(results, reverse=True)[0][1]
|
37
|
+
|
38
|
+
|
39
|
+
vd.addCommand('', 'help-hint', 'status(getHint() or pressMenu("Help"))', 'get context-dependent hint on what to do next')
|