visidata 2.11.1__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 +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- 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
- {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 +59 -50
- 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 +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 +30 -5
- 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} +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} +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 +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 +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/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 +301 -148
- visidata/man/vd.txt +290 -153
- 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 +50 -201
- 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 +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 +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.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
- {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.1.dist-info → visidata-3.0.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.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/movement.py
CHANGED
@@ -6,6 +6,7 @@ from visidata import vd, VisiData, BaseSheet, Sheet, Column, Progress, ALT, asyn
|
|
6
6
|
|
7
7
|
def rotateRange(n, idx, reverse=False):
|
8
8
|
'Wraps an iter starting from idx. Yields indices from idx to n and then 0 to idx.'
|
9
|
+
if n == 0: return []
|
9
10
|
if reverse:
|
10
11
|
rng = range(idx-1, -1, -1)
|
11
12
|
rng2 = range(n-1, idx-1, -1)
|
@@ -96,47 +97,35 @@ def nextColRegex(sheet, colregex):
|
|
96
97
|
def visibleWidth(self):
|
97
98
|
'Width of column as is displayed in terminal'
|
98
99
|
vcolidx = self.sheet.visibleCols.index(self)
|
100
|
+
if vcolidx not in self.sheet._visibleColLayout:
|
101
|
+
self.sheet.calcSingleColLayout(vcolidx)
|
99
102
|
return self.sheet._visibleColLayout[vcolidx][1]
|
100
103
|
|
101
104
|
|
102
|
-
Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left')
|
103
|
-
Sheet.addCommand(None, 'go-down', 'cursorDown(+1)', 'go down')
|
104
|
-
Sheet.addCommand(None, 'go-up', 'cursorDown(-1)', 'go up')
|
105
|
-
Sheet.addCommand(None, 'go-right', 'cursorRight(+1)', 'go right')
|
106
|
-
Sheet.addCommand(None, 'go-pagedown', 'cursorDown(nScreenRows-1); sheet.topRowIndex = bottomRowIndex', 'scroll one page forward')
|
107
|
-
Sheet.addCommand(None, 'go-pageup', 'cursorDown(-nScreenRows+1); sheet.bottomRowIndex = topRowIndex', 'scroll one page backward')
|
105
|
+
Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left')
|
106
|
+
Sheet.addCommand(None, 'go-down', 'cursorDown(+1)', 'go down')
|
107
|
+
Sheet.addCommand(None, 'go-up', 'cursorDown(-1)', 'go up')
|
108
|
+
Sheet.addCommand(None, 'go-right', 'cursorRight(+1)', 'go right')
|
109
|
+
Sheet.addCommand(None, 'go-pagedown', 'cursorDown(nScreenRows-1); sheet.topRowIndex = bottomRowIndex', 'scroll one page forward')
|
110
|
+
Sheet.addCommand(None, 'go-pageup', 'cursorDown(-nScreenRows+1); sheet.bottomRowIndex = topRowIndex', 'scroll one page backward')
|
108
111
|
|
109
|
-
Sheet.addCommand(None, 'go-leftmost', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = 0', 'go all the way to the left of sheet')
|
110
|
-
Sheet.addCommand(None, 'go-top', 'sheet.cursorRowIndex = sheet.topRowIndex = 0', 'go all the way to the top of sheet')
|
111
|
-
Sheet.addCommand(None, 'go-bottom', 'sheet.cursorRowIndex = len(rows); sheet.topRowIndex = cursorRowIndex-nScreenRows', 'go all the way to the bottom of sheet')
|
112
|
-
Sheet.addCommand(None, 'go-rightmost', 'sheet.leftVisibleColIndex = len(visibleCols)-1; pageLeft(); sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go all the way to the right of sheet')
|
112
|
+
Sheet.addCommand(None, 'go-leftmost', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = 0', 'go all the way to the left of sheet')
|
113
|
+
Sheet.addCommand(None, 'go-top', 'sheet.cursorRowIndex = sheet.topRowIndex = 0', 'go all the way to the top of sheet')
|
114
|
+
Sheet.addCommand(None, 'go-bottom', 'sheet.cursorRowIndex = len(rows); sheet.topRowIndex = cursorRowIndex-nScreenRows', 'go all the way to the bottom of sheet')
|
115
|
+
Sheet.addCommand(None, 'go-rightmost', 'sheet.leftVisibleColIndex = len(visibleCols)-1; pageLeft(); sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go all the way to the right of sheet')
|
113
116
|
|
114
|
-
|
115
|
-
def go_mouse(sheet):
|
116
|
-
ridx = sheet.visibleRowAtY(sheet.mouseY)
|
117
|
-
if ridx is not None:
|
118
|
-
sheet.cursorRowIndex = ridx
|
119
|
-
cidx = sheet.visibleColAtX(sheet.mouseX)
|
120
|
-
if cidx is not None:
|
121
|
-
sheet.cursorVisibleColIndex = cidx
|
122
|
-
|
123
|
-
Sheet.addCommand(None, 'scroll-mouse', 'sheet.topRowIndex=cursorRowIndex-mouseY+1', 'scroll to mouse cursor location'),
|
124
|
-
|
125
|
-
Sheet.addCommand('ScrollwheelUp', 'scroll-up', 'cursorDown(options.scroll_incr); sheet.topRowIndex += options.scroll_incr', 'scroll one row up'),
|
126
|
-
Sheet.addCommand('ScrollwheelDown', 'scroll-down', 'cursorDown(-options.scroll_incr); sheet.topRowIndex -= options.scroll_incr', 'scroll one row down'),
|
127
|
-
|
128
|
-
Sheet.addCommand('c', 'go-col-regex', 'sheet.cursorVisibleColIndex=nextColRegex(input("column name regex: ", type="regex-col", defaultLast=True))', 'go to next column with name matching regex')
|
117
|
+
Sheet.addCommand('c', 'go-col-regex', 'sheet.cursorVisibleColIndex=nextColRegex(inputRegex("column name regex: ", type="regex-col", defaultLast=True))', 'go to next column with name matching regex')
|
129
118
|
Sheet.addCommand('zc', 'go-col-number', 'sheet.cursorVisibleColIndex = int(input("move to column number: "))', 'go to given column number (0-based)')
|
130
119
|
Sheet.addCommand('zr', 'go-row-number', 'sheet.cursorRowIndex = int(input("move to row number: "))', 'go to the given row number (0-based)')
|
131
120
|
|
132
121
|
|
133
|
-
Sheet.addCommand('<', 'go-prev-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, reverse=True, msg="no different value up this column")', 'go up current column to next value')
|
134
|
-
Sheet.addCommand('>', 'go-next-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, msg="no different value down this column")', 'go down current column to next value')
|
135
|
-
Sheet.addCommand('{', 'go-prev-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), reverse=True, msg="no previous selected row")', 'go up current column to previous selected row')
|
136
|
-
Sheet.addCommand('}', 'go-next-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), msg="no next selected row") ', 'go down current column to next selected row')
|
122
|
+
Sheet.addCommand('<', 'go-prev-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, reverse=True, msg="no different value up this column")', 'go up current column to next value')
|
123
|
+
Sheet.addCommand('>', 'go-next-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, msg="no different value down this column")', 'go down current column to next value')
|
124
|
+
Sheet.addCommand('{', 'go-prev-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), reverse=True, msg="no previous selected row")', 'go up current column to previous selected row')
|
125
|
+
Sheet.addCommand('}', 'go-next-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), msg="no next selected row") ', 'go down current column to next selected row')
|
137
126
|
|
138
|
-
Sheet.addCommand('z<', 'go-prev-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), reverse=True, msg="no null up this column")', 'go up current column to next null value')
|
139
|
-
Sheet.addCommand('z>', 'go-next-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), msg="no null down this column")', 'go down current column to next null value')
|
127
|
+
Sheet.addCommand('z<', 'go-prev-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), reverse=True, msg="no null up this column")', 'go up current column to next null value')
|
128
|
+
Sheet.addCommand('z>', 'go-next-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), msg="no null down this column")', 'go down current column to next null value')
|
140
129
|
|
141
130
|
for i in range(1, 11):
|
142
131
|
BaseSheet.addCommand(ALT+str(i)[-1], 'jump-sheet-'+str(i), f'vd.push(*(list(s for s in allSheets if s.shortcut==str({i})) or fail("no sheet")))', f'jump to sheet {i}')
|
@@ -181,9 +170,8 @@ Sheet.addCommand('gzk', 'scroll-cells-top', 'cursorCol.voffset = 0', 'scroll dis
|
|
181
170
|
Sheet.addCommand(None, 'go-end', 'sheet.cursorRowIndex = len(rows)-1; sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go to last row and last column')
|
182
171
|
Sheet.addCommand(None, 'go-home', 'sheet.topRowIndex = sheet.cursorRowIndex = 0; sheet.leftVisibleColIndex = sheet.cursorVisibleColIndex = 0', 'go to first row and first column')
|
183
172
|
|
184
|
-
BaseSheet.bindkey('
|
185
|
-
BaseSheet.bindkey('
|
186
|
-
BaseSheet.bindkey('CTRL-2097152', 'scroll-right')
|
173
|
+
BaseSheet.bindkey('Ctrl+ScrollUp', 'scroll-left')
|
174
|
+
BaseSheet.bindkey('Ctrl+ScrollDown', 'scroll-right')
|
187
175
|
|
188
176
|
BaseSheet.bindkey('zKEY_UP', 'scroll-up')
|
189
177
|
BaseSheet.bindkey('zKEY_DOWN', 'scroll-down')
|
@@ -208,10 +196,30 @@ BaseSheet.bindkey('gl', 'go-rightmost')
|
|
208
196
|
BaseSheet.addCommand('^^', 'jump-prev', 'vd.activeStack[1:] or fail("no previous sheet"); vd.push(vd.activeStack[1])', 'jump to previous sheet in this pane')
|
209
197
|
BaseSheet.addCommand('g^^', 'jump-first', 'vd.push(vd.activeStack[-1])', 'jump to first sheet')
|
210
198
|
|
211
|
-
BaseSheet.addCommand('BUTTON1_RELEASED', 'no-op', 'pass')
|
199
|
+
BaseSheet.addCommand('BUTTON1_RELEASED', 'no-op', 'pass', 'do nothing')
|
212
200
|
|
213
201
|
BaseSheet.addCommand(None, 'mouse-enable', 'mm, _ = curses.mousemask(-1); status("mouse "+("ON" if mm else "OFF"))', 'enable mouse events')
|
214
202
|
BaseSheet.addCommand(None, 'mouse-disable', 'mm, _ = curses.mousemask(0); status("mouse "+("ON" if mm else "OFF"))', 'disable mouse events')
|
215
203
|
|
216
204
|
|
217
205
|
vd.addGlobals({'rotateRange': rotateRange})
|
206
|
+
|
207
|
+
vd.addMenuItems('''
|
208
|
+
View > Other sheet > previous sheet > jump-prev
|
209
|
+
View > Other sheet > first sheet > jump-first
|
210
|
+
Column > Goto > by number > go-col-number
|
211
|
+
Column > Goto > by name > go-col-regex
|
212
|
+
Row > Goto > top > go-top
|
213
|
+
Row > Goto > bottom > go-bottom
|
214
|
+
Row > Goto > previous > page > go-pageup
|
215
|
+
Row > Goto > previous > null > go-prev-null
|
216
|
+
Row > Goto > previous > value > go-prev-value
|
217
|
+
Row > Goto > previous > selected > go-prev-selected
|
218
|
+
Row > Goto > next > page > go-pagedown
|
219
|
+
Row > Goto > next > null > go-next-null
|
220
|
+
Row > Goto > next > value > go-next-value
|
221
|
+
Row > Goto > next > selected > go-next-selected
|
222
|
+
Row > Goto > by number > go-row-number
|
223
|
+
View > Other sheet > previous sheet > jump-prev
|
224
|
+
View > Other sheet > first sheet > jump-first
|
225
|
+
''')
|
visidata/optionssheet.py
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
from visidata import vd, VisiData, BaseSheet, Sheet, Column, AttrColumn, CellColorizer, Option
|
2
|
+
|
3
|
+
|
4
|
+
@BaseSheet.lazy_property
|
5
|
+
def optionsSheet(sheet):
|
6
|
+
return OptionsSheet(sheet.name+"_options", source=sheet)
|
7
|
+
|
8
|
+
@VisiData.lazy_property
|
9
|
+
def globalOptionsSheet(vd):
|
10
|
+
return OptionsSheet('global_options', source='global')
|
11
|
+
|
12
|
+
|
13
|
+
@VisiData.api
|
14
|
+
class OptionsSheet(Sheet):
|
15
|
+
_rowtype = Option # rowdef: Option
|
16
|
+
rowtype = 'options'
|
17
|
+
precious = False
|
18
|
+
columns = (
|
19
|
+
Column('option', getter=lambda col,row: row.name),
|
20
|
+
Column('module', getter=lambda col,row: row.module, max_help=1),
|
21
|
+
Column('value',
|
22
|
+
getter=lambda col,row: col.sheet.diffOption(row.name),
|
23
|
+
setter=lambda col,row,val: col.sheet.source.options.set(row.name, val)
|
24
|
+
),
|
25
|
+
Column('default', getter=lambda col,row: vd.options.getdefault(row.name)),
|
26
|
+
Column('description', width=40, getter=lambda col,row: vd.options._get(row.name, 'default').helpstr),
|
27
|
+
AttrColumn('replayable', max_help=1),
|
28
|
+
)
|
29
|
+
colorizers = [
|
30
|
+
CellColorizer(3, None, lambda s,c,r,v: v.value if r and c in s.columns[2:4] and r.name.startswith('color_') else None),
|
31
|
+
]
|
32
|
+
nKeys = 2
|
33
|
+
|
34
|
+
@property
|
35
|
+
def guide(self):
|
36
|
+
if self.source == 'global':
|
37
|
+
r = '# Global Options\nThis is a list of global option settings.'
|
38
|
+
else:
|
39
|
+
r = '# Sheet Options\nThis is a list of option settings specifically for the current sheet.'
|
40
|
+
|
41
|
+
r += f'\n\n- `e` to edit/toggle the current option value'
|
42
|
+
r += '\n- `d` to restore option to builtin default'
|
43
|
+
return r
|
44
|
+
|
45
|
+
def diffOption(self, optname):
|
46
|
+
return vd.options.getonly(optname, self.source, '')
|
47
|
+
|
48
|
+
def editOption(self, row):
|
49
|
+
currentValue = vd.options.getobj(row.name, self.source)
|
50
|
+
vd.addUndo(vd.options.set, row.name, currentValue, self.source)
|
51
|
+
if isinstance(row.value, bool):
|
52
|
+
vd.options.set(row.name, not currentValue, self.source)
|
53
|
+
else:
|
54
|
+
helpstr = f'# options.{self.cursorRow.name}'
|
55
|
+
opt = vd.options._get(self.cursorRow.name, 'default')
|
56
|
+
if opt.helpstr:
|
57
|
+
x = getattr(vd, 'help_'+opt.helpstr, opt.helpstr or '')
|
58
|
+
helpstr += (x or '').strip()
|
59
|
+
if opt.extrahelp:
|
60
|
+
helpstr += '\n'+opt.extrahelp.strip()
|
61
|
+
valcolidx = self.visibleCols.index(self.column(self.valueColName))
|
62
|
+
v = self.editCell(valcolidx, value=currentValue, help=helpstr)
|
63
|
+
vd.options.set(row.name, v, self.source)
|
64
|
+
|
65
|
+
@property
|
66
|
+
def valueColName(self):
|
67
|
+
return 'global_value' if self.source == 'global' else 'sheet_value'
|
68
|
+
|
69
|
+
def beforeLoad(self):
|
70
|
+
super().beforeLoad()
|
71
|
+
self.columns[2].name = self.valueColName
|
72
|
+
|
73
|
+
def iterload(self):
|
74
|
+
for k in vd.options.keys():
|
75
|
+
v = vd.options._get(k)
|
76
|
+
# if vd.options.disp_help > v.max_help:
|
77
|
+
# continue
|
78
|
+
if v.sheettype in [None, BaseSheet]:
|
79
|
+
yield v
|
80
|
+
elif self.source != 'global' and v.sheettype in self.source.superclasses():
|
81
|
+
yield v
|
82
|
+
|
83
|
+
def newRow(self):
|
84
|
+
vd.fail('adding rows to the options sheet is not supported.')
|
85
|
+
|
86
|
+
|
87
|
+
BaseSheet.addCommand('O', 'options-global', 'vd.push(vd.globalOptionsSheet)', 'open Options Sheet: edit global options (apply to all sheets)')
|
88
|
+
|
89
|
+
BaseSheet.addCommand('zO', 'options-sheet', 'vd.push(sheet.optionsSheet)', 'open Options Sheet: edit sheet options (apply to current sheet only)')
|
90
|
+
|
91
|
+
OptionsSheet.addCommand('d', 'unset-option', 'options.unset(cursorRow.name, str(source))', 'remove option override for this context')
|
92
|
+
OptionsSheet.addCommand(None, 'edit-option', 'editOption(cursorRow)', 'edit option at current row')
|
93
|
+
OptionsSheet.bindkey('e', 'edit-option')
|
94
|
+
OptionsSheet.bindkey('Enter', 'edit-option')
|
95
|
+
|
96
|
+
vd.addMenuItems('''
|
97
|
+
File > Options > all sheets > options-global
|
98
|
+
File > Options > this sheet > options-sheet
|
99
|
+
''')
|
visidata/path.py
CHANGED
@@ -7,10 +7,46 @@ import pathlib
|
|
7
7
|
from urllib.parse import urlparse, urlunparse
|
8
8
|
from functools import wraps, lru_cache
|
9
9
|
|
10
|
-
from visidata import
|
10
|
+
from visidata import vd
|
11
|
+
from visidata import VisiData, Progress
|
11
12
|
|
12
|
-
vd.
|
13
|
-
|
13
|
+
vd.help_encoding = '''Common Encodings:
|
14
|
+
|
15
|
+
- `utf-8`: Unicode (ASCII compatible, most common)
|
16
|
+
- `utf-8-sig`: Unicode as above, but saves/skips leading BOM
|
17
|
+
- `ascii`: 7-bit ASCII
|
18
|
+
- `latin1`: also known as `iso-8859-1`
|
19
|
+
- `cp437`: original IBM PC character set
|
20
|
+
- `shift_jis`: Japanese
|
21
|
+
|
22
|
+
See [:onclick https://docs.python.org/3/library/codecs.html#standard-encodings]https://docs.python.org/3/library/codecs.html#standard-encodings[/]
|
23
|
+
'''
|
24
|
+
|
25
|
+
vd.help_encoding_errors = '''Encoding Error Handlers:
|
26
|
+
|
27
|
+
- `strict`: raise error
|
28
|
+
- `ignore`: discard
|
29
|
+
- `replace`: replacement marker
|
30
|
+
- `backslashreplace`: use "\\uxxxxxx"
|
31
|
+
- `surrogateescape`: use surrogate characters
|
32
|
+
|
33
|
+
See [:onclick https://docs.python.org/3/library/codecs.html#error-handlers]https://docs.python.org/3/library/codecs.html#error-handlers[/]
|
34
|
+
'''
|
35
|
+
|
36
|
+
vd.option('encoding', 'utf-8-sig', 'encoding passed to codecs.open when reading a file', replay=True, help=vd.help_encoding)
|
37
|
+
vd.option('encoding_errors', 'surrogateescape', 'encoding_errors passed to codecs.open', replay=True, help=vd.help_encoding_errors)
|
38
|
+
|
39
|
+
@VisiData.api
|
40
|
+
def pkg_resources_files(vd, package):
|
41
|
+
'''
|
42
|
+
Returns a Traversable object (Path-like), based on the location of the package.
|
43
|
+
importlib.resources.files exists in Python >= 3.9; use importlib_resources for the rest.
|
44
|
+
'''
|
45
|
+
try:
|
46
|
+
from importlib.resources import files
|
47
|
+
except ImportError: #1968
|
48
|
+
from importlib_resources import files
|
49
|
+
return files(package)
|
14
50
|
|
15
51
|
@lru_cache()
|
16
52
|
def vstat(path, force=False):
|
@@ -22,7 +58,9 @@ def vstat(path, force=False):
|
|
22
58
|
def filesize(path):
|
23
59
|
if hasattr(path, 'filesize') and path.filesize is not None:
|
24
60
|
return path.filesize
|
25
|
-
if path
|
61
|
+
if hasattr(path, 'is_url') and path.is_url():
|
62
|
+
return 0
|
63
|
+
if hasattr(path, 'has_fp') and path.has_fp():
|
26
64
|
return 0
|
27
65
|
st = path.stat() # vstat(path)
|
28
66
|
return st and st.st_size
|
@@ -38,8 +76,8 @@ class BytesIOWrapper(io.BufferedReader):
|
|
38
76
|
|
39
77
|
def __init__(self, text_io_buffer, encoding=None, errors=None, **kwargs):
|
40
78
|
super(BytesIOWrapper, self).__init__(text_io_buffer, **kwargs)
|
41
|
-
self.encoding = encoding or text_io_buffer.encoding or options.encoding
|
42
|
-
self.errors = errors or text_io_buffer.errors or options.encoding_errors
|
79
|
+
self.encoding = encoding or text_io_buffer.encoding or vd.options.encoding
|
80
|
+
self.errors = errors or text_io_buffer.errors or vd.options.encoding_errors
|
43
81
|
|
44
82
|
def _encoding_call(self, method_name, *args, **kwargs):
|
45
83
|
raw_method = getattr(self.raw, method_name)
|
@@ -129,6 +167,11 @@ class Path(os.PathLike):
|
|
129
167
|
self.filesize = filesize
|
130
168
|
self.rfile = None
|
131
169
|
|
170
|
+
@property
|
171
|
+
def name(self):
|
172
|
+
'Filename without any extensions. Not the same as pathlib.Path.'
|
173
|
+
return self.base_stem
|
174
|
+
|
132
175
|
@lru_cache()
|
133
176
|
def stat(self, force=False):
|
134
177
|
return self._path.stat()
|
@@ -148,17 +191,17 @@ class Path(os.PathLike):
|
|
148
191
|
|
149
192
|
self.ext = self.suffix[1:]
|
150
193
|
if self.suffix: #1450 don't make this a oneliner; [:-0] doesn't work
|
151
|
-
self.
|
194
|
+
self.base_stem = self._path.name[:-len(self.suffix)]
|
152
195
|
elif self._given == '.': #1768
|
153
|
-
self.
|
196
|
+
self.base_stem = self._path.absolute().name
|
154
197
|
else:
|
155
|
-
self.
|
198
|
+
self.base_stem = self._path.name
|
156
199
|
|
157
200
|
# check if file is compressed
|
158
201
|
if self.suffix in ['.gz', '.bz2', '.xz', '.lzma', '.zst']:
|
159
202
|
self.compression = self.ext
|
160
203
|
uncompressedpath = Path(self.given[:-len(self.suffix)]) # strip suffix
|
161
|
-
self.
|
204
|
+
self.base_stem = uncompressedpath.base_stem
|
162
205
|
self.ext = uncompressedpath.ext
|
163
206
|
else:
|
164
207
|
self.compression = None
|
@@ -190,23 +233,26 @@ class Path(os.PathLike):
|
|
190
233
|
'Return True if this is a virtual Path to an already open file.'
|
191
234
|
return bool(self.fp or self.fptext)
|
192
235
|
|
193
|
-
def
|
194
|
-
'Open path in text mode, using options.encoding and options.encoding_errors. Return open file-pointer or file-pointer-like.'
|
236
|
+
def open(self, mode='rt', encoding=None, encoding_errors=None, newline=None):
|
237
|
+
'Open path in text or binary mode, using options.encoding and options.encoding_errors. Return open file-pointer or file-pointer-like.'
|
195
238
|
# rfile makes a single-access fp reusable
|
196
239
|
|
197
240
|
if self.rfile:
|
198
|
-
|
241
|
+
if 'b' in mode:
|
242
|
+
raise ValueError('a RepeatFile holds text and cannot be reopened in binary mode')
|
243
|
+
return self.rfile.reopen()
|
199
244
|
|
200
245
|
if self.fp:
|
201
|
-
|
202
|
-
|
203
|
-
|
246
|
+
if 'b' not in mode:
|
247
|
+
self.fptext = codecs.iterdecode(self.fp,
|
248
|
+
encoding=encoding or vd.options.encoding,
|
249
|
+
errors=encoding_errors or vd.options.encoding_errors)
|
204
250
|
|
205
251
|
if self.fptext:
|
206
252
|
self.rfile = RepeatFile(self.fptext)
|
207
253
|
return self.rfile
|
208
254
|
|
209
|
-
if 't' not in mode:
|
255
|
+
if 't' not in mode and 'b' not in mode:
|
210
256
|
mode += 't'
|
211
257
|
|
212
258
|
if self.given == '-':
|
@@ -216,18 +262,21 @@ class Path(os.PathLike):
|
|
216
262
|
# convert 'a' to 'w' for stdout: https://bugs.python.org/issue27805
|
217
263
|
return open(os.dup(vd._stdout.fileno()), 'wt')
|
218
264
|
else:
|
219
|
-
vd.error('invalid mode "%s" for Path.
|
265
|
+
vd.error('invalid mode "%s" for Path.open()' % mode)
|
220
266
|
return sys.stderr
|
221
267
|
|
222
|
-
|
268
|
+
if 'b' in mode:
|
269
|
+
return self._open(mode=mode)
|
270
|
+
else:
|
271
|
+
return self._open(mode=mode, encoding=encoding or vd.options.encoding, errors=vd.options.encoding_errors, newline=newline)
|
223
272
|
|
224
273
|
@wraps(pathlib.Path.read_text)
|
225
274
|
def read_text(self, *args, **kwargs):
|
226
275
|
'Open the file in text mode and return its entire decoded contents.'
|
227
276
|
if 'encoding' not in kwargs:
|
228
|
-
kwargs['encoding'] = options.encoding
|
277
|
+
kwargs['encoding'] = vd.options.encoding
|
229
278
|
if 'errors' not in kwargs:
|
230
|
-
kwargs['errors'] = kwargs.get('encoding_errors', options.encoding_errors)
|
279
|
+
kwargs['errors'] = kwargs.get('encoding_errors', vd.options.encoding_errors)
|
231
280
|
|
232
281
|
if self.lines:
|
233
282
|
return RepeatFile(self.lines).read()
|
@@ -237,7 +286,7 @@ class Path(os.PathLike):
|
|
237
286
|
return self._path.read_text(*args, **kwargs)
|
238
287
|
|
239
288
|
@wraps(pathlib.Path.open)
|
240
|
-
def
|
289
|
+
def _open(self, *args, **kwargs):
|
241
290
|
if self.fp:
|
242
291
|
return FileProgress(self, fp=self.fp, **kwargs)
|
243
292
|
|
@@ -256,7 +305,7 @@ class Path(os.PathLike):
|
|
256
305
|
import lzma
|
257
306
|
zopen = lzma.open
|
258
307
|
elif self.compression == 'zst':
|
259
|
-
|
308
|
+
zstandard = vd.importExternal('zstandard')
|
260
309
|
zopen = zstandard.open
|
261
310
|
else:
|
262
311
|
return FileProgress(path, fp=self._path.open(*args, **kwargs), **kwargs)
|
@@ -270,7 +319,7 @@ class Path(os.PathLike):
|
|
270
319
|
|
271
320
|
def __iter__(self):
|
272
321
|
with Progress(total=filesize(self)) as prog:
|
273
|
-
with self.
|
322
|
+
with self.open(encoding=vd.options.encoding) as fd:
|
274
323
|
for i, line in enumerate(fd):
|
275
324
|
prog.addProgress(len(line))
|
276
325
|
yield line.rstrip('\n')
|
@@ -279,13 +328,22 @@ class Path(os.PathLike):
|
|
279
328
|
'Open the file pointed by this path and return a file object in binary mode.'
|
280
329
|
if 'b' not in mode:
|
281
330
|
mode += 'b'
|
282
|
-
return self.open(mode=mode)
|
331
|
+
return self.open(mode=mode) #1880
|
283
332
|
|
284
333
|
def read_bytes(self):
|
285
334
|
'Return the entire binary contents of the pointed-to file as a bytes object.'
|
286
335
|
with self.open(mode='rb') as fp:
|
287
336
|
return fp.read()
|
288
337
|
|
338
|
+
@wraps(pathlib.Path.is_fifo)
|
339
|
+
def is_fifo(self):
|
340
|
+
'Return True if the path is a file.'
|
341
|
+
return self._path.is_fifo()
|
342
|
+
|
343
|
+
def is_local(self):
|
344
|
+
'Return True if self.filename refers to a file on the local disk.'
|
345
|
+
return not bool(self.fp) and not bool(self.fptext)
|
346
|
+
|
289
347
|
def is_url(self):
|
290
348
|
'Return True if the given path appears to be a URL.'
|
291
349
|
return '://' in self.given
|
@@ -338,22 +396,31 @@ class RepeatFile:
|
|
338
396
|
|
339
397
|
def __enter__(self):
|
340
398
|
'''Returns a new independent file-like object, sharing the same line cache.'''
|
341
|
-
return
|
399
|
+
return self.reopen()
|
342
400
|
|
343
401
|
def __exit__(self, a,b,c):
|
344
402
|
pass
|
345
403
|
|
404
|
+
def reopen(self):
|
405
|
+
'Return copy of file-like with internal iterator reset.'
|
406
|
+
return RepeatFile(self.iter_lines, lines=self.lines)
|
407
|
+
|
346
408
|
def read(self, n=None):
|
347
|
-
r =
|
409
|
+
r = None
|
348
410
|
if n is None:
|
349
411
|
n = 10**12 # some too huge number
|
350
|
-
while len(r) < n:
|
412
|
+
while r is None or len(r) < n:
|
351
413
|
try:
|
352
414
|
s = next(self.iter)
|
353
|
-
r
|
415
|
+
if r is None:
|
416
|
+
r = '' if isinstance(s, str) else b''
|
417
|
+
else:
|
418
|
+
assert isinstance(r, type(s)), (r, type(s))
|
419
|
+
|
420
|
+
r += s + '\n' if isinstance(s, str) else b'\n'
|
354
421
|
except StopIteration:
|
355
422
|
break # end of file
|
356
|
-
return r
|
423
|
+
return r or ''
|
357
424
|
|
358
425
|
def write(self, s):
|
359
426
|
return self.iter_lines.write(s)
|
@@ -362,9 +429,16 @@ class RepeatFile:
|
|
362
429
|
'''Tells the current position as an opaque line marker.'''
|
363
430
|
return self.iter.nextIndex
|
364
431
|
|
365
|
-
def seek(self,
|
432
|
+
def seek(self, offset, whence=io.SEEK_SET):
|
366
433
|
'''Seek to an already seen opaque line position marker only.'''
|
367
|
-
|
434
|
+
if whence != io.SEEK_SET and offset != 0:
|
435
|
+
if whence == io.SEEK_CUR:
|
436
|
+
raise io.UnsupportedOperation("can't do nonzero cur-relative seeks")
|
437
|
+
elif whence == io.SEEK_END:
|
438
|
+
raise io.UnsupportedOperation("can't do nonzero end-relative seeks")
|
439
|
+
else:
|
440
|
+
raise ValueError('invalid whence (%s, should be %s, %s or %s)' % (whence, io.SEEK_SET, io.SEEK_CUR, io.SEEK_END))
|
441
|
+
self.iter.nextIndex = offset
|
368
442
|
|
369
443
|
def readline(self, size=-1):
|
370
444
|
if size != -1:
|
@@ -408,3 +482,10 @@ class RepeatFileIter:
|
|
408
482
|
|
409
483
|
self.nextIndex += 1
|
410
484
|
return r
|
485
|
+
|
486
|
+
|
487
|
+
vd.addGlobals(RepeatFile=RepeatFile,
|
488
|
+
Path=Path,
|
489
|
+
modtime=modtime,
|
490
|
+
filesize=filesize,
|
491
|
+
vstat=vstat)
|