visidata 2.11.1__py3-none-any.whl → 3.0.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.
- visidata/__init__.py +72 -91
- visidata/_input.py +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +78 -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
- {vgit → visidata/apps/vgit}/blame.py +5 -2
- {vgit → visidata/apps/vgit}/branch.py +31 -16
- {vgit → visidata/apps/vgit}/config.py +3 -3
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- {vgit → visidata/apps/vgit}/grep.py +6 -5
- visidata/apps/vgit/log.py +81 -0
- {vgit → visidata/apps/vgit}/main.py +18 -5
- {vgit → visidata/apps/vgit}/remote.py +8 -4
- visidata/apps/vgit/repos.py +71 -0
- {vgit → visidata/apps/vgit}/setup.py +6 -4
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- {vgit → visidata/apps/vgit}/statusbar.py +2 -0
- visidata/basesheet.py +63 -51
- visidata/canvas.py +208 -93
- visidata/choose.py +6 -6
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +73 -17
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +88 -114
- visidata/color.py +142 -56
- visidata/column.py +121 -129
- 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 +6 -2
- 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 +22 -4
- 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 +197 -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} +3 -1
- 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} +32 -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} +77 -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 +200 -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 +20 -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 +2 -4
- 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 +54 -12
- 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/macros.py +129 -42
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -155
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -149
- visidata/man/vd.txt +291 -154
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +78 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +113 -32
- visidata/pivot.py +73 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +55 -205
- visidata/rename_col.py +20 -0
- visidata/save.py +37 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -25
- visidata/sheets.py +239 -260
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +114 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/benchmark.csv +52 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +65 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +28 -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 +89 -40
- 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 +17 -5
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
- visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
- visidata-3.0.1.dist-info/RECORD +258 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
- vgit/__init__.py +0 -1
- vgit/gitsheet.py +0 -164
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.1.data/scripts/vgit +0 -9
- visidata-2.11.1.dist-info/RECORD +0 -155
- {vgit → visidata/apps/vgit}/__main__.py +0 -0
- {vgit → visidata/apps/vgit}/abort.py +0 -0
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
visidata/pyobj.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
from functools import singledispatch
|
2
1
|
from typing import Mapping
|
3
2
|
import inspect
|
4
3
|
import math
|
5
4
|
|
6
|
-
from visidata import
|
5
|
+
from visidata import vd, asyncthread, ENTER, deduceType
|
6
|
+
from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet
|
7
|
+
import visidata
|
7
8
|
|
8
9
|
vd.option('visibility', 0, 'visibility level')
|
9
10
|
vd.option('default_sample_size', 100, 'number of rows to sample for regex.split (0=all)', replay=True)
|
@@ -16,177 +17,6 @@ class PythonSheet(Sheet):
|
|
16
17
|
return PyobjSheet("%s[%s]" % (self.name, self.keystr(row)), source=row)
|
17
18
|
|
18
19
|
|
19
|
-
class InferColumnsSheet(Sheet):
|
20
|
-
_rowtype = dict
|
21
|
-
@asyncthread
|
22
|
-
def reload(self):
|
23
|
-
self.reloadCols()
|
24
|
-
|
25
|
-
self.rows = []
|
26
|
-
for r in self.iterload():
|
27
|
-
self.addRow(r)
|
28
|
-
|
29
|
-
# if an ordering has been specified, sort the sheet
|
30
|
-
if self._ordering:
|
31
|
-
vd.sync(self.sort())
|
32
|
-
|
33
|
-
def reloadCols(self):
|
34
|
-
self.columns = []
|
35
|
-
self._knownKeys.clear()
|
36
|
-
for c in type(self).columns:
|
37
|
-
self.addColumn(deepcopy(c))
|
38
|
-
|
39
|
-
def addColumn(self, *cols, index=None):
|
40
|
-
for c in cols:
|
41
|
-
self._knownKeys.add(c.expr or c.name)
|
42
|
-
return super().addColumn(*cols, index=index)
|
43
|
-
|
44
|
-
def addRow(self, row, index=None):
|
45
|
-
ret = super().addRow(row, index=index)
|
46
|
-
for k in row:
|
47
|
-
if k not in self._knownKeys:
|
48
|
-
self.addColumn(ColumnItem(k, type=deduceType(row[k])))
|
49
|
-
|
50
|
-
return ret
|
51
|
-
|
52
|
-
|
53
|
-
InferColumnsSheet.init('_knownKeys', set, copy=True) # set of row keys already seen
|
54
|
-
InferColumnsSheet.init('_ordering', list, copy=True)
|
55
|
-
|
56
|
-
|
57
|
-
@Sheet.api
|
58
|
-
def getSampleRows(sheet):
|
59
|
-
'Return list of sample rows centered around the cursor.'
|
60
|
-
n = sheet.options.default_sample_size
|
61
|
-
if n == 0 or n >= sheet.nRows:
|
62
|
-
return sheet.rows
|
63
|
-
|
64
|
-
vd.warning(f'sampling {n} rows')
|
65
|
-
seq = sheet.rows
|
66
|
-
start = math.ceil(sheet.cursorRowIndex - n / 2) % len(seq)
|
67
|
-
end = (start + n) % len(seq)
|
68
|
-
if start < end:
|
69
|
-
return seq[start:end]
|
70
|
-
return seq[start:] + seq[:end]
|
71
|
-
|
72
|
-
|
73
|
-
@asyncthread
|
74
|
-
def expand_cols_deep(sheet, cols, rows=None, depth=0): # depth == 0 means drill all the way
|
75
|
-
'expand all visible columns of containers to the given depth (0=fully)'
|
76
|
-
ret = []
|
77
|
-
if not rows:
|
78
|
-
rows = sheet.getSampleRows()
|
79
|
-
|
80
|
-
for col in cols:
|
81
|
-
newcols = col.expand(rows)
|
82
|
-
if depth != 1: # countdown not yet complete, or negative (indefinite)
|
83
|
-
ret.extend(expand_cols_deep.__wrapped__(sheet, newcols, rows, depth-1))
|
84
|
-
return ret
|
85
|
-
|
86
|
-
@singledispatch
|
87
|
-
def _createExpandedColumns(sampleValue, col, rows):
|
88
|
-
'''By default, a column is not expandable. Supported container types for
|
89
|
-
sampleValue trigger alternate, type-specific expansions.'''
|
90
|
-
return []
|
91
|
-
|
92
|
-
@_createExpandedColumns.register(dict)
|
93
|
-
def _(sampleValue, col, vals):
|
94
|
-
'''Build a set of columns to add, using the first occurrence of each key to
|
95
|
-
determine column type'''
|
96
|
-
newcols = {}
|
97
|
-
|
98
|
-
for val in Progress(vals, 'expanding'):
|
99
|
-
colsToAdd = set(val).difference(newcols)
|
100
|
-
colsToAdd and newcols.update({
|
101
|
-
k: deduceType(v)
|
102
|
-
for k, v in val.items()
|
103
|
-
if k in colsToAdd
|
104
|
-
})
|
105
|
-
|
106
|
-
return [
|
107
|
-
ExpandedColumn(col.sheet.options.fmt_expand_dict % (col.name, k), type=v, origCol=col, expr=k)
|
108
|
-
for k, v in newcols.items()
|
109
|
-
]
|
110
|
-
|
111
|
-
def _createExpandedColumnsNamedTuple(col, val):
|
112
|
-
return [
|
113
|
-
ExpandedColumn(col.sheet.options.fmt_expand_dict % (col.name, k), type=colType, origCol=col, expr=i)
|
114
|
-
for i, (k, colType) in enumerate(zip(val._fields, (deduceType(v) for v in val)))
|
115
|
-
]
|
116
|
-
|
117
|
-
@_createExpandedColumns.register(list)
|
118
|
-
@_createExpandedColumns.register(tuple)
|
119
|
-
def _(sampleValue, col, vals):
|
120
|
-
'''Use the longest sequence to determine the number of columns we need to
|
121
|
-
create, and their presumed types'''
|
122
|
-
def lenNoExceptions(v):
|
123
|
-
try:
|
124
|
-
return len(v)
|
125
|
-
except Exception as e:
|
126
|
-
return 0
|
127
|
-
|
128
|
-
if hasattr(sampleValue, '_fields'): # looks like a namedtuple
|
129
|
-
return _createExpandedColumnsNamedTuple(col, vals[0])
|
130
|
-
|
131
|
-
longestSeq = max(vals, key=lenNoExceptions)
|
132
|
-
colTypes = [deduceType(v) for v in longestSeq]
|
133
|
-
return [
|
134
|
-
ExpandedColumn(col.sheet.options.fmt_expand_list % (col.name, k), type=colType, origCol=col, expr=k)
|
135
|
-
for k, colType in enumerate(colTypes)
|
136
|
-
]
|
137
|
-
|
138
|
-
|
139
|
-
@Column.api
|
140
|
-
def expand(col, rows):
|
141
|
-
isNull = col.sheet.isNullFunc()
|
142
|
-
nonNulls = [
|
143
|
-
col.getTypedValue(row)
|
144
|
-
for row in rows
|
145
|
-
if not isNull(col.getValue(row))
|
146
|
-
]
|
147
|
-
|
148
|
-
if not nonNulls:
|
149
|
-
return []
|
150
|
-
|
151
|
-
# The type of the first non-null value for col determines if and how the
|
152
|
-
# column can be expanded.
|
153
|
-
expandedCols = _createExpandedColumns(nonNulls[0], col, nonNulls)
|
154
|
-
|
155
|
-
idx = col.sheet.columns.index(col)
|
156
|
-
|
157
|
-
for i, c in enumerate(expandedCols):
|
158
|
-
col.sheet.addColumn(c, index=idx+i+1)
|
159
|
-
if expandedCols:
|
160
|
-
col.hide()
|
161
|
-
return expandedCols
|
162
|
-
|
163
|
-
|
164
|
-
def deduceType(v):
|
165
|
-
if isinstance(v, (float, int)):
|
166
|
-
return type(v)
|
167
|
-
else:
|
168
|
-
return anytype
|
169
|
-
|
170
|
-
|
171
|
-
class ExpandedColumn(Column):
|
172
|
-
def calcValue(self, row):
|
173
|
-
return getitemdef(self.origCol.getValue(row), self.expr)
|
174
|
-
|
175
|
-
def setValue(self, row, value):
|
176
|
-
self.origCol.getValue(row)[self.expr] = value
|
177
|
-
|
178
|
-
|
179
|
-
def closeColumn(sheet, col):
|
180
|
-
if hasattr(col, 'origCol'):
|
181
|
-
origCol = col.origCol
|
182
|
-
else:
|
183
|
-
vd.fail('column has not been expanded')
|
184
|
-
vd.addUndo(setattr, sheet, 'columns', sheet.columns)
|
185
|
-
origCol.width = options.default_width
|
186
|
-
cols = [c for c in sheet.columns if getattr(c, "origCol", None) is not origCol]
|
187
|
-
sheet.columns = cols
|
188
|
-
|
189
|
-
|
190
20
|
#### generic list/dict/object browsing
|
191
21
|
@VisiData.global_api
|
192
22
|
def view(vd, obj):
|
@@ -212,10 +42,10 @@ def SheetList(*names, **kwargs):
|
|
212
42
|
|
213
43
|
src = kwargs.get('source', None)
|
214
44
|
if not src:
|
215
|
-
vd.
|
216
|
-
return
|
45
|
+
vd.warning('no content in %s' % names)
|
46
|
+
return Sheet(*names, **kwargs)
|
217
47
|
|
218
|
-
if isinstance(src[0],
|
48
|
+
if isinstance(src[0], Mapping):
|
219
49
|
return ListOfDictSheet(*names, **kwargs)
|
220
50
|
elif isinstance(src[0], tuple):
|
221
51
|
if getattr(src[0], '_fields', None): # looks like a namedtuple
|
@@ -226,7 +56,7 @@ def SheetList(*names, **kwargs):
|
|
226
56
|
|
227
57
|
class ListOfPyobjSheet(PythonSheet):
|
228
58
|
rowtype = 'python objects'
|
229
|
-
def
|
59
|
+
def loader(self):
|
230
60
|
self.rows = self.source
|
231
61
|
self.columns = []
|
232
62
|
self.addColumn(Column(self.name,
|
@@ -275,7 +105,7 @@ class SheetNamedTuple(PythonSheet):
|
|
275
105
|
self.rows = list(zip(self.source._fields, self.source))
|
276
106
|
|
277
107
|
def openRow(self, row):
|
278
|
-
return PyobjSheet(self.name
|
108
|
+
return PyobjSheet(f'{self.name}.{row[0]}', source=row[1])
|
279
109
|
|
280
110
|
|
281
111
|
# source is dict
|
@@ -291,7 +121,7 @@ class SheetDict(PythonSheet):
|
|
291
121
|
self.rows = list(self.source.keys())
|
292
122
|
|
293
123
|
def openRow(self, row):
|
294
|
-
return PyobjSheet(self.name
|
124
|
+
return PyobjSheet(f'{self.name}.{row}', source=self.source[row])
|
295
125
|
|
296
126
|
|
297
127
|
class ColumnSourceAttr(Column):
|
@@ -347,10 +177,11 @@ class PyobjSheet(PythonSheet):
|
|
347
177
|
if vislevel <= 2 and r.startswith('__'): continue
|
348
178
|
if vislevel <= 1 and r.startswith('_'): continue
|
349
179
|
if vislevel <= 0 and callable(getattr(self.source, r)): continue
|
350
|
-
self.addRow(r)
|
351
180
|
except Exception:
|
352
181
|
pass
|
353
182
|
|
183
|
+
self.addRow(r)
|
184
|
+
|
354
185
|
def openRow(self, row):
|
355
186
|
'dive further into Python object'
|
356
187
|
v = getattr(self.source, row)
|
@@ -358,9 +189,13 @@ class PyobjSheet(PythonSheet):
|
|
358
189
|
|
359
190
|
|
360
191
|
@TableSheet.api
|
361
|
-
def openRow(sheet, row):
|
192
|
+
def openRow(sheet, row, rowidx=None):
|
362
193
|
'Return Sheet diving into *row*.'
|
363
|
-
|
194
|
+
if rowidx is None:
|
195
|
+
k = sheet.keystr(row) or str(sheet.cursorRowIndex)
|
196
|
+
else:
|
197
|
+
k = rowidx
|
198
|
+
|
364
199
|
name = f'{sheet.name}[{k}]'
|
365
200
|
return TableSheet(name,
|
366
201
|
rows=sheet.visibleCols,
|
@@ -372,40 +207,48 @@ def openRow(sheet, row):
|
|
372
207
|
nKeys=1)
|
373
208
|
|
374
209
|
@TableSheet.api
|
375
|
-
def openCell(sheet, col, row):
|
210
|
+
def openCell(sheet, col, row, rowidx=None):
|
376
211
|
'Return Sheet diving into cell at *row* in *col*.'
|
377
|
-
|
212
|
+
if rowidx is None:
|
213
|
+
k = sheet.keystr(row) or str(sheet.cursorRowIndex)
|
214
|
+
else:
|
215
|
+
k = rowidx
|
378
216
|
name = f'{sheet.name}[{k}].{col.name}'
|
379
217
|
return PyobjSheet(name, source=col.getTypedValue(row))
|
380
218
|
|
219
|
+
@TableSheet.api
|
220
|
+
def openRowPyobj(sheet, rowidx):
|
221
|
+
'Return Sheet of raw Python object of row.'
|
222
|
+
return PyobjSheet("%s[%s]" % (sheet.name, rowidx), source=sheet.rows[rowidx])
|
223
|
+
|
224
|
+
@TableSheet.api
|
225
|
+
def openCellPyobj(sheet, col, rowidx):
|
226
|
+
'Return Sheet of raw Python object of cell.'
|
227
|
+
name = f'{sheet.name}[{rowidx}].{col.name}'
|
228
|
+
return PyobjSheet(name, source=col.getValue(sheet.rows[rowidx]))
|
229
|
+
|
381
230
|
|
382
231
|
@BaseSheet.api
|
383
|
-
def
|
232
|
+
def inputPythonExpr(sheet):
|
384
233
|
def launch_repl(v, i):
|
385
234
|
import code
|
386
235
|
with SuspendCurses():
|
387
236
|
code.InteractiveConsole(locals=locals()).interact()
|
388
237
|
return v, i
|
389
|
-
|
390
|
-
vd.push(PyobjSheet(expr, source=sheet.evalExpr(expr)))
|
238
|
+
return vd.input("eval: ", "expr", completer=visidata.CompleteExpr(), bindings={'^X': launch_repl})
|
391
239
|
|
392
|
-
BaseSheet.addCommand('^X', 'pyobj-expr', '
|
240
|
+
BaseSheet.addCommand('^X', 'pyobj-expr', 'expr=inputPythonExpr(); vd.push(PyobjSheet(expr, source=sheet.evalExpr(expr)))', 'evaluate Python expression and open result as Python object')
|
393
241
|
BaseSheet.addCommand('', 'exec-python', 'expr = input("exec: ", "expr", completer=CompleteExpr()); exec(expr, getGlobals(), LazyChainMap(sheet, *vd.contexts, locals=vd.getGlobals()))', 'execute Python statement with expression scope')
|
394
242
|
BaseSheet.addCommand('g^X', 'import-python', 'modname=input("import: ", type="import_python"); exec("import "+modname, getGlobals())', 'import Python module in the global scope')
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
Sheet.addCommand('z^Y', 'pyobj-cell', 'status(type(cursorValue)); vd.push(PyobjSheet("%s[%s].%s" % (sheet.name, cursorRowIndex, cursorCol.name), source=cursorValue))', 'open current cell as Python object')
|
399
|
-
globalCommand('g^Y', 'pyobj-sheet', 'status(type(sheet)); vd.push(PyobjSheet(sheet.name+"_sheet", source=sheet))', 'open current sheet as Python object')
|
243
|
+
BaseSheet.addCommand('z^X', 'pyobj-expr-row', 'expr = input("eval over current row: ", "expr", completer=CompleteExpr()); vd.push(PyobjSheet(expr, source=evalExpr(expr, row=cursorRow)))', 'evaluate Python expression, in context of current row, and open result as Python object')
|
244
|
+
BaseSheet.addCommand('', 'assert-expr', 'expr=inputPythonExpr(); assert sheet.evalExpr(expr), f"{expr} not true"', 'eval Python expression and assert result is truthy')
|
245
|
+
BaseSheet.addCommand('', 'assert-expr-row', 'expr=inputPythonExpr(); assert sheet.evalExpr(expr, row=cursorRow), f"{expr} not true"', 'eval Python expression in context of current row, and assert result is truthy')
|
400
246
|
|
401
|
-
Sheet.addCommand('
|
402
|
-
Sheet.addCommand('
|
403
|
-
|
404
|
-
Sheet.addCommand('gz(', 'expand-cols-depth', 'expand_cols_deep(sheet, visibleCols, depth=int(input("expand depth=", value=1)))', 'expand all visible columns of containers to given depth (0=fully)')
|
247
|
+
Sheet.addCommand('^Y', 'pyobj-row', 'status(type(cursorRow).__name__); vd.push(openRowPyobj(cursorRowIndex))', 'open current row as Python object')
|
248
|
+
Sheet.addCommand('z^Y', 'pyobj-cell', 'status(type(cursorValue).__name__); vd.push(openCellPyobj(cursorCol, cursorRowIndex))', 'open current cell as Python object')
|
249
|
+
BaseSheet.addCommand('g^Y', 'pyobj-sheet', 'status(type(sheet).__name__); vd.push(PyobjSheet(sheet.name+"_sheet", source=sheet))', 'open current sheet as Python object')
|
405
250
|
|
406
|
-
Sheet.addCommand('
|
407
|
-
|
408
|
-
Sheet.addCommand('', 'open-row-basic', 'vd.push(TableSheet.openRow(sheet, cursorRow))', 'open sheet with open sheet with copies of rows referenced in current row')
|
251
|
+
Sheet.addCommand('', 'open-row-basic', 'vd.push(TableSheet.openRow(sheet, cursorRow))', 'dive into current row as basic table (ignoring subsheet dive)')
|
409
252
|
Sheet.addCommand(ENTER, 'open-row', 'vd.push(openRow(cursorRow))', 'open current row with sheet-specific dive')
|
410
253
|
Sheet.addCommand('z'+ENTER, 'open-cell', 'vd.push(openCell(cursorCol, cursorRow))', 'open sheet with copies of rows referenced in current cell')
|
411
254
|
Sheet.addCommand('g'+ENTER, 'dive-selected', 'for r in selectedRows: vd.push(openRow(r))', 'open sheet with copies of rows referenced in selected rows')
|
@@ -416,14 +259,21 @@ PyobjSheet.addCommand('gv', 'show-hidden', 'sheet.options.visibility = 2; reload
|
|
416
259
|
PyobjSheet.addCommand('zv', 'hide-hidden', 'sheet.options.visibility -= 1; reload()', 'hide methods and hidden properties')
|
417
260
|
|
418
261
|
vd.addGlobals({
|
419
|
-
'ExpandedColumn': ExpandedColumn,
|
420
262
|
'PythonSheet': PythonSheet,
|
421
|
-
'expand_cols_deep': expand_cols_deep,
|
422
|
-
'deduceType': deduceType,
|
423
|
-
'closeColumn': closeColumn,
|
424
263
|
'ListOfDictSheet': ListOfDictSheet,
|
425
264
|
'SheetDict': SheetDict,
|
426
|
-
'InferColumnsSheet': InferColumnsSheet,
|
427
265
|
'PyobjSheet': PyobjSheet,
|
428
266
|
'view': view,
|
429
267
|
})
|
268
|
+
|
269
|
+
vd.addMenuItems('''
|
270
|
+
View > Visibility > Methods and dunder attributes > show > show-hidden
|
271
|
+
View > Visibility > Methods and dunder attributes > hide > hide-hidden
|
272
|
+
Row > Dive into > open-row
|
273
|
+
System > Python > import library > import-python
|
274
|
+
System > Python > current sheet > pyobj-sheet
|
275
|
+
System > Python > current row > pyobj-row
|
276
|
+
System > Python > current cell > pyobj-cell
|
277
|
+
System > Python > expression > pyobj-expr
|
278
|
+
System > Python > exec() > exec-python
|
279
|
+
''')
|
visidata/rename_col.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
from visidata import vd, Sheet
|
2
|
+
|
3
|
+
@Sheet.api
|
4
|
+
def hint_rename_col(sheet):
|
5
|
+
if vd.cleanName(sheet.cursorCol.name) != sheet.cursorCol.name:
|
6
|
+
return 5, f"[:hint]The current column can't be used in an expression because [:code]{sheet.cursorCol.name}[/] is not a valid Python identifier. [:onclick rename-col]Rename the column[/] with `^`.[/]"
|
7
|
+
|
8
|
+
|
9
|
+
Sheet.addCommand('^', 'rename-col', 'vd.addUndoColNames([cursorCol]); cursorCol.name = editCell(cursorVisibleColIndex, -1, value=cleanName(cursorCol.name))', 'rename current column')
|
10
|
+
Sheet.addCommand('z^', 'rename-col-selected', 'updateColNames(selectedRows or [cursorRow], [sheet.cursorCol], overwrite=True)', 'rename current column to combined contents of current cell in selected rows (or current row)')
|
11
|
+
Sheet.addCommand('g^', 'rename-cols-row', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols)', 'rename all unnamed visible columns to contents of selected rows (or current row)')
|
12
|
+
Sheet.addCommand('gz^', 'rename-cols-selected', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols, overwrite=True)', 'rename all visible columns to combined contents of selected rows (or current row)')
|
13
|
+
|
14
|
+
|
15
|
+
vd.addMenuItems('''
|
16
|
+
Column > Rename > current column > rename-col
|
17
|
+
Column > Rename > from selected cells > current column > rename-col-selected
|
18
|
+
Column > Rename > from selected cells > unnamed columns > rename-cols-row
|
19
|
+
Column > Rename > from selected cells > all columns > rename-cols-selected
|
20
|
+
''')
|
visidata/save.py
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
import collections
|
2
|
+
import os
|
2
3
|
from copy import copy
|
3
4
|
|
4
|
-
from visidata import
|
5
|
+
from visidata import vd
|
6
|
+
from visidata import Sheet, BaseSheet, VisiData, IndexSheet, Path, Progress, TypedExceptionWrapper
|
5
7
|
|
6
|
-
|
7
|
-
vd.option('confirm_overwrite', True, 'whether to prompt for overwrite confirmation on save')
|
8
8
|
vd.option('safe_error', '#ERR', 'error string to use while saving', replay=True)
|
9
|
-
vd.option('
|
9
|
+
vd.option('save_encoding', 'utf-8', 'encoding passed to codecs.open when saving a file', replay=True, help=vd.help_encoding)
|
10
10
|
|
11
11
|
@Sheet.api
|
12
12
|
def safe_trdict(vs):
|
13
13
|
'returns string.translate dictionary for replacing tabs and newlines'
|
14
|
-
if options.safety_first:
|
14
|
+
if vs.options.safety_first:
|
15
15
|
delim = vs.options.delimiter
|
16
16
|
return {
|
17
17
|
0: '', # strip NUL completely
|
@@ -38,7 +38,7 @@ def iterdispvals(sheet, *cols, format=False):
|
|
38
38
|
if trdict:
|
39
39
|
transformers[col].append(lambda v,trdict=trdict: v.translate(trdict))
|
40
40
|
|
41
|
-
options_safe_error = options.safe_error
|
41
|
+
options_safe_error = sheet.options.safe_error
|
42
42
|
for r in Progress(sheet.rows):
|
43
43
|
dispvals = collections.OrderedDict() # [col] -> value
|
44
44
|
for col, transforms in transformers.items():
|
@@ -85,7 +85,7 @@ def getDefaultSaveName(sheet):
|
|
85
85
|
return str(src.with_suffix('')) + '.' + sheet.options.save_filetype
|
86
86
|
return str(src)
|
87
87
|
else:
|
88
|
-
return sheet.name+'.'+getattr(sheet, 'filetype', options.save_filetype)
|
88
|
+
return sheet.name+'.'+getattr(sheet, 'filetype', sheet.options.save_filetype)
|
89
89
|
|
90
90
|
|
91
91
|
@VisiData.api
|
@@ -99,28 +99,32 @@ def save_cols(vd, cols):
|
|
99
99
|
else:
|
100
100
|
savedcoltxt = '%s columns' % len(cols)
|
101
101
|
path = vd.inputPath('save %s to: ' % savedcoltxt, value=vs.getDefaultSaveName())
|
102
|
-
vd.saveSheets(path, vs
|
102
|
+
vd.saveSheets(path, vs)
|
103
103
|
|
104
104
|
|
105
105
|
@VisiData.api
|
106
|
-
def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=
|
106
|
+
def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
|
107
107
|
'Save all *vsheets* to *givenpath*.'
|
108
108
|
|
109
109
|
if not vsheets: # blank tuple
|
110
110
|
vd.warning('no sheets to save')
|
111
111
|
return
|
112
112
|
|
113
|
-
|
113
|
+
filetypes = [givenpath.ext, vd.options.save_filetype]
|
114
114
|
|
115
115
|
vd.clearCaches()
|
116
116
|
|
117
|
-
|
117
|
+
for ft in filetypes:
|
118
|
+
savefunc = getattr(vsheets[0], 'save_' + ft, None) or getattr(vd, 'save_' + ft, None)
|
119
|
+
if savefunc:
|
120
|
+
filetype = ft
|
121
|
+
break
|
118
122
|
|
119
123
|
if savefunc is None:
|
120
124
|
vd.fail(f'no function to save as {filetype}')
|
121
125
|
|
122
|
-
if
|
123
|
-
vd.
|
126
|
+
if confirm_overwrite:
|
127
|
+
vd.confirmOverwrite(givenpath)
|
124
128
|
|
125
129
|
vd.status('saving %s sheets to %s as %s' % (len(vsheets), givenpath.given, filetype))
|
126
130
|
|
@@ -149,6 +153,9 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
|
|
149
153
|
p = Path((givenpath / vs.name).with_suffix('.'+filetype))
|
150
154
|
savefunc(p, vs)
|
151
155
|
vs.hasBeenModified = False
|
156
|
+
|
157
|
+
vd.status(f'{givenpath} save finished') #2157
|
158
|
+
|
152
159
|
return vd.execAsync(_savefiles, vsheets, givenpath, savefunc, filetype)
|
153
160
|
|
154
161
|
|
@@ -166,19 +173,20 @@ def save_zip(vd, p, *vsheets):
|
|
166
173
|
savefunc = getattr(vs, 'save_' + filetype, None) or getattr(vd, 'save_' + filetype, None)
|
167
174
|
savefunc(tmpp, vs)
|
168
175
|
zfp.write(tmpp, f'{vs.name}.{vs.options.save_filetype}')
|
169
|
-
vd.status('%s save finished' % p)
|
170
176
|
|
171
177
|
|
172
178
|
@VisiData.api
|
173
179
|
def save_txt(vd, p, *vsheets):
|
174
|
-
|
180
|
+
if len(vsheets) == 1 and vsheets[0].nVisibleCols > 1: #2173
|
181
|
+
return vd.save_tsv(p, vsheets[0])
|
182
|
+
|
183
|
+
with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
|
175
184
|
for vs in vsheets:
|
176
185
|
unitsep = vs.options.delimiter
|
177
186
|
rowsep = vs.options.row_delimiter
|
178
187
|
for dispvals in vs.iterdispvals(*vs.visibleCols, format=True):
|
179
188
|
fp.write(unitsep.join(dispvals.values()))
|
180
189
|
fp.write(rowsep)
|
181
|
-
vd.status('%s save finished' % p)
|
182
190
|
|
183
191
|
|
184
192
|
@BaseSheet.api
|
@@ -189,9 +197,18 @@ def rootSheet(sheet):
|
|
189
197
|
|
190
198
|
return r
|
191
199
|
|
192
|
-
|
193
|
-
BaseSheet.addCommand('', 'save-
|
194
|
-
BaseSheet.addCommand('
|
195
|
-
|
200
|
+
|
201
|
+
BaseSheet.addCommand('^S', 'save-sheet', 'vd.saveSheets(inputPath("save to: ", value=getDefaultSaveName()), sheet)', 'save current sheet to filename in format determined by extension (default .tsv)')
|
202
|
+
BaseSheet.addCommand('', 'save-sheet-really', 'vd.saveSheets(Path(getDefaultSaveName()), sheet, confirm_overwrite=False)', 'save current sheet without asking for filename or confirmation')
|
203
|
+
BaseSheet.addCommand('', 'save-source', 'vd.saveSheets(rootSheet().source, rootSheet())', 'save root sheet to its source')
|
204
|
+
BaseSheet.addCommand('g^S', 'save-all', 'vd.saveSheets(inputPath("save all sheets to: "), *vd.stackedSheets)', 'save all sheets to given file or directory)')
|
205
|
+
IndexSheet.addCommand('g^S', 'save-selected', 'vd.saveSheets(inputPath("save %d sheets to: " % nSelectedRows, value="_".join(getattr(vs, "name", None) or "blank" for vs in selectedRows)), *selectedRows)', 'save all selected sheets to given file or directory')
|
196
206
|
Sheet.addCommand('', 'save-col', 'save_cols([cursorCol])', 'save current column only to filename in format determined by extension (default .tsv)')
|
197
207
|
Sheet.addCommand('', 'save-col-keys', 'save_cols(keyCols + [cursorCol])', 'save key columns and current column to filename in format determined by extension (default .tsv)')
|
208
|
+
|
209
|
+
vd.addMenuItems('''
|
210
|
+
File > Save > current sheet > save-sheet
|
211
|
+
File > Save > all sheets > save-all
|
212
|
+
File > Save > current column > save-col
|
213
|
+
File > Save > keys and current column > save-col-keys
|
214
|
+
''')
|
visidata/search.py
CHANGED
@@ -3,6 +3,15 @@ from visidata import vd, VisiData, BaseSheet, Sheet, Column, Progress, asyncthre
|
|
3
3
|
|
4
4
|
VisiData.init('searchContext', dict) # [(regex, columns, backward)] -> kwargs from previous search
|
5
5
|
|
6
|
+
vd.help_regex_flags = '''# Regex Flags Help
|
7
|
+
- `A` (ASCII) ASCII-only matching (not unicode)
|
8
|
+
- `I` (IGNORECASE): case-insensitive matching
|
9
|
+
- `M` (MULTILINE): `^` and `$` match after/before newlines
|
10
|
+
- `S` (DOTALL): `.` match any character at all, including newline
|
11
|
+
- `X` (VERBOSE): allow verbose regex
|
12
|
+
'''
|
13
|
+
|
14
|
+
|
6
15
|
@VisiData.api
|
7
16
|
@asyncthread
|
8
17
|
def moveRegex(vd, sheet, *args, **kwargs):
|
@@ -11,7 +20,7 @@ def moveRegex(vd, sheet, *args, **kwargs):
|
|
11
20
|
|
12
21
|
# kwargs: regex=None, columns=None, backward=False
|
13
22
|
@VisiData.api
|
14
|
-
def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
|
23
|
+
def searchRegex(vd, sheet, moveCursor=False, reverse=False, regex_flags=None, **kwargs):
|
15
24
|
'Set row index if moveCursor, otherwise return list of row indexes.'
|
16
25
|
def findMatchingColumn(sheet, row, columns, func):
|
17
26
|
'Find column for which func matches the displayed value in this row'
|
@@ -23,7 +32,15 @@ def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
|
|
23
32
|
|
24
33
|
regex = kwargs.get("regex")
|
25
34
|
if regex:
|
26
|
-
|
35
|
+
if regex_flags is None:
|
36
|
+
regex_flags = sheet.options.regex_flags # regex_flags defined in features.regex
|
37
|
+
flagbits = sum(getattr(re, f.upper()) for f in regex_flags)
|
38
|
+
try:
|
39
|
+
compiled_re = re.compile(regex, flagbits)
|
40
|
+
vd.searchContext["regex"] = compiled_re
|
41
|
+
except re.error as e:
|
42
|
+
vd.searchContext["regex"] = None # make future calls to search-next fail
|
43
|
+
vd.error('invalid regex: %s' % e.msg)
|
27
44
|
|
28
45
|
regex = vd.searchContext.get("regex") or vd.fail("no regex")
|
29
46
|
|
@@ -54,8 +71,23 @@ def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
|
|
54
71
|
matchingRowIndexes += 1
|
55
72
|
yield rowidx
|
56
73
|
|
57
|
-
|
74
|
+
if kwargs.get('printStatus', True):
|
75
|
+
vd.status('%s matches for /%s/' % (matchingRowIndexes, regex.pattern))
|
76
|
+
|
77
|
+
|
78
|
+
@Sheet.api
|
79
|
+
def searchInputRegex(sheet, action:str, columns:str='cursorCol'):
|
80
|
+
r = vd.inputMultiple(regex=dict(prompt=f"{action} regex: ", type="regex", defaultLast=True, help=vd.help_regex),
|
81
|
+
flags=dict(prompt="regex flags: ", type="regex_flags", value=sheet.options.regex_flags, help=vd.help_regex_flags))
|
58
82
|
|
83
|
+
return vd.searchRegex(sheet, regex=r['regex'], regex_flags=r['flags'], columns=columns)
|
84
|
+
|
85
|
+
@Sheet.api
|
86
|
+
def moveInputRegex(sheet, action:str, type="regex", **kwargs):
|
87
|
+
r = vd.inputMultiple(regex=dict(prompt=f"{action} regex: ", type=type, defaultLast=True, help=vd.help_regex),
|
88
|
+
flags=dict(prompt="regex flags: ", type="regex_flags", value=sheet.options.regex_flags, help=vd.help_regex_flags))
|
89
|
+
|
90
|
+
return vd.moveRegex(sheet, regex=r['regex'], regex_flags=r['flags'], **kwargs)
|
59
91
|
|
60
92
|
@Sheet.api
|
61
93
|
@asyncthread
|
@@ -71,13 +103,25 @@ def search_expr(sheet, expr, reverse=False):
|
|
71
103
|
vd.fail(f'no {sheet.rowtype} where {expr}')
|
72
104
|
|
73
105
|
|
74
|
-
Sheet.addCommand('r', 'search-keys', 'tmp=cursorVisibleColIndex;
|
75
|
-
Sheet.addCommand('/', 'search-col', '
|
76
|
-
Sheet.addCommand('?', 'searchr-col', '
|
77
|
-
Sheet.addCommand('n', 'search-next', 'vd.moveRegex(sheet, reverse=False)', 'go to next match from last regex search')
|
78
|
-
Sheet.addCommand('N', 'searchr-next', 'vd.moveRegex(sheet, reverse=True)', 'go to previous match from last regex search')
|
106
|
+
Sheet.addCommand('r', 'search-keys', 'tmp=cursorVisibleColIndex; moveInputRegex("row key", type="regex-row", columns=keyCols or [visibleCols[0]]); sheet.cursorVisibleColIndex=tmp', 'go to next row with key matching regex')
|
107
|
+
Sheet.addCommand('/', 'search-col', 'moveInputRegex("search", columns="cursorCol", backward=False)', 'search for regex forwards in current column')
|
108
|
+
Sheet.addCommand('?', 'searchr-col', 'moveInputRegex("reverse search", columns="cursorCol", backward=True)', 'search for regex backwards in current column')
|
109
|
+
Sheet.addCommand('n', 'search-next', 'vd.moveRegex(sheet, reverse=False)', 'go to next match from last regex search')
|
110
|
+
Sheet.addCommand('N', 'searchr-next', 'vd.moveRegex(sheet, reverse=True)', 'go to previous match from last regex search')
|
79
111
|
|
80
|
-
Sheet.addCommand('g/', 'search-cols', '
|
81
|
-
Sheet.addCommand('g?', 'searchr-cols', '
|
112
|
+
Sheet.addCommand('g/', 'search-cols', 'moveInputRegex("g/", backward=False, columns="visibleCols")', 'search for regex forwards over all visible columns')
|
113
|
+
Sheet.addCommand('g?', 'searchr-cols', 'moveInputRegex("g?", backward=True, columns="visibleCols")', 'search for regex backwards over all visible columns')
|
82
114
|
Sheet.addCommand('z/', 'search-expr', 'search_expr(inputExpr("search by expr: ") or fail("no expr"))', 'search by Python expression forwards in current column (with column names as variables)')
|
83
115
|
Sheet.addCommand('z?', 'searchr-expr', 'search_expr(inputExpr("searchr by expr: ") or fail("no expr"), reverse=True)', 'search by Python expression backwards in current column (with column names as variables)')
|
116
|
+
|
117
|
+
vd.addMenuItems('''
|
118
|
+
View > Search > current column > search-col
|
119
|
+
View > Search > visible columns > search-cols
|
120
|
+
View > Search > key columns > search-keys
|
121
|
+
View > Search > by Python expr > search-expr
|
122
|
+
View > Search > again > search-next
|
123
|
+
View > Search backward > current column > searchr-col
|
124
|
+
View > Search backward > visible columns > searchr-cols
|
125
|
+
View > Search backward > by Python expr > searchr-expr
|
126
|
+
View > Search backward > again > searchr-next
|
127
|
+
''')
|