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/cmdlog.py
CHANGED
@@ -1,40 +1,47 @@
|
|
1
1
|
import threading
|
2
2
|
|
3
|
-
from visidata import
|
3
|
+
from visidata import vd, UNLOADED, namedlist, vlen, asyncthread, globalCommand, date
|
4
|
+
from visidata import VisiData, BaseSheet, Sheet, ColumnAttr, VisiDataMetaSheet, JsonLinesSheet, TypedWrapper, AttrDict, Progress, ErrorSheet, CompleteKey, Path
|
4
5
|
import visidata
|
5
6
|
|
6
7
|
vd.option('replay_wait', 0.0, 'time to wait between replayed commands, in seconds', sheettype=None)
|
7
|
-
vd.
|
8
|
-
vd.
|
9
|
-
vd.option('color_status_replay', 'green', 'color of replay status indicator')
|
10
|
-
vd.option('replay_movement', False, 'insert movements during replay', sheettype=None)
|
8
|
+
vd.theme_option('disp_replay_play', '▶', 'status indicator for active replay')
|
9
|
+
vd.theme_option('color_status_replay', 'green', 'color of replay status indicator')
|
11
10
|
|
12
11
|
# prefixes which should not be logged
|
13
12
|
nonLogged = '''forget exec-longname undo redo quit
|
14
13
|
show error errors statuses options threads jump
|
15
|
-
replay cancel save-cmdlog macro cmdlog-sheet menu repeat
|
14
|
+
replay cancel save-cmdlog macro cmdlog-sheet menu repeat reload-every
|
16
15
|
go- search scroll prev next page start end zoom resize visibility sidebar
|
17
16
|
mouse suspend redraw no-op help syscopy sysopen profile toggle'''.split()
|
18
17
|
|
19
18
|
vd.option('rowkey_prefix', 'キ', 'string prefix for rowkey in the cmdlog', sheettype=None)
|
20
|
-
vd.option('cmdlog_histfile', '', 'file to autorecord each cmdlog action to', sheettype=None)
|
21
19
|
|
22
20
|
vd.activeCommand = UNLOADED
|
21
|
+
vd._nextCommands = [] # list[str|CommandLogRow] for vd.queueCommand
|
22
|
+
|
23
|
+
CommandLogRow = namedlist('CommandLogRow', 'sheet col row longname input keystrokes comment undofuncs'.split())
|
24
|
+
|
25
|
+
@VisiData.api
|
26
|
+
def queueCommand(vd, longname, input=None, sheet=None, col=None, row=None):
|
27
|
+
'Add command to queue of next commands to execute.'
|
28
|
+
vd._nextCommands.append(CommandLogRow(longname=longname, input=input, sheet=sheet, col=col, row=row))
|
29
|
+
|
23
30
|
|
24
31
|
@VisiData.api
|
25
32
|
def open_vd(vd, p):
|
26
|
-
return CommandLog(p.
|
33
|
+
return CommandLog(p.base_stem, source=p, precious=True)
|
27
34
|
|
28
35
|
@VisiData.api
|
29
36
|
def open_vdj(vd, p):
|
30
|
-
return CommandLogJsonl(p.
|
37
|
+
return CommandLogJsonl(p.base_stem, source=p, precious=True)
|
31
38
|
|
32
39
|
VisiData.save_vd = VisiData.save_tsv
|
33
40
|
|
34
41
|
|
35
42
|
@VisiData.api
|
36
43
|
def save_vdj(vd, p, *vsheets):
|
37
|
-
with p.
|
44
|
+
with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
|
38
45
|
fp.write("#!vd -p\n")
|
39
46
|
for vs in vsheets:
|
40
47
|
vs.write_jsonl(fp)
|
@@ -46,7 +53,7 @@ def checkVersion(vd, desired_version):
|
|
46
53
|
vd.fail("version %s required" % desired_version)
|
47
54
|
|
48
55
|
@VisiData.api
|
49
|
-
def fnSuffix(vd, prefix):
|
56
|
+
def fnSuffix(vd, prefix:str):
|
50
57
|
i = 0
|
51
58
|
fn = prefix + '.vdj'
|
52
59
|
while Path(fn).exists():
|
@@ -55,18 +62,6 @@ def fnSuffix(vd, prefix):
|
|
55
62
|
|
56
63
|
return fn
|
57
64
|
|
58
|
-
@BaseSheet.api
|
59
|
-
def inputLongname(sheet):
|
60
|
-
longnames = set(k for (k, obj), v in vd.commands.iter(sheet))
|
61
|
-
return vd.input("command name: ", completer=CompleteKey(sorted(longnames)), type='longname')
|
62
|
-
|
63
|
-
@BaseSheet.api
|
64
|
-
def exec_longname(sheet, longname):
|
65
|
-
if not sheet.getCommand(longname):
|
66
|
-
vd.warning(f'no command {longname}')
|
67
|
-
return
|
68
|
-
sheet.execCommand(longname)
|
69
|
-
|
70
65
|
def indexMatch(L, func):
|
71
66
|
'returns the smallest i for which func(L[i]) is true'
|
72
67
|
for i, x in enumerate(L):
|
@@ -74,7 +69,7 @@ def indexMatch(L, func):
|
|
74
69
|
return i
|
75
70
|
|
76
71
|
def keystr(k):
|
77
|
-
return options.rowkey_prefix+','.join(map(str, k))
|
72
|
+
return vd.options.rowkey_prefix+','.join(map(str, k))
|
78
73
|
|
79
74
|
@VisiData.api
|
80
75
|
def isLoggableCommand(vd, longname):
|
@@ -84,7 +79,7 @@ def isLoggableCommand(vd, longname):
|
|
84
79
|
return True
|
85
80
|
|
86
81
|
def isLoggableSheet(sheet):
|
87
|
-
return sheet is not vd.cmdlog and not isinstance(sheet, (OptionsSheet, ErrorSheet))
|
82
|
+
return sheet is not vd.cmdlog and not isinstance(sheet, (vd.OptionsSheet, ErrorSheet))
|
88
83
|
|
89
84
|
|
90
85
|
@Sheet.api
|
@@ -94,13 +89,7 @@ def moveToRow(vs, rowstr):
|
|
94
89
|
if rowidx is None:
|
95
90
|
return False
|
96
91
|
|
97
|
-
|
98
|
-
while vs.cursorRowIndex != rowidx:
|
99
|
-
vs.cursorRowIndex += 1 if (rowidx - vs.cursorRowIndex) > 0 else -1
|
100
|
-
while not vd.delay(0.5):
|
101
|
-
pass
|
102
|
-
else:
|
103
|
-
vs.cursorRowIndex = rowidx
|
92
|
+
vs.cursorRowIndex = rowidx
|
104
93
|
|
105
94
|
return True
|
106
95
|
|
@@ -126,13 +115,7 @@ def moveToCol(vs, col):
|
|
126
115
|
if vcolidx is None or vcolidx >= vs.nVisibleCols:
|
127
116
|
return False
|
128
117
|
|
129
|
-
|
130
|
-
while vs.cursorVisibleColIndex != vcolidx:
|
131
|
-
vs.cursorVisibleColIndex += 1 if (vcolidx - vs.cursorVisibleColIndex) > 0 else -1
|
132
|
-
while not vd.delay(0.5):
|
133
|
-
pass
|
134
|
-
else:
|
135
|
-
vs.cursorVisibleColIndex = vcolidx
|
118
|
+
vs.cursorVisibleColIndex = vcolidx
|
136
119
|
|
137
120
|
return True
|
138
121
|
|
@@ -147,7 +130,10 @@ def commandCursor(sheet, execstr):
|
|
147
130
|
rowname = keystr(k) if k else sheet.cursorRowIndex
|
148
131
|
|
149
132
|
if contains(execstr, 'cursorTypedValue', 'cursorDisplay', 'cursorValue', 'cursorCell', 'cursorCol', 'cursorVisibleCol', 'ColumnAtCursor'):
|
150
|
-
|
133
|
+
if sheet.cursorCol:
|
134
|
+
colname = sheet.cursorCol.name or sheet.visibleCols.index(sheet.cursorCol)
|
135
|
+
else:
|
136
|
+
colname = None
|
151
137
|
return colname, rowname
|
152
138
|
|
153
139
|
|
@@ -156,7 +142,7 @@ class CommandLogBase:
|
|
156
142
|
'Log of commands for current session.'
|
157
143
|
rowtype = 'logged commands'
|
158
144
|
precious = False
|
159
|
-
_rowtype =
|
145
|
+
_rowtype = CommandLogRow
|
160
146
|
columns = [
|
161
147
|
ColumnAttr('sheet'),
|
162
148
|
ColumnAttr('col'),
|
@@ -185,7 +171,7 @@ class CommandLogBase:
|
|
185
171
|
|
186
172
|
contains = lambda s, *substrs: any((a in s) for a in substrs)
|
187
173
|
if contains(cmd.execstr, 'pasteFromClipboard'):
|
188
|
-
args = vd.
|
174
|
+
args = vd.sysclipValue().strip()
|
189
175
|
|
190
176
|
|
191
177
|
comment = vd.currentReplayRow.comment if vd.currentReplayRow else cmd.helpstr
|
@@ -211,18 +197,10 @@ class CommandLogBase:
|
|
211
197
|
return
|
212
198
|
|
213
199
|
# remove user-aborted commands and simple movements (unless first command on the sheet, which created the sheet)
|
214
|
-
if not sheet.
|
200
|
+
if not sheet.cmdlog_sheet.rows or vd.isLoggableCommand(vd.activeCommand.longname):
|
215
201
|
if isLoggableSheet(sheet): # don't record actions from cmdlog or other internal sheets on global cmdlog
|
216
202
|
self.addRow(vd.activeCommand) # add to global cmdlog
|
217
203
|
sheet.cmdlog_sheet.addRow(vd.activeCommand) # add to sheet-specific cmdlog
|
218
|
-
if options.cmdlog_histfile:
|
219
|
-
name = date().strftime(options.cmdlog_histfile)
|
220
|
-
p = Path(name)
|
221
|
-
if not p.is_absolute():
|
222
|
-
p = Path(options.visidata_dir)/f'{name}.jsonl'
|
223
|
-
if not getattr(vd, 'sessionlog', None):
|
224
|
-
vd.sessionlog = vd.loadInternalSheet(CommandLog, p)
|
225
|
-
vd.sessionlog.append_tsv_row(vd.activeCommand)
|
226
204
|
|
227
205
|
vd.activeCommand = None
|
228
206
|
|
@@ -256,49 +234,27 @@ class CommandLogJsonl(CommandLogBase, JsonLinesSheet):
|
|
256
234
|
vd.paused = False
|
257
235
|
vd.currentReplay = None # CommandLog replaying currently
|
258
236
|
vd.currentReplayRow = None # must be global, to allow replay
|
259
|
-
vd.semaphore = threading.Semaphore(0)
|
260
|
-
|
261
|
-
|
262
|
-
@VisiData.api
|
263
|
-
def replay_pause(vd):
|
264
|
-
if not vd.currentReplay:
|
265
|
-
vd.fail('no replay to pause')
|
266
|
-
else:
|
267
|
-
if vd.paused:
|
268
|
-
vd.replay_advance()
|
269
|
-
vd.paused = not vd.paused
|
270
|
-
vd.status('paused' if vd.paused else 'resumed')
|
271
|
-
|
272
|
-
|
273
|
-
@VisiData.api
|
274
|
-
def replay_advance(vd):
|
275
|
-
vd.currentReplay or vd.fail("no replay to advance")
|
276
|
-
vd.semaphore.release()
|
277
237
|
|
278
238
|
|
279
239
|
@VisiData.api
|
280
240
|
def replay_cancel(vd):
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
vd.semaphore.release()
|
241
|
+
vd.currentReplayRow = None
|
242
|
+
vd.currentReplay = None
|
243
|
+
vd._nextCommands.clear()
|
285
244
|
|
286
245
|
|
287
246
|
@VisiData.api
|
288
247
|
def moveToReplayContext(vd, r, vs):
|
289
248
|
'set the sheet/row/col to the values in the replay row'
|
249
|
+
vs.ensureLoaded()
|
250
|
+
vd.sync()
|
251
|
+
vd.clearCaches()
|
252
|
+
|
290
253
|
if r.row not in [None, '']:
|
291
|
-
vs.moveToRow(r.row) or vd.error('no "
|
254
|
+
vs.moveToRow(r.row) or vd.error(f'no "{r.row}" row on {vs}')
|
292
255
|
|
293
256
|
if r.col not in [None, '']:
|
294
|
-
vs.moveToCol(r.col) or vd.error('no "
|
295
|
-
|
296
|
-
|
297
|
-
@VisiData.api
|
298
|
-
def delay(vd, factor=1):
|
299
|
-
'returns True if delay satisfied'
|
300
|
-
acquired = vd.semaphore.acquire(timeout=options.replay_wait*factor if not vd.paused else None)
|
301
|
-
return acquired or not vd.paused
|
257
|
+
vs.moveToCol(r.col) or vd.error(f'no "{r.col}" column on {vs}')
|
302
258
|
|
303
259
|
|
304
260
|
@VisiData.api
|
@@ -329,18 +285,21 @@ def replayOne(vd, r):
|
|
329
285
|
else:
|
330
286
|
vs = vs or vd.activeSheet
|
331
287
|
if vs:
|
332
|
-
vd.push
|
288
|
+
if vs in vd.sheets: # if already on sheet stack, push to top
|
289
|
+
vd.push(vs)
|
333
290
|
else:
|
334
291
|
vs = vd.cmdlog
|
335
292
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
293
|
+
try:
|
294
|
+
vd.moveToReplayContext(r, vs)
|
295
|
+
if r.comment:
|
296
|
+
vd.status(r.comment)
|
340
297
|
|
341
|
-
|
342
|
-
|
343
|
-
|
298
|
+
# <=v1.2 used keystrokes in longname column; getCommand fetches both
|
299
|
+
escaped = vs.execCommand(longname if longname else r.keystrokes, keystrokes=r.keystrokes)
|
300
|
+
except Exception as e:
|
301
|
+
vd.exceptionCaught(e)
|
302
|
+
escaped = True
|
344
303
|
|
345
304
|
vd.currentReplayRow = None
|
346
305
|
|
@@ -350,10 +309,21 @@ def replayOne(vd, r):
|
|
350
309
|
|
351
310
|
|
352
311
|
@VisiData.api
|
353
|
-
|
354
|
-
|
312
|
+
class DisableAsync:
|
313
|
+
def __enter__(self):
|
314
|
+
vd.execAsync = vd.execSync
|
315
|
+
|
316
|
+
def __exit__(self, exc_type, exc_val, tb):
|
317
|
+
vd.execAsync = lambda *args, vd=vd, **kwargs: visidata.VisiData.execAsync(vd, *args, **kwargs)
|
318
|
+
|
319
|
+
|
320
|
+
@VisiData.api
|
321
|
+
def replay_sync(vd, cmdlog):
|
322
|
+
'Replay all commands in *cmdlog*.'
|
323
|
+
with vd.DisableAsync():
|
355
324
|
cmdlog.cursorRowIndex = 0
|
356
325
|
vd.currentReplay = cmdlog
|
326
|
+
|
357
327
|
with Progress(total=len(cmdlog.rows)) as prog:
|
358
328
|
while cmdlog.cursorRowIndex < len(cmdlog.rows):
|
359
329
|
if vd.currentReplay is None:
|
@@ -376,22 +346,16 @@ def replay_sync(vd, cmdlog, live=False):
|
|
376
346
|
|
377
347
|
if vd.activeSheet:
|
378
348
|
vd.activeSheet.ensureLoaded()
|
379
|
-
vd.sync()
|
380
|
-
while not vd.delay():
|
381
|
-
pass
|
382
349
|
|
383
350
|
vd.status('replay complete')
|
384
351
|
vd.currentReplay = None
|
385
352
|
|
386
353
|
|
387
354
|
@VisiData.api
|
388
|
-
@asyncthread
|
389
355
|
def replay(vd, cmdlog):
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
thread.noblock = True
|
394
|
-
vd.replay_sync(cmdlog, live=True)
|
356
|
+
'Inject commands into live execution with interface.'
|
357
|
+
vd.push(cmdlog)
|
358
|
+
vd._nextCommands.extend(cmdlog.rows)
|
395
359
|
|
396
360
|
|
397
361
|
@VisiData.api
|
@@ -413,8 +377,9 @@ def setLastArgs(vd, args):
|
|
413
377
|
|
414
378
|
@VisiData.property
|
415
379
|
def replayStatus(vd):
|
416
|
-
|
417
|
-
return '
|
380
|
+
if vd._nextCommands:
|
381
|
+
return f' | [:status_replay] {len(vd._nextCommands)} {vd.options.disp_replay_play}[:]'
|
382
|
+
return ''
|
418
383
|
|
419
384
|
|
420
385
|
@BaseSheet.property
|
@@ -427,7 +392,14 @@ def cmdlog(sheet):
|
|
427
392
|
|
428
393
|
@BaseSheet.lazy_property
|
429
394
|
def cmdlog_sheet(sheet):
|
430
|
-
|
395
|
+
c = CommandLogJsonl(sheet.name+'_cmdlog', source=sheet, rows=[])
|
396
|
+
# copy over all existing globally set options
|
397
|
+
# you only need to do this for the first BaseSheet in a tree
|
398
|
+
if not isinstance(sheet.source, BaseSheet):
|
399
|
+
for r in vd.cmdlog.rows:
|
400
|
+
if r.sheet == 'global' and (r.longname == 'set-option') or (r.longname == 'unset-option'):
|
401
|
+
c.addRow(r)
|
402
|
+
return c
|
431
403
|
|
432
404
|
|
433
405
|
@BaseSheet.property
|
@@ -451,7 +423,7 @@ def shortcut(self):
|
|
451
423
|
def cmdlog(vd):
|
452
424
|
if not vd._cmdlog:
|
453
425
|
vd._cmdlog = CommandLogJsonl('cmdlog', rows=[]) # no reload
|
454
|
-
vd._cmdlog.
|
426
|
+
vd._cmdlog.resetCols()
|
455
427
|
vd.beforeExecHooks.append(vd._cmdlog.beforeExecHook)
|
456
428
|
return vd._cmdlog
|
457
429
|
|
@@ -488,27 +460,30 @@ BaseSheet.init('_shortcut')
|
|
488
460
|
globalCommand('gD', 'cmdlog-all', 'vd.push(vd.cmdlog)', 'open global CommandLog for all commands executed in current session')
|
489
461
|
globalCommand('D', 'cmdlog-sheet', 'vd.push(sheet.cmdlog)', "open current sheet's CommandLog with all other loose ends removed; includes commands from parent sheets")
|
490
462
|
globalCommand('zD', 'cmdlog-sheet-only', 'vd.push(sheet.cmdlog_sheet)', 'open CommandLog for current sheet with commands from parent sheets removed')
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
globalCommand('^K', 'replay-stop', 'vd.replay_cancel()', 'cancel current replay')
|
463
|
+
BaseSheet.addCommand('^D', 'save-cmdlog', 'saveSheets(inputPath("save cmdlog to: ", value=fnSuffix(name)), vd.cmdlog)', 'save CommandLog to filename.vdj file')
|
464
|
+
BaseSheet.bindkey('^N', 'no-op')
|
465
|
+
BaseSheet.addCommand('^K', 'replay-stop', 'vd.replay_cancel(); vd.warning("replay canceled")', 'cancel current replay')
|
495
466
|
|
496
467
|
globalCommand(None, 'show-status', 'status(input("status: "))', 'show given message on status line')
|
497
468
|
globalCommand('^V', 'show-version', 'status(__version_info__);', 'Show version and copyright information on status line')
|
498
469
|
globalCommand('z^V', 'check-version', 'checkVersion(input("require version: ", value=__version_info__))', 'check VisiData version against given version')
|
499
470
|
|
500
|
-
globalCommand(' ', 'exec-longname', 'exec_longname(inputLongname())', 'execute command by its longname')
|
501
|
-
|
502
471
|
CommandLog.addCommand('x', 'replay-row', 'vd.replayOne(cursorRow); status("replayed one row")', 'replay command in current row')
|
503
472
|
CommandLog.addCommand('gx', 'replay-all', 'vd.replay(sheet)', 'replay contents of entire CommandLog')
|
504
|
-
CommandLog.addCommand('^C', 'replay-stop', 'sheet.cursorRowIndex = sheet.nRows', 'abort replay')
|
505
473
|
|
506
474
|
CommandLogJsonl.addCommand('x', 'replay-row', 'vd.replayOne(cursorRow); status("replayed one row")', 'replay command in current row')
|
507
475
|
CommandLogJsonl.addCommand('gx', 'replay-all', 'vd.replay(sheet)', 'replay contents of entire CommandLog')
|
508
|
-
CommandLogJsonl.addCommand('^C', 'replay-stop', 'sheet.cursorRowIndex = sheet.nRows', 'abort replay')
|
509
476
|
|
510
477
|
CommandLog.options.json_sort_keys = False
|
511
478
|
CommandLog.options.encoding = 'utf-8'
|
512
479
|
CommandLogJsonl.options.json_sort_keys = False
|
513
480
|
|
514
|
-
vd.addGlobals(
|
481
|
+
vd.addGlobals(CommandLogBase=CommandLogBase, CommandLogRow=CommandLogRow)
|
482
|
+
|
483
|
+
vd.addMenuItems('''
|
484
|
+
View > Command log > this sheet > cmdlog-sheet
|
485
|
+
View > Command log > this sheet only > cmdlog-sheet-only
|
486
|
+
View > Command log > all commands > cmdlog-all
|
487
|
+
System > Execute longname > exec-longname
|
488
|
+
Help > Version > show-version
|
489
|
+
''')
|
visidata/color.py
CHANGED
@@ -1,45 +1,83 @@
|
|
1
1
|
import curses
|
2
2
|
import functools
|
3
3
|
from copy import copy
|
4
|
+
from collections import namedtuple
|
5
|
+
from dataclasses import dataclass
|
4
6
|
|
5
|
-
from visidata import options, Extensible, drawcache, drawcache_property, VisiData
|
7
|
+
from visidata import vd, options, Extensible, drawcache, drawcache_property, VisiData
|
6
8
|
import visidata
|
7
|
-
from collections import namedtuple
|
8
9
|
|
9
|
-
__all__ = ['ColorAttr', 'colors', 'update_attr', 'ColorMaker']
|
10
|
+
__all__ = ['ColorAttr', 'colors', 'update_attr', 'ColorMaker', 'rgb_to_attr']
|
10
11
|
|
12
|
+
vd.help_color = '''Color syntax: `<attribute> <fg-color> on <bg-color>`
|
11
13
|
|
12
|
-
|
14
|
+
- attributes: [:bold]bold[/] [:underline]underline[/] [:italic]italic[/] [:reverse]reverse[/]
|
15
|
+
- colors: 0-255 or [:black on 238]black[/] [:red on 238]red[/] [:green on 238]green[/] [:yellow on 238]yellow[/] [:blue on 238]blue[/] [:magenta on 238]magenta[/] [:cyan on 238]cyan[/] [:white on 238]white[/]
|
16
|
+
- the second color is used as a fallback if the first color is not available
|
13
17
|
|
18
|
+
See [:onclick https://visidata.org/docs/colors]https://visidata.org/docs/colors[/] for more detailed info.
|
19
|
+
'''
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class ColorAttr:
|
24
|
+
fg:int = -1 # default is no foreground specified
|
25
|
+
bg:int = -1 # default is no background specified
|
26
|
+
attributes:int = 0 # default is no attributes
|
27
|
+
precedence:int = 0 # default is lowest priority
|
28
|
+
colorname:str = ''
|
29
|
+
|
30
|
+
def __init__(self, fg:int=-1, bg:int=-1, attributes:int=0, precedence:int=0, colorname:str=''):
|
31
|
+
assert fg < 256, fg
|
32
|
+
assert bg < 256, bg
|
33
|
+
self.fg = fg
|
34
|
+
self.bg = bg
|
35
|
+
self.attributes = attributes
|
36
|
+
self.precedence = precedence
|
37
|
+
self.colorname = colorname
|
38
|
+
|
39
|
+
def update(self, b:'ColorAttr', prec:int=10) -> 'ColorAttr':
|
40
|
+
return update_attr(self, b, prec)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def attr(self) -> int:
|
44
|
+
a = colors._get_colorpair(self.fg, self.bg, self.colorname) | self.attributes
|
45
|
+
assert a >= 0, a
|
46
|
+
return a
|
47
|
+
|
48
|
+
def as_str(self) -> str:
|
49
|
+
attrnames = [attrname for attrname, attr in colors._attrs.items() if self.attributes & attr]
|
50
|
+
attrnames.append(f'{self.fg} on {self.bg}')
|
51
|
+
return ' '.join(attrnames)
|
52
|
+
|
53
|
+
|
54
|
+
def update_attr(oldattr:ColorAttr, updattr:ColorAttr, updprec:int=None) -> ColorAttr:
|
55
|
+
assert isinstance(updattr, ColorAttr), updattr
|
56
|
+
if updprec is None:
|
57
|
+
updprec = updattr.precedence
|
58
|
+
updfg = updattr.fg
|
59
|
+
updbg = updattr.bg
|
60
|
+
updattr = updattr.attributes
|
26
61
|
|
27
62
|
# starting values, work backwards
|
28
|
-
|
63
|
+
newfg = oldattr.fg
|
64
|
+
newbg = oldattr.bg
|
29
65
|
newattr = oldattr.attributes | updattr
|
30
66
|
newprec = oldattr.precedence
|
31
67
|
|
32
|
-
if
|
33
|
-
|
34
|
-
|
35
|
-
|
68
|
+
if newfg < 0 or (updfg >= 0 and updprec > newprec):
|
69
|
+
newfg = updfg
|
70
|
+
if newbg < 0 or (updbg >= 0 and updprec > newprec):
|
71
|
+
newbg = updbg
|
36
72
|
|
37
|
-
|
73
|
+
newprec = max(updprec, newprec)
|
74
|
+
|
75
|
+
return ColorAttr(newfg, newbg, newattr, newprec)
|
38
76
|
|
39
77
|
|
40
78
|
class ColorMaker:
|
41
79
|
def __init__(self):
|
42
|
-
self.color_pairs = {} # (fg,bg) -> (
|
80
|
+
self.color_pairs = {} # (fg,bg) -> (pairnum, colornamestr) (pairnum can be or'ed with other attrs)
|
43
81
|
self.color_cache = {} # colorname -> colorpair
|
44
82
|
|
45
83
|
@drawcache_property
|
@@ -47,30 +85,44 @@ class ColorMaker:
|
|
47
85
|
return {}
|
48
86
|
|
49
87
|
def setup(self):
|
50
|
-
|
88
|
+
try:
|
89
|
+
curses.use_default_colors()
|
90
|
+
except Exception as e:
|
91
|
+
pass
|
51
92
|
|
52
93
|
@drawcache_property
|
53
94
|
def colors(self):
|
54
95
|
'not computed until curses color has been initialized'
|
55
96
|
return {x[6:]:getattr(curses, x) for x in dir(curses) if x.startswith('COLOR_') and x != 'COLOR_PAIRS'}
|
56
97
|
|
57
|
-
def __getitem__(self,
|
58
|
-
|
98
|
+
def __getitem__(self, colorname:str) -> ColorAttr:
|
99
|
+
'colors["green"] or colors["foo"] returns parsed ColorAttr.'
|
100
|
+
return self.get_color(colorname)
|
59
101
|
|
60
|
-
def __getattr__(self, optname):
|
61
|
-
'colors.color_foo
|
62
|
-
return self.get_color(optname)
|
102
|
+
def __getattr__(self, optname) -> ColorAttr:
|
103
|
+
'colors.color_foo or colors.foo returns parsed ColorAttr.'
|
104
|
+
return self.get_color(optname)
|
63
105
|
|
64
106
|
@drawcache
|
65
107
|
def resolve_colors(self, colorstack):
|
66
108
|
'Returns the ColorAttr for the colorstack, a list of (prec, color_option_name) sorted highest-precedence color first.'
|
67
|
-
cattr = ColorAttr(
|
109
|
+
cattr = ColorAttr()
|
68
110
|
for prec, coloropt in colorstack:
|
69
111
|
c = self.get_color(coloropt)
|
70
112
|
cattr = update_attr(cattr, c, prec)
|
71
113
|
return cattr
|
72
114
|
|
73
|
-
def
|
115
|
+
def get_color(self, optname:str, precedence:int=0) -> ColorAttr:
|
116
|
+
'''Return ColorAttr for options.color_foo if *optname* of either "foo" or "color_foo",
|
117
|
+
Otherwise parse *optname* for colorstring like "bold 34 red on 135 blue".'''
|
118
|
+
r = self.colorcache.get(optname, None)
|
119
|
+
if r is None:
|
120
|
+
coloropt = vd.options._get(optname) or vd.options._get(f'color_{optname}')
|
121
|
+
colornamestr = coloropt.value if coloropt else optname
|
122
|
+
r = self.colorcache[optname] = self._colornames_to_cattr(colornamestr, precedence)
|
123
|
+
return r
|
124
|
+
|
125
|
+
def _split_colorstr(self, colorstr):
|
74
126
|
'Return (fgstr, bgstr, attrlist) parsed from colorstr.'
|
75
127
|
fgbgattrs = ['', '', []] # fgstr, bgstr, attrlist
|
76
128
|
if not colorstr:
|
@@ -92,12 +144,19 @@ class ColorMaker:
|
|
92
144
|
if not fgbgattrs[i]: # keep first known color
|
93
145
|
if self._get_colornum(x) is not None: # only set known colors
|
94
146
|
fgbgattrs[i] = x
|
147
|
+
else:
|
148
|
+
fgbgattrs[i] = None
|
95
149
|
|
96
150
|
return fgbgattrs
|
97
151
|
|
98
|
-
def _get_colornum(self, colorname, default=-1):
|
152
|
+
def _get_colornum(self, colorname:'str|int', default:int=-1) -> int:
|
99
153
|
'Return terminal color number for colorname.'
|
100
|
-
if
|
154
|
+
if isinstance(colorname, int):
|
155
|
+
return colorname
|
156
|
+
|
157
|
+
if not colorname:
|
158
|
+
return default
|
159
|
+
|
101
160
|
r = self.color_cache.get(colorname, None)
|
102
161
|
if r is not None:
|
103
162
|
return r
|
@@ -119,41 +178,68 @@ class ColorMaker:
|
|
119
178
|
except ValueError: # Python 3.10+ issue #1227
|
120
179
|
return None
|
121
180
|
|
122
|
-
|
123
|
-
def _colornames_to_cattr(self, colornamestr, precedence=0):
|
124
|
-
fg, bg, attrlist = self.split_colorstr(colornamestr)
|
181
|
+
def _attrnames_to_num(self, attrnames:'list[str]') -> int:
|
125
182
|
attrs = 0
|
126
|
-
for attr in
|
183
|
+
for attr in attrnames:
|
127
184
|
attrs |= getattr(curses, 'A_'+attr.upper())
|
185
|
+
return attrs
|
128
186
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
187
|
+
@drawcache_property
|
188
|
+
def _attrs(self):
|
189
|
+
return {k[2:].lower():getattr(curses, k) for k in dir(curses) if k.startswith('A_') and k != 'A_ATTRIBUTES'}
|
190
|
+
|
191
|
+
@drawcache
|
192
|
+
def _colornames_to_cattr(self, colorname:str, precedence=0) -> ColorAttr:
|
193
|
+
fg, bg, attrlist = self._split_colorstr(colorname)
|
194
|
+
|
195
|
+
fg = self._get_colornum(fg)
|
196
|
+
bg = self._get_colornum(bg)
|
197
|
+
return ColorAttr(fg, bg,
|
198
|
+
self._attrnames_to_num(attrlist),
|
199
|
+
precedence, colorname)
|
200
|
+
|
201
|
+
def _get_colorpair(self, fg:'int|None', bg:'int|None', colorname:str) -> int:
|
202
|
+
pairnum, _ = self.color_pairs.get((fg, bg), (None, ''))
|
136
203
|
if pairnum is None:
|
137
204
|
if len(self.color_pairs) > 254:
|
138
205
|
self.color_pairs.clear() # start over
|
139
206
|
self.color_cache.clear()
|
140
207
|
pairnum = len(self.color_pairs)+1
|
208
|
+
if fg is None: fg = -1
|
209
|
+
if bg is None: bg = -1
|
141
210
|
try:
|
142
|
-
curses.init_pair(pairnum,
|
211
|
+
curses.init_pair(pairnum, fg, bg)
|
143
212
|
except curses.error as e:
|
144
|
-
return
|
145
|
-
self.color_pairs[
|
213
|
+
return 0 # do not cache
|
214
|
+
self.color_pairs[(fg, bg)] = (pairnum, colorname)
|
146
215
|
|
147
|
-
|
148
|
-
return ColorAttr(color, attrs, precedence, color | attrs)
|
216
|
+
return curses.color_pair(pairnum)
|
149
217
|
|
150
|
-
def get_color(self, optname, precedence=0):
|
151
|
-
'colors.color_foo returns colors[options.color_foo]'
|
152
|
-
r = self.colorcache.get(optname, None)
|
153
|
-
if r is None:
|
154
|
-
coloropt = options._get(optname)
|
155
|
-
colornamestr = coloropt.value if coloropt else optname
|
156
|
-
r = self.colorcache[optname] = self._colornames_to_cattr(colornamestr, precedence)
|
157
|
-
return r
|
158
218
|
|
159
219
|
colors = ColorMaker()
|
220
|
+
|
221
|
+
def rgb_to_xterm256(r:int,g:int,b:int,a:int=255) -> int:
|
222
|
+
if a == 0:
|
223
|
+
return -1
|
224
|
+
|
225
|
+
if max(r,g,b) - min(r,g,b) < 8:
|
226
|
+
if r <= 4: return 16
|
227
|
+
elif r <= 8: return 232
|
228
|
+
elif r >= 247: return 231
|
229
|
+
elif r >= 238: return 255
|
230
|
+
else:
|
231
|
+
return int(232 + (r-8)//10)
|
232
|
+
else:
|
233
|
+
r = max(0, r-(95-40)) // 40
|
234
|
+
g = max(0, g-(95-40)) // 40
|
235
|
+
b = max(0, b-(95-40)) // 40
|
236
|
+
return int(16 + r*36 + g*6 + b)
|
237
|
+
|
238
|
+
|
239
|
+
@functools.lru_cache(256)
|
240
|
+
def rgb_to_attr(r:int,g:int,b:int,a:int=255) -> str:
|
241
|
+
return str(rgb_to_xterm256(r,g,b,a))
|
242
|
+
|
243
|
+
|
244
|
+
import sys
|
245
|
+
vd.addGlobals({k:getattr(sys.modules[__name__], k) for k in __all__})
|