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/metasheets.py
CHANGED
@@ -2,7 +2,7 @@ import collections
|
|
2
2
|
|
3
3
|
from visidata import globalCommand, BaseSheet, Column, options, vd, anytype, ENTER, asyncthread, Sheet, IndexSheet
|
4
4
|
from visidata import CellColorizer, RowColorizer, JsonLinesSheet, AttrDict
|
5
|
-
from visidata import ColumnAttr,
|
5
|
+
from visidata import ColumnAttr, ItemColumn
|
6
6
|
from visidata import TsvSheet, Path, Option
|
7
7
|
from visidata import undoAttrFunc, VisiData, vlen
|
8
8
|
|
@@ -11,20 +11,30 @@ vd.option('visibility', 0, 'visibility level (0=low, 1=high)')
|
|
11
11
|
vd_system_sep = '\t'
|
12
12
|
|
13
13
|
|
14
|
-
@BaseSheet.lazy_property
|
15
|
-
def optionsSheet(sheet):
|
16
|
-
return OptionsSheet(sheet.name+"_options", source=sheet)
|
17
|
-
|
18
|
-
@VisiData.lazy_property
|
19
|
-
def globalOptionsSheet(vd):
|
20
|
-
return OptionsSheet('global_options', source='global')
|
21
|
-
|
22
|
-
|
23
14
|
class ColumnsSheet(Sheet):
|
24
15
|
rowtype = 'columns'
|
25
16
|
_rowtype = Column
|
26
17
|
_coltype = ColumnAttr
|
27
18
|
precious = False
|
19
|
+
guide = '''# Columns Sheet
|
20
|
+
This is a list of {sheet.nSourceCols} columns on {sheet.displaySource}.
|
21
|
+
Edit values on this sheet to change the appearance of the source sheet.
|
22
|
+
For example, edit the _{sheet.cursorCol.name}_ column to **{sheet.cursorCol.formatted_help}**.
|
23
|
+
|
24
|
+
Some new commands on this sheet operate on all selected columns on the source sheet:
|
25
|
+
|
26
|
+
- {help.commands.hide_selected}
|
27
|
+
- {help.commands.key_selected}
|
28
|
+
- {help.commands.key_off_selected}
|
29
|
+
- {help.commands.type_int_selected}
|
30
|
+
- or `g` with any standard type to set type of selected source columns to that type
|
31
|
+
|
32
|
+
## As usual
|
33
|
+
|
34
|
+
- {help.commands.setcol_input}
|
35
|
+
{sheet.help_columns}
|
36
|
+
'''
|
37
|
+
|
28
38
|
class ValueColumn(Column):
|
29
39
|
'passthrough to the value on the source cursorRow'
|
30
40
|
def calcValue(self, srcCol):
|
@@ -34,17 +44,18 @@ class ColumnsSheet(Sheet):
|
|
34
44
|
|
35
45
|
columns = [
|
36
46
|
ColumnAttr('sheet', type=str),
|
37
|
-
ColumnAttr('name'),
|
47
|
+
ColumnAttr('name', help='rename the column on the source sheet'),
|
38
48
|
ColumnAttr('keycol', type=int, width=0),
|
39
|
-
ColumnAttr('width', type=int),
|
40
|
-
ColumnAttr('height', type=int),
|
49
|
+
ColumnAttr('width', type=int, help='set the column width (`0` to hide completely)'),
|
50
|
+
ColumnAttr('height', type=int, max_help=1, help='set a maximum height for the row, if this column will fill it'),
|
41
51
|
ColumnAttr('hoffset', type=int, width=0),
|
42
52
|
ColumnAttr('voffset', type=int, width=0),
|
43
|
-
ColumnAttr('type', 'typestr'),
|
44
|
-
ColumnAttr('fmtstr'),
|
45
|
-
ColumnAttr('formatter'),
|
46
|
-
|
47
|
-
|
53
|
+
ColumnAttr('type', 'typestr', help='convert all values to a specific type'),
|
54
|
+
ColumnAttr('fmtstr', help='use a custom format string, either C-style (`%0.4f`) or Python-style (`{{:0.4f}}`)'),
|
55
|
+
ColumnAttr('formatter', max_help=1, help='use a custom format function (**{col.help_formatters}**)'),
|
56
|
+
ColumnAttr('displayer', max_help=1, help='use a custom display function (**{col.help_displayers}**)'),
|
57
|
+
ValueColumn('value', help='change the value of this cell on the source sheet'),
|
58
|
+
ColumnAttr('expr', max_help=1, help='change the main column parameter'),
|
48
59
|
ColumnAttr('ncalcs', type=int, width=0, cache=False),
|
49
60
|
ColumnAttr('maxtime', type=float, width=0, cache=False),
|
50
61
|
ColumnAttr('totaltime', type=float, width=0, cache=False),
|
@@ -54,13 +65,18 @@ class ColumnsSheet(Sheet):
|
|
54
65
|
RowColorizer(7, 'color_key_col', lambda s,c,r,v: r and r.keycol),
|
55
66
|
RowColorizer(8, 'color_hidden_col', lambda s,c,r,v: r and r.hidden),
|
56
67
|
]
|
57
|
-
|
68
|
+
|
69
|
+
@property
|
70
|
+
def nSourceCols(self):
|
71
|
+
return sum(vs.nCols for vs in self.source)
|
72
|
+
|
73
|
+
def loader(self):
|
58
74
|
if len(self.source) == 1:
|
59
75
|
self.rows = self.source[0].columns
|
60
76
|
self.cursorRowIndex = self.source[0].cursorColIndex
|
61
77
|
self.columns[0].hide() # hide 'sheet' column if only one sheet
|
62
78
|
else:
|
63
|
-
self.rows = [col for vs in self.source for col in vs.visibleCols if vs is not self]
|
79
|
+
self.rows = [col for vs in self.source for col in vs.visibleCols if isinstance(vs, Sheet) and vs is not self]
|
64
80
|
|
65
81
|
def newRow(self):
|
66
82
|
c = type(self.source[0])._coltype()
|
@@ -81,110 +97,14 @@ VisiDataMetaSheet.options.row_delimiter = '\n'
|
|
81
97
|
VisiDataMetaSheet.options.encoding = 'utf-8'
|
82
98
|
|
83
99
|
|
84
|
-
class OptionsSheet(Sheet):
|
85
|
-
_rowtype = Option # rowdef: Option
|
86
|
-
rowtype = 'options'
|
87
|
-
precious = False
|
88
|
-
columns = (
|
89
|
-
Column('option', getter=lambda col,row: row.name),
|
90
|
-
Column('value',
|
91
|
-
getter=lambda col,row: col.sheet.diffOption(row.name),
|
92
|
-
setter=lambda col,row,val: options.set(row.name, val, col.sheet.source),
|
93
|
-
),
|
94
|
-
Column('default', getter=lambda col,row: options.getdefault(row.name)),
|
95
|
-
Column('description', width=40, getter=lambda col,row: options._get(row.name, 'default').helpstr),
|
96
|
-
ColumnAttr('replayable'),
|
97
|
-
)
|
98
|
-
colorizers = [
|
99
|
-
CellColorizer(3, None, lambda s,c,r,v: v.value if r and c in s.columns[1:3] and r.name.startswith('color_') else None),
|
100
|
-
]
|
101
|
-
nKeys = 1
|
102
|
-
|
103
|
-
def diffOption(self, optname):
|
104
|
-
return options.getonly(optname, self.source, '')
|
105
|
-
|
106
|
-
def editOption(self, row):
|
107
|
-
currentValue = options.getobj(row.name, self.source)
|
108
|
-
vd.addUndo(options.set, row.name, currentValue, self.source)
|
109
|
-
if isinstance(row.value, bool):
|
110
|
-
options.set(row.name, not currentValue, self.source)
|
111
|
-
else:
|
112
|
-
options.set(row.name, self.editCell(1, value=currentValue), self.source)
|
113
|
-
|
114
|
-
def reload(self):
|
115
|
-
self.rows = []
|
116
|
-
for k in options.keys():
|
117
|
-
opt = options._get(k)
|
118
|
-
self.addRow(opt)
|
119
|
-
self.columns[1].name = 'global_value' if self.source == 'global' else 'sheet_value'
|
120
|
-
|
121
|
-
|
122
|
-
vd._lastInputs = collections.defaultdict(dict) # [input_type] -> {'input': anything}
|
123
|
-
|
124
|
-
class LastInputsSheet(JsonLinesSheet):
|
125
|
-
columns = [
|
126
|
-
ColumnItem('type'),
|
127
|
-
ColumnItem('input'),
|
128
|
-
]
|
129
|
-
def __init__(self, *args, **kwargs):
|
130
|
-
super().__init__(*args, **kwargs)
|
131
|
-
self.colnames = {col.name:col for col in self.columns}
|
132
|
-
|
133
|
-
def addRow(self, row, **kwargs):
|
134
|
-
'Update lastInputs before adding row.'
|
135
|
-
row = AttrDict(row)
|
136
|
-
if row.input in vd._lastInputs[row.type]:
|
137
|
-
del vd._lastInputs[row.type][row.input] # so will be added as last entry
|
138
|
-
vd._lastInputs[row.type][row.input] = 1
|
139
|
-
return super().addRow(row, **kwargs)
|
140
|
-
|
141
|
-
def appendRow(self, row):
|
142
|
-
'Append *row* (AttrDict with *type* and *input*) directly to source.'
|
143
|
-
hist = self.history(row.type)
|
144
|
-
if hist and hist[-1] == row.input:
|
145
|
-
return
|
146
|
-
|
147
|
-
self.addRow(row)
|
148
|
-
|
149
|
-
if self.source:
|
150
|
-
with self.source.open_text(mode='a', encoding=self.options.encoding) as fp:
|
151
|
-
import json
|
152
|
-
fp.write(json.dumps(row) + '\n')
|
153
|
-
|
154
|
-
def history(self, t):
|
155
|
-
'Return list of inputs in category *t*, with last element being the most recently added.'
|
156
|
-
return list(vd._lastInputs[t].keys())
|
157
|
-
|
158
|
-
|
159
|
-
@VisiData.lazy_property
|
160
|
-
def lastInputsSheet(vd):
|
161
|
-
name = options.input_history
|
162
|
-
|
163
|
-
if not name:
|
164
|
-
return LastInputsSheet('last_inputs', source=None, rows=[])
|
165
|
-
|
166
|
-
p = Path(name)
|
167
|
-
if not p.is_absolute():
|
168
|
-
p = Path(options.visidata_dir)/f'{name}.jsonl'
|
169
|
-
|
170
|
-
vs = LastInputsSheet(name, source=p)
|
171
|
-
try:
|
172
|
-
vs.reload.__wrapped__(vs)
|
173
|
-
except FileNotFoundError:
|
174
|
-
pass
|
175
|
-
except Exception as e:
|
176
|
-
vd.exceptionCaught(e)
|
177
|
-
|
178
|
-
return vs
|
179
|
-
|
180
|
-
|
181
100
|
@VisiData.property
|
182
101
|
def allColumnsSheet(vd):
|
183
102
|
return ColumnsSheet("all_columns", source=vd.stackedSheets)
|
184
103
|
|
104
|
+
|
185
105
|
@VisiData.api
|
186
106
|
def save_visidatarc(vd, p, vs):
|
187
|
-
with p.
|
107
|
+
with p.open(mode='w') as fp:
|
188
108
|
for opt in vs.rows:
|
189
109
|
rval = repr(opt.value)
|
190
110
|
defopt = vd.options._get(opt.name, 'default')
|
@@ -192,7 +112,9 @@ def save_visidatarc(vd, p, vs):
|
|
192
112
|
fp.write(f'{leading}options.{opt.name:25s} = {rval:10s} # {defopt.helpstr}\n')
|
193
113
|
|
194
114
|
|
195
|
-
|
115
|
+
ColumnsSheet.addCommand('', 'join-cols', 'sheet.join_cols()', 'add column from concatenating selected source columns')
|
116
|
+
|
117
|
+
@ColumnsSheet.api
|
196
118
|
def join_cols(sheet):
|
197
119
|
cols = sheet.onlySelectedRows
|
198
120
|
destSheet = cols[0].sheet
|
@@ -209,40 +131,36 @@ def join_cols(sheet):
|
|
209
131
|
|
210
132
|
# copy vd.sheets so that ColumnsSheet itself isn't included (for recalc in addRow)
|
211
133
|
globalCommand('gC', 'columns-all', 'vd.push(vd.allColumnsSheet)', 'open Columns Sheet: edit column properties for all visible columns from all sheets on the sheets stack')
|
212
|
-
globalCommand('O', 'options-global', 'vd.push(vd.globalOptionsSheet)', 'open Options Sheet: edit global options (apply to all sheets)')
|
213
|
-
|
214
|
-
BaseSheet.addCommand('zO', 'options-sheet', 'vd.push(sheet.optionsSheet)', 'open Options Sheet: edit sheet options (apply to current sheet only)')
|
215
|
-
BaseSheet.addCommand(None, 'open-inputs', 'vd.push(lastInputsSheet)', '')
|
216
134
|
|
217
135
|
Sheet.addCommand('C', 'columns-sheet', 'vd.push(ColumnsSheet(name+"_columns", source=[sheet]))', 'open Columns Sheet: edit column properties for current sheet')
|
218
136
|
|
219
137
|
# used ColumnsSheet, affecting the 'row' (source column)
|
220
|
-
ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'toggle selected
|
221
|
-
ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected
|
138
|
+
ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'toggle selected source columns as key columns')
|
139
|
+
ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected source columns as key columns')
|
222
140
|
|
223
|
-
ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected
|
141
|
+
ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected source columns')
|
224
142
|
ColumnsSheet.addCommand(None, 'resize-source-rows-max', 'for c in selectedRows or [cursorRow]: c.setWidth(c.getMaxWidth(c.sheet.visibleRows))', 'adjust widths of selected source columns')
|
225
143
|
|
226
|
-
ColumnsSheet.addCommand('g%', 'type-float-selected', 'onlySelectedRows.type=float', 'set type of selected columns to float')
|
227
|
-
ColumnsSheet.addCommand('g#', 'type-int-selected', 'onlySelectedRows.type=int', 'set type of selected columns to int')
|
228
|
-
ColumnsSheet.addCommand('gz#', 'type-len-selected', 'onlySelectedRows.type=vlen', 'set type of selected columns to len')
|
229
|
-
ColumnsSheet.addCommand('g@', 'type-date-selected', 'onlySelectedRows.type=date', 'set type of selected columns to date')
|
230
|
-
ColumnsSheet.addCommand('g$', 'type-currency-selected', 'onlySelectedRows.type=currency', 'set type of selected columns to currency')
|
231
|
-
ColumnsSheet.addCommand('g~', 'type-string-selected', 'onlySelectedRows.type=str', 'set type of selected columns to str')
|
232
|
-
ColumnsSheet.addCommand('gz~', 'type-any-selected', 'onlySelectedRows.type=anytype', 'set type of selected columns to anytype')
|
233
|
-
ColumnsSheet.addCommand('gz%', 'type-floatsi-selected', 'onlySelectedRows.type=floatsi', 'set type of selected columns to floatsi')
|
234
|
-
ColumnsSheet.addCommand('', 'type-floatlocale-selected', 'onlySelectedRows.type=floatlocale', 'set type of selected columns to float using system locale')
|
235
|
-
|
236
|
-
OptionsSheet.addCommand('d', 'unset-option', 'options.unset(cursorRow.name, str(source))', 'remove option override for this context')
|
237
|
-
OptionsSheet.addCommand(None, 'edit-option', 'editOption(cursorRow)', 'edit option at current row')
|
238
|
-
OptionsSheet.bindkey('e', 'edit-option')
|
239
|
-
OptionsSheet.bindkey(ENTER, 'edit-option')
|
144
|
+
ColumnsSheet.addCommand('g%', 'type-float-selected', 'onlySelectedRows.type=float', 'set type of selected source columns to float')
|
145
|
+
ColumnsSheet.addCommand('g#', 'type-int-selected', 'onlySelectedRows.type=int', 'set type of selected source columns to int')
|
146
|
+
ColumnsSheet.addCommand('gz#', 'type-len-selected', 'onlySelectedRows.type=vlen', 'set type of selected source columns to len')
|
147
|
+
ColumnsSheet.addCommand('g@', 'type-date-selected', 'onlySelectedRows.type=date', 'set type of selected source columns to date')
|
148
|
+
ColumnsSheet.addCommand('g$', 'type-currency-selected', 'onlySelectedRows.type=currency', 'set type of selected source columns to currency')
|
149
|
+
ColumnsSheet.addCommand('g~', 'type-string-selected', 'onlySelectedRows.type=str', 'set type of selected source columns to str')
|
150
|
+
ColumnsSheet.addCommand('gz~', 'type-any-selected', 'onlySelectedRows.type=anytype', 'set type of selected source columns to anytype')
|
151
|
+
ColumnsSheet.addCommand('gz%', 'type-floatsi-selected', 'onlySelectedRows.type=floatsi', 'set type of selected source columns to floatsi')
|
152
|
+
ColumnsSheet.addCommand('', 'type-floatlocale-selected', 'onlySelectedRows.type=floatlocale', 'set type of selected source columns to float using system locale')
|
153
|
+
|
240
154
|
MetaSheet.options.header = 0
|
241
155
|
|
242
156
|
|
243
157
|
vd.addGlobals({
|
244
158
|
'ColumnsSheet': ColumnsSheet,
|
245
159
|
'MetaSheet': MetaSheet,
|
246
|
-
'OptionsSheet': OptionsSheet,
|
247
160
|
'VisiDataMetaSheet': VisiDataMetaSheet,
|
248
161
|
})
|
162
|
+
|
163
|
+
vd.addMenuItems('''
|
164
|
+
View > Columns > this sheet > columns-sheet
|
165
|
+
View > Columns > all sheets > columns-all
|
166
|
+
''')
|
visidata/modify.py
CHANGED
@@ -1,8 +1,37 @@
|
|
1
|
-
from
|
1
|
+
from copy import copy
|
2
2
|
|
3
|
-
vd
|
4
|
-
|
5
|
-
|
3
|
+
from visidata import vd, VisiData, asyncthread
|
4
|
+
from visidata import Sheet, RowColorizer, CellColorizer, Column, BaseSheet, Progress
|
5
|
+
|
6
|
+
vd.theme_option('color_add_pending', 'green', 'color for rows pending add')
|
7
|
+
vd.theme_option('color_change_pending', 'reverse yellow', 'color for cells pending modification')
|
8
|
+
vd.theme_option('color_delete_pending', 'red', 'color for rows pending delete')
|
9
|
+
vd.option('overwrite', 'c', 'overwrite existing files {y=yes|c=confirm|n=no}')
|
10
|
+
|
11
|
+
vd.optalias('readonly', 'overwrite', 'n')
|
12
|
+
vd.optalias('ro', 'overwrite', 'n')
|
13
|
+
vd.optalias('y', 'overwrite', 'y')
|
14
|
+
|
15
|
+
|
16
|
+
@VisiData.api
|
17
|
+
def couldOverwrite(vd) -> bool:
|
18
|
+
'Return True if overwrite might be allowed.'
|
19
|
+
return vd.options.overwrite.startswith(('y','c'))
|
20
|
+
|
21
|
+
|
22
|
+
@VisiData.api
|
23
|
+
def confirmOverwrite(vd, path, msg:str=''):
|
24
|
+
'Fail if file exists and overwrite not allowed.'
|
25
|
+
if path is None or path.exists():
|
26
|
+
msg = msg or f'{path.given} exists. overwrite? '
|
27
|
+
ow = vd.options.overwrite
|
28
|
+
if ow.startswith('c'): # confirm
|
29
|
+
vd.confirm(msg)
|
30
|
+
elif ow.startswith('y'): # yes/always
|
31
|
+
pass
|
32
|
+
else: #1805 empty/no/never/readonly
|
33
|
+
vd.fail('overwrite disabled')
|
34
|
+
return True
|
6
35
|
|
7
36
|
# deferred cached
|
8
37
|
@Sheet.lazy_property
|
@@ -19,7 +48,7 @@ def _deferredDels(sheet):
|
|
19
48
|
|
20
49
|
Sheet.colorizers += [
|
21
50
|
RowColorizer(9, 'color_add_pending', lambda s,c,r,v: s.rowid(r) in s._deferredAdds),
|
22
|
-
CellColorizer(8, 'color_change_pending', lambda s,c,r,v: s.isChanged(c, r)),
|
51
|
+
CellColorizer(8, 'color_change_pending', lambda s,c,r,v: c and (r is not None) and s.isChanged(c, r)),
|
23
52
|
RowColorizer(9, 'color_delete_pending', lambda s,c,r,v: s.isDeleted(r)),
|
24
53
|
]
|
25
54
|
|
@@ -88,9 +117,9 @@ def addRows(sheet, rows, index=None, undo=True):
|
|
88
117
|
'Add *rows* after row at *index*.'
|
89
118
|
addedRows = {}
|
90
119
|
if index is None: index=len(sheet.rows)
|
91
|
-
for row in Progress(rows, gerund='adding'):
|
120
|
+
for i, row in enumerate(Progress(rows, gerund='adding')):
|
92
121
|
addedRows[sheet.rowid(row)] = row
|
93
|
-
sheet.addRow(row, index=index+1)
|
122
|
+
sheet.addRow(row, index=index+i+1)
|
94
123
|
|
95
124
|
if sheet.defer:
|
96
125
|
sheet.rowAdded(row)
|
@@ -136,8 +165,11 @@ def deleteBy(sheet, func, commit=False, undo=True):
|
|
136
165
|
if r is newCursorRow:
|
137
166
|
sheet.cursorRowIndex = len(sheet.rows)-1
|
138
167
|
else:
|
139
|
-
|
140
|
-
|
168
|
+
try:
|
169
|
+
sheet.commitDeleteRow(r)
|
170
|
+
ndeleted += 1
|
171
|
+
except Exception as e:
|
172
|
+
vd.exceptionCaught(e)
|
141
173
|
|
142
174
|
if undo:
|
143
175
|
vd.addUndo(setattr, sheet, 'rows', oldrows)
|
@@ -176,17 +208,30 @@ def getSourceValue(col, row):
|
|
176
208
|
@Sheet.api
|
177
209
|
def commitAdds(self):
|
178
210
|
'Return the number of rows that have been marked for deferred add-row. Clear the marking.'
|
179
|
-
nadded =
|
180
|
-
|
181
|
-
|
211
|
+
nadded = 0
|
212
|
+
nerrors = 0
|
213
|
+
for row in self._deferredAdds.values():
|
214
|
+
try:
|
215
|
+
self.commitAddRow(row)
|
216
|
+
nadded += 1
|
217
|
+
except Exception as e:
|
218
|
+
vd.exceptionCaught(e)
|
219
|
+
nerrors += 1
|
220
|
+
|
221
|
+
if nadded or nerrors:
|
222
|
+
vd.status(f'added {nadded} {self.rowtype} ({nerrors} errors)')
|
223
|
+
|
182
224
|
self._deferredAdds.clear()
|
183
225
|
return nadded
|
184
226
|
|
227
|
+
|
185
228
|
@Sheet.api
|
186
|
-
def commitMods(
|
187
|
-
'
|
229
|
+
def commitMods(sheet):
|
230
|
+
'Commit all deferred modifications (that are not from rows added or deleted in this commit. Return number of cells changed.'
|
231
|
+
_, deferredmods, _ = sheet.getDeferredChanges()
|
232
|
+
|
188
233
|
nmods = 0
|
189
|
-
for row, rowmods in
|
234
|
+
for row, rowmods in deferredmods.values():
|
190
235
|
for col, val in rowmods.items():
|
191
236
|
try:
|
192
237
|
col.putValue(row, val)
|
@@ -194,7 +239,7 @@ def commitMods(self):
|
|
194
239
|
except Exception as e:
|
195
240
|
vd.exceptionCaught(e)
|
196
241
|
|
197
|
-
|
242
|
+
sheet._deferredMods.clear()
|
198
243
|
return nmods
|
199
244
|
|
200
245
|
@Sheet.api
|
@@ -206,9 +251,16 @@ def commitDeletes(self):
|
|
206
251
|
vd.status('deleted %s %s' % (ndeleted, self.rowtype))
|
207
252
|
return ndeleted
|
208
253
|
|
254
|
+
|
255
|
+
@Sheet.api
|
256
|
+
def commitAddRow(self, row):
|
257
|
+
'To commit an added row. Override per sheet type.'
|
258
|
+
|
259
|
+
|
209
260
|
@Sheet.api
|
210
|
-
def
|
211
|
-
|
261
|
+
def commitDeleteRow(self, row):
|
262
|
+
'To commit a deleted row. Override per sheet type.'
|
263
|
+
|
212
264
|
|
213
265
|
@asyncthread
|
214
266
|
@Sheet.api
|
@@ -218,8 +270,6 @@ def putChanges(sheet):
|
|
218
270
|
sheet.commitMods()
|
219
271
|
sheet.commitDeletes()
|
220
272
|
|
221
|
-
vd.saveSheets(Path(sheet.source), sheet, confirm_overwrite=False)
|
222
|
-
|
223
273
|
# clear after save, to ensure cstr (in commit()) is aware of deletes
|
224
274
|
sheet._deferredDels.clear()
|
225
275
|
|
@@ -268,10 +318,8 @@ def commit(sheet, *rows):
|
|
268
318
|
|
269
319
|
adds, mods, deletes = sheet.getDeferredChanges()
|
270
320
|
cstr = sheet.changestr(adds, mods, deletes)
|
271
|
-
path = sheet.source
|
272
321
|
|
273
|
-
|
274
|
-
vd.confirm('really %s? ' % cstr)
|
322
|
+
vd.confirmOverwrite(sheet.rootSheet().source, 'really ' + cstr + '? ')
|
275
323
|
|
276
324
|
sheet.putChanges()
|
277
325
|
sheet.hasBeenModified = False
|
@@ -287,3 +335,11 @@ Sheet.addCommand('za', 'addcol-new', 'addColumnAtCursor(SettableColumn(input("co
|
|
287
335
|
Sheet.addCommand('gza', 'addcol-bulk', 'addColumnAtCursor(*(SettableColumn() for c in range(int(input("add columns: ")))))', 'append N empty columns')
|
288
336
|
|
289
337
|
Sheet.addCommand('z^S', 'commit-sheet', 'commit()', 'commit changes back to source. not undoable!')
|
338
|
+
|
339
|
+
vd.addMenuItems('''
|
340
|
+
File > Save > changes to source > commit-sheet
|
341
|
+
Row > Add > one row
|
342
|
+
Row > Add > multiple rows
|
343
|
+
Column > Add column > empty > one column > addcol-new
|
344
|
+
Column > Add column > empty > multiple columns > addcol-bulk
|
345
|
+
''')
|
visidata/motd.py
CHANGED
@@ -5,7 +5,7 @@ a motd file from a url. The file may be text or unheaded TSV, with one message
|
|
5
5
|
|
6
6
|
Any Exception ends the thread silently.
|
7
7
|
|
8
|
-
options.motd_url may be set to another URL, or empty to disable entirely.
|
8
|
+
options.motd_url may be set to another URL, or empty string to disable entirely.
|
9
9
|
'''
|
10
10
|
|
11
11
|
import random
|
@@ -15,7 +15,7 @@ from visidata import options, asyncsingle, vd, VisiData
|
|
15
15
|
vd.option('motd_url', 'https://visidata.org/motd-'+vd.version, 'source of randomized startup messages', sheettype=None)
|
16
16
|
|
17
17
|
|
18
|
-
vd.motd = ''
|
18
|
+
vd.motd = 'Support VisiData: https://github.com/sponsors/saulpw'
|
19
19
|
|
20
20
|
@VisiData.api
|
21
21
|
@asyncsingle
|
@@ -25,6 +25,6 @@ def domotd(vd):
|
|
25
25
|
p = vd.urlcache(options.motd_url, days=1)
|
26
26
|
line = random.choice(list(p))
|
27
27
|
vd.motd = line.split('\t')[0]
|
28
|
-
vd.status(vd.motd, priority=-1)
|
29
28
|
except Exception:
|
30
29
|
pass
|
30
|
+
vd.status(vd.motd, priority=-1)
|
visidata/mouse.py
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
import curses
|
2
|
+
|
3
|
+
from visidata import vd, VisiData, BaseSheet, Sheet, AttrDict
|
4
|
+
|
5
|
+
|
6
|
+
# registry of mouse events. cleared before every draw cycle.
|
7
|
+
vd.mousereg = [] # list of AttrDict(y=, x=, h=, w=, buttonfuncs=dict)
|
8
|
+
|
9
|
+
# sheet mouse position for current mouse event
|
10
|
+
BaseSheet.init('mouseX', int)
|
11
|
+
BaseSheet.init('mouseY', int)
|
12
|
+
|
13
|
+
|
14
|
+
@VisiData.after
|
15
|
+
def initCurses(vd):
|
16
|
+
curses.MOUSE_ALL = 0xffffffff
|
17
|
+
curses.mousemask(curses.MOUSE_ALL if vd.options.mouse_interval else 0)
|
18
|
+
curses.mouseinterval(vd.options.mouse_interval)
|
19
|
+
curses.mouseEvents = {}
|
20
|
+
|
21
|
+
for k in dir(curses):
|
22
|
+
if k.startswith('BUTTON') or k in ('REPORT_MOUSE_POSITION', '2097152'):
|
23
|
+
curses.mouseEvents[getattr(curses, k)] = k
|
24
|
+
|
25
|
+
|
26
|
+
@VisiData.after
|
27
|
+
def clearCaches(vd):
|
28
|
+
vd.mousereg = []
|
29
|
+
|
30
|
+
|
31
|
+
@VisiData.api
|
32
|
+
def onMouse(vd, scr, x, y, w, h, **kwargs):
|
33
|
+
px, py = vd.getrootxy(scr)
|
34
|
+
e = AttrDict(x=x+px, y=y+py, w=w, h=h, buttonfuncs=kwargs)
|
35
|
+
vd.mousereg.append(e)
|
36
|
+
|
37
|
+
|
38
|
+
@VisiData.api
|
39
|
+
def getMouse(vd, _x, _y, button):
|
40
|
+
for reg in vd.mousereg[::-1]:
|
41
|
+
if reg.x <= _x < reg.x+reg.w and reg.y <= _y < reg.y+reg.h and button in reg.buttonfuncs:
|
42
|
+
return reg.buttonfuncs[button]
|
43
|
+
|
44
|
+
|
45
|
+
@VisiData.api
|
46
|
+
def parseMouse(vd, **kwargs):
|
47
|
+
'Return list of mouse interactions (clicktype, y, x, name, scr) for curses screens given in kwargs as name:scr.'
|
48
|
+
|
49
|
+
devid, x, y, z, bstate = curses.getmouse()
|
50
|
+
|
51
|
+
clicktype = ''
|
52
|
+
if bstate & curses.BUTTON_CTRL:
|
53
|
+
clicktype += "Ctrl+"
|
54
|
+
bstate &= ~curses.BUTTON_CTRL
|
55
|
+
if bstate & curses.BUTTON_ALT:
|
56
|
+
clicktype += "Alt+"
|
57
|
+
bstate &= ~curses.BUTTON_ALT
|
58
|
+
if bstate & curses.BUTTON_SHIFT:
|
59
|
+
clicktype += "Shift+"
|
60
|
+
bstate &= ~curses.BUTTON_SHIFT
|
61
|
+
|
62
|
+
keystroke = clicktype + curses.mouseEvents.get(bstate, str(bstate))
|
63
|
+
ret = AttrDict(keystroke=keystroke, y=y, x=x, found=[])
|
64
|
+
for winname, winscr in kwargs.items():
|
65
|
+
px, py = vd.getrootxy(winscr)
|
66
|
+
mh, mw = winscr.getmaxyx()
|
67
|
+
if py <= y < py+mh and px <= x < px+mw:
|
68
|
+
ret.found.append(winname)
|
69
|
+
# vd.debug(f'{keystroke} at ({x-px}, {y-py}) in window {winname} {winscr}')
|
70
|
+
|
71
|
+
return ret
|
72
|
+
|
73
|
+
|
74
|
+
@VisiData.api
|
75
|
+
def handleMouse(vd, sheet):
|
76
|
+
try:
|
77
|
+
vd.keystrokes = ''
|
78
|
+
pct = vd.windowConfig['pct']
|
79
|
+
topPaneActive = ((vd.activePane == 2 and pct < 0) or (vd.activePane == 1 and pct > 0))
|
80
|
+
bottomPaneActive = ((vd.activePane == 1 and pct < 0) or (vd.activePane == 2 and pct > 0))
|
81
|
+
r = None
|
82
|
+
r = vd.parseMouse(top=vd.winTop, bot=vd.winBottom, menu=vd.scrMenu)
|
83
|
+
if (bottomPaneActive and 'top' in r.found) or (topPaneActive and 'bot' in r.found):
|
84
|
+
vd.activePane = 1 if vd.activePane == 2 else 2
|
85
|
+
sheet = vd.activeSheet
|
86
|
+
|
87
|
+
f = vd.getMouse(r.x, r.y, r.keystroke)
|
88
|
+
winx, winy = vd.getrootxy(sheet._scr)
|
89
|
+
sheet.mouseX, sheet.mouseY = r.x-winx, r.y-winy
|
90
|
+
if f:
|
91
|
+
if isinstance(f, str):
|
92
|
+
if f.startswith('onclick'):
|
93
|
+
if '://' in f:
|
94
|
+
vd.launchBrowser(f[8:])
|
95
|
+
else:
|
96
|
+
sheet.execCommand(f[8:])
|
97
|
+
else:
|
98
|
+
for cmd in f.split():
|
99
|
+
sheet.execCommand(cmd)
|
100
|
+
else:
|
101
|
+
f(r.y, r.x, r.keystroke)
|
102
|
+
|
103
|
+
vd.keystrokes = vd.prettykeys(r.keystroke)
|
104
|
+
return '' # handled
|
105
|
+
except curses.error:
|
106
|
+
pass
|
107
|
+
|
108
|
+
return r.keystroke if r else ''
|
109
|
+
|
110
|
+
|
111
|
+
@Sheet.api
|
112
|
+
def visibleColAtX(sheet, x):
|
113
|
+
for vcolidx, (colx, w) in sheet._visibleColLayout.items():
|
114
|
+
if colx <= x <= colx+w:
|
115
|
+
return vcolidx
|
116
|
+
|
117
|
+
|
118
|
+
@Sheet.api
|
119
|
+
def visibleRowAtY(sheet, y):
|
120
|
+
for rowidx, (rowy, h) in sheet._rowLayout.items():
|
121
|
+
if rowy <= y <= rowy+h-1:
|
122
|
+
return rowidx
|
123
|
+
|
124
|
+
|
125
|
+
@Sheet.command('BUTTON1_PRESSED', 'go-mouse', 'set cursor to row and column where mouse was clicked')
|
126
|
+
def go_mouse(sheet):
|
127
|
+
ridx = sheet.visibleRowAtY(sheet.mouseY)
|
128
|
+
if ridx is not None:
|
129
|
+
sheet.cursorRowIndex = ridx
|
130
|
+
cidx = sheet.visibleColAtX(sheet.mouseX)
|
131
|
+
if cidx is not None:
|
132
|
+
sheet.cursorVisibleColIndex = cidx
|
133
|
+
|
134
|
+
Sheet.addCommand(None, 'scroll-mouse', 'sheet.topRowIndex=cursorRowIndex-mouseY+1', 'scroll to mouse cursor location')
|
135
|
+
|
136
|
+
Sheet.addCommand('ScrollUp', 'scroll-up', 'cursorDown(options.scroll_incr); sheet.topRowIndex += options.scroll_incr', 'scroll one row up')
|
137
|
+
Sheet.addCommand('ScrollDown', 'scroll-down', 'cursorDown(-options.scroll_incr); sheet.topRowIndex -= options.scroll_incr', 'scroll one row down')
|