visidata 2.11.1__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +72 -91
- visidata/_input.py +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +78 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- {vgit → visidata/apps/vgit}/blame.py +5 -2
- {vgit → visidata/apps/vgit}/branch.py +31 -16
- {vgit → visidata/apps/vgit}/config.py +3 -3
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- {vgit → visidata/apps/vgit}/grep.py +6 -5
- visidata/apps/vgit/log.py +81 -0
- {vgit → visidata/apps/vgit}/main.py +18 -5
- {vgit → visidata/apps/vgit}/remote.py +8 -4
- visidata/apps/vgit/repos.py +71 -0
- {vgit → visidata/apps/vgit}/setup.py +6 -4
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- {vgit → visidata/apps/vgit}/statusbar.py +2 -0
- visidata/basesheet.py +63 -51
- visidata/canvas.py +208 -93
- visidata/choose.py +6 -6
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +73 -17
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +88 -114
- visidata/color.py +142 -56
- visidata/column.py +121 -129
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +6 -2
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +22 -4
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +197 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +3 -1
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +32 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +77 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +200 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +20 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +2 -4
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +54 -12
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macros.py +129 -42
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -155
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -149
- visidata/man/vd.txt +291 -154
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +78 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +113 -32
- visidata/pivot.py +73 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +55 -205
- visidata/rename_col.py +20 -0
- visidata/save.py +37 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -25
- visidata/sheets.py +239 -260
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +114 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/benchmark.csv +52 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +65 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +28 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +89 -40
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +17 -5
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
- visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
- visidata-3.0.1.dist-info/RECORD +258 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
- vgit/__init__.py +0 -1
- vgit/gitsheet.py +0 -164
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.1.data/scripts/vgit +0 -9
- visidata-2.11.1.dist-info/RECORD +0 -155
- {vgit → visidata/apps/vgit}/__main__.py +0 -0
- {vgit → visidata/apps/vgit}/abort.py +0 -0
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
from visidata import vd, Column, VisiData, ItemColumn, Path, AttrDict, BaseSheet, IndexSheet
|
2
|
+
from visidata import RowColorizer, CellColorizer
|
3
|
+
from visidata import filesize, modtime, date
|
4
|
+
|
5
|
+
from .gitsheet import GitSheet
|
6
|
+
#from .diff import DifferSheet
|
7
|
+
|
8
|
+
vd.option('vgit_show_ignored', False, 'show ignored files on git status')
|
9
|
+
vd.theme_option('color_git_staged_mod', 'green', 'color of files staged with modifications')
|
10
|
+
vd.theme_option('color_git_staged_add', 'green', 'color of files staged for addition')
|
11
|
+
vd.theme_option('color_git_staged_del', 'red', 'color of files staged for deletion')
|
12
|
+
vd.theme_option('color_git_unstaged_del', 'on 88', 'color of files deleted but unstaged')
|
13
|
+
vd.theme_option('color_git_untracked', '243 blue', 'color of ignored/untracked files')
|
14
|
+
|
15
|
+
|
16
|
+
@VisiData.api
|
17
|
+
def git_status(vd, p, args, **kwargs):
|
18
|
+
vs = GitStatus('/'.join(p.parts[-2:]), source=p)
|
19
|
+
if not vs.gitRootPath:
|
20
|
+
return vd.git_repos(p, [])
|
21
|
+
return vs
|
22
|
+
|
23
|
+
class GitFile:
|
24
|
+
def __init__(self, path, gitsrc):
|
25
|
+
self.path = path
|
26
|
+
self.filename = path.relative_to(gitsrc)
|
27
|
+
self.is_dir = self.path.is_dir()
|
28
|
+
|
29
|
+
def __str__(self):
|
30
|
+
return str(self.filename) + (self.is_dir and '/' or '')
|
31
|
+
|
32
|
+
class GitStatus(GitSheet):
|
33
|
+
rowtype = 'files' # rowdef: GitFile
|
34
|
+
guide = '''
|
35
|
+
# git status
|
36
|
+
An overview of the local git checkout.
|
37
|
+
|
38
|
+
- `Enter` to open diff of file (`git diff`)
|
39
|
+
- `a` to stage changes in file (`git add`)
|
40
|
+
- `r` to unstage changes in file (`git reset`)
|
41
|
+
- `c` to revert all unstaged changes in file (`git checkout`)
|
42
|
+
- `d` to stage the entire file for deletion (`git rm`)
|
43
|
+
- `z Ctrl+S` to commit staged changes (`git commit`)
|
44
|
+
'''
|
45
|
+
|
46
|
+
columns = [
|
47
|
+
Column('path', width=40, getter=lambda c,r: str(r)),
|
48
|
+
Column('status', getter=lambda c,r: c.sheet.statusText(c.sheet.git_status(r)), width=8),
|
49
|
+
Column('status_raw', getter=lambda c,r: c.sheet.git_status(r), width=0),
|
50
|
+
Column('staged', getter=lambda c,r: c.sheet.git_status(r).dels),
|
51
|
+
Column('unstaged', getter=lambda c,r: c.sheet.git_status(r).adds),
|
52
|
+
Column('type', getter=lambda c,r: r.is_dir() and '/' or r.suffix, width=0),
|
53
|
+
Column('size', type=int, getter=lambda c,r: filesize(r)),
|
54
|
+
Column('modtime', type=date, getter=lambda c,r: modtime(r)),
|
55
|
+
]
|
56
|
+
nKeys = 1
|
57
|
+
|
58
|
+
colorizers = [
|
59
|
+
CellColorizer(3, 'color_git_staged_mod', lambda s,c,r,v: r and c and c.name == 'staged' and s.git_status(r).status[0] == 'M'), # staged mod
|
60
|
+
CellColorizer(1, 'color_git_staged_del', lambda s,c,r,v: r and c and c.name == 'staged' and s.git_status(r).status == 'D '), # staged delete
|
61
|
+
RowColorizer(1, 'color_git_staged_add', lambda s,c,r,v: r and s.git_status(r).status in ['A ', 'M ']), # staged add/mod
|
62
|
+
RowColorizer(1, 'color_git_unstaged_del', lambda s,c,r,v: r and s.git_status(r).status[1] == 'D'), # unstaged delete
|
63
|
+
RowColorizer(3, 'color_git_untracked', lambda s,c,r,v: r and s.git_status(r).status == '!!'), # ignored
|
64
|
+
RowColorizer(1, 'color_git_untracked', lambda s,c,r,v: r and s.git_status(r).status == '??'), # untracked
|
65
|
+
]
|
66
|
+
|
67
|
+
def statusText(self, st):
|
68
|
+
vmod = {'A': 'add', 'D': 'rm', 'M': 'mod', 'T': 'chmod', '?': '', '!': 'ignored', 'U': 'unmerged'}
|
69
|
+
x, y = st.status
|
70
|
+
if st == '??': # untracked
|
71
|
+
return 'new'
|
72
|
+
elif st == '!!': # ignored
|
73
|
+
return 'ignored'
|
74
|
+
elif x != ' ' and y == ' ': # staged
|
75
|
+
return vmod.get(x, x)
|
76
|
+
elif y != ' ': # unstaged
|
77
|
+
return vmod.get(y, y)
|
78
|
+
else:
|
79
|
+
return ''
|
80
|
+
|
81
|
+
@property
|
82
|
+
def workdir(self):
|
83
|
+
return str(self.source)
|
84
|
+
|
85
|
+
def git_status(self, r):
|
86
|
+
'''return tuple of (status, adds, dels).
|
87
|
+
status like !! ??
|
88
|
+
adds and dels are lists of additions and deletions.
|
89
|
+
'''
|
90
|
+
if not r:
|
91
|
+
return None
|
92
|
+
|
93
|
+
fn = str(r)
|
94
|
+
ret = self._cachedStatus.get(fn, None)
|
95
|
+
if not ret:
|
96
|
+
ret = AttrDict(status='??')
|
97
|
+
self._cachedStatus[fn] = ret
|
98
|
+
|
99
|
+
return ret
|
100
|
+
|
101
|
+
def ignored(self, fn):
|
102
|
+
if self.options.vgit_show_ignored:
|
103
|
+
return False
|
104
|
+
|
105
|
+
if fn in self._cachedStatus:
|
106
|
+
return self._cachedStatus[fn].status == '!!'
|
107
|
+
|
108
|
+
return False
|
109
|
+
|
110
|
+
@property
|
111
|
+
def remotediff(self):
|
112
|
+
return self.gitBranchStatuses.get(self.branch, 'no branch')
|
113
|
+
|
114
|
+
def iterload(self):
|
115
|
+
files = [GitFile(p, self.source) for p in self.source.iterdir() if p.base_stem not in ('.git')] # files in working dir
|
116
|
+
|
117
|
+
filenames = dict((gf.filename, gf) for gf in files)
|
118
|
+
|
119
|
+
self._cachedStatus.clear()
|
120
|
+
for fn in self.git_iter('ls-files', '-z'):
|
121
|
+
self._cachedStatus[fn] = AttrDict(status=' ')
|
122
|
+
|
123
|
+
for line in self.git_iter('status', '-z', '-unormal', '--ignored'):
|
124
|
+
if not line: continue
|
125
|
+
|
126
|
+
if line[2:3] == ' ':
|
127
|
+
st, fn = line[:2], line[3:]
|
128
|
+
else:
|
129
|
+
fn = line
|
130
|
+
st = '??' # untracked file
|
131
|
+
|
132
|
+
self._cachedStatus[fn] = AttrDict(status=st)
|
133
|
+
if not self.ignored(fn):
|
134
|
+
yield Path(fn)
|
135
|
+
|
136
|
+
for line in self.git_iter('diff-files', '--numstat', '-z'):
|
137
|
+
if not line: continue
|
138
|
+
adds, dels, fn = line.split('\t')
|
139
|
+
if fn not in self._cachedStatus:
|
140
|
+
self._cachedStatus[fn] = AttrDict(status='##')
|
141
|
+
cs = self._cachedStatus[fn]
|
142
|
+
cs.adds = '+%s/-%s' % (adds, dels)
|
143
|
+
|
144
|
+
for line in self.git_iter('diff-index', '--cached', '--numstat', '-z', 'HEAD'):
|
145
|
+
if not line: continue
|
146
|
+
adds, dels, fn = line.split('\t')
|
147
|
+
if fn not in self._cachedStatus:
|
148
|
+
self._cachedStatus[fn] = AttrDict(status='$$')
|
149
|
+
cs = self._cachedStatus[fn]
|
150
|
+
cs.dels = '+%s/-%s' % (adds, dels)
|
151
|
+
|
152
|
+
self.orderBy(None, self.columns[-1], reverse=True)
|
153
|
+
|
154
|
+
self.recalc() # erase column caches
|
155
|
+
|
156
|
+
def openRow(self, row):
|
157
|
+
'Open unstaged diffs for this file, or dive into directory'
|
158
|
+
if row.is_dir:
|
159
|
+
return GitStatus(row.path)
|
160
|
+
else:
|
161
|
+
return DifferSheet(row, "HEAD", "index", "working", source=sheet)
|
162
|
+
|
163
|
+
def openRows(self, rows):
|
164
|
+
'Open unstaged hunks for selected rows'
|
165
|
+
return getHunksSheet(sheet, *rows)
|
166
|
+
|
167
|
+
|
168
|
+
@GitStatus.lazy_property
|
169
|
+
def _cachedStatus(self):
|
170
|
+
return {} # [filename] -> AttrDict(status='xx', adds=, dels=)
|
171
|
+
|
172
|
+
|
173
|
+
GitStatus.addCommand('a', 'git-add', 'loggit("add", cursorRow.filename)', 'add this new file or modified file to staging')
|
174
|
+
#GitStatus.addCommand('m', 'git-mv', 'loggit("mv", cursorRow.filename, input("rename file to: ", value=cursorRow.filename))', 'rename this file')
|
175
|
+
GitStatus.addCommand('d', 'git-rm', 'loggit("rm", cursorRow.filename)', 'stage this file for deletion')
|
176
|
+
GitStatus.addCommand('r', 'git-reset', 'loggit("reset", "HEAD", cursorRow.filename)', 'reset/unstage this file')
|
177
|
+
GitStatus.addCommand('c', 'git-checkout', 'loggit("checkout", cursorRow.filename)', 'checkout this file')
|
178
|
+
GitStatus.addCommand('ga', 'git-add-selected', 'loggit("add", *[r for r in selectedRows])', 'add all selected files to staging')
|
179
|
+
GitStatus.addCommand('gd', 'git-rm-selected', 'loggit("rm", *[r for r in selectedRows])', 'delete all selected files')
|
180
|
+
GitStatus.addCommand(None, 'git-commit', 'loggit("commit", "-m", input("commit message: "))', 'commit changes')
|
181
|
+
GitStatus.addCommand(None, 'git-ignore-file', 'open(rootPath/".gitignore", "a").write(cursorRow.filename+"\\n"); reload()', 'add file to toplevel .gitignore')
|
182
|
+
GitStatus.addCommand(None, 'git-ignore-wildcard', 'open(rootPath/.gitignore, "a").write(input("add wildcard to .gitignore: "))', 'add input line to toplevel .gitignore')
|
183
|
+
|
184
|
+
|
185
|
+
#GitStatus.addCommand('z^J', 'diff-file-staged', 'vd.push(getStagedHunksSheet(sheet, cursorRow))', 'push staged diffs for this file')
|
186
|
+
#GitStatus.addCommand('gz^J', 'diff-selected-staged', 'vd.push(getStagedHunksSheet(sheet, *(selectedRows or rows)))', 'push staged diffs for selected files or all files')
|
187
|
+
#GitStatus.addCommand('^O', 'sysopen-row', 'launchExternalEditorPath(Path(cursorRow.path))', 'open this file in $EDITOR')
|
188
|
+
|
189
|
+
|
190
|
+
vd.addMenuItems('''
|
191
|
+
Git > View staged changes > current file > diff-file-staged
|
192
|
+
Git > View staged changes > selected files > staged changes > diff-selected-staged
|
193
|
+
Git > Stage > current file > git-add
|
194
|
+
Git > Stage > selected files > git-add-selected
|
195
|
+
Git > Unstage > current file > git-reset
|
196
|
+
Git > Unstage > selected files > git-reset-selected
|
197
|
+
Git > Rename file > git-mv
|
198
|
+
Git > Delete > file > git-rm
|
199
|
+
Git > Delete > selected files > git-rm-selected
|
200
|
+
Git > Ignore > file > ignore-file
|
201
|
+
Git > Ignore > wildcard > ignore-wildcard
|
202
|
+
Git > Commit staged changes > git-commit
|
203
|
+
Git > Revert unstaged changes > current file > git-checkout
|
204
|
+
''')
|
visidata/basesheet.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
import os
|
2
2
|
|
3
3
|
import visidata
|
4
|
-
from visidata import Extensible, VisiData, vd, EscapeException,
|
5
|
-
from unittest import mock
|
4
|
+
from visidata import Extensible, VisiData, vd, EscapeException, MissingAttrFormatter, AttrDict
|
6
5
|
|
7
6
|
|
8
7
|
UNLOADED = tuple() # sentinel for a sheet not yet loaded for the first time
|
@@ -19,6 +18,9 @@ class LazyChainMap:
|
|
19
18
|
if k not in self.objs:
|
20
19
|
self.objs[k] = obj
|
21
20
|
|
21
|
+
def __iter__(self):
|
22
|
+
return iter(self.objs)
|
23
|
+
|
22
24
|
def __contains__(self, k):
|
23
25
|
return k in self.objs
|
24
26
|
|
@@ -98,6 +100,7 @@ class BaseSheet(DrawablePane):
|
|
98
100
|
rowtype = 'objects' # one word, plural, describing the items
|
99
101
|
precious = True # False for a few discardable metasheets
|
100
102
|
defer = False # False for not deferring changes until save
|
103
|
+
guide = '' # default to show in sidebar
|
101
104
|
|
102
105
|
def _obj_options(self):
|
103
106
|
return vd.OptionsObject(vd._options, obj=self)
|
@@ -107,15 +110,14 @@ class BaseSheet(DrawablePane):
|
|
107
110
|
|
108
111
|
class_options = options = _dualproperty(_obj_options, _class_options)
|
109
112
|
|
110
|
-
def __init__(self, *names, **kwargs):
|
113
|
+
def __init__(self, *names, rows=UNLOADED, **kwargs):
|
111
114
|
self._name = None # initial cache value necessary for self.options
|
112
|
-
self.
|
115
|
+
self.loading = False
|
116
|
+
self.names = list(names)
|
113
117
|
self.name = self.options.name_joiner.join(str(x) for x in self.names if x)
|
114
118
|
self.source = None
|
115
|
-
self.rows =
|
116
|
-
self._scr =
|
117
|
-
self.mouseX = 0
|
118
|
-
self.mouseY = 0
|
119
|
+
self.rows = rows # list of opaque objects
|
120
|
+
self._scr = None
|
119
121
|
self.hasBeenModified = False
|
120
122
|
|
121
123
|
super().__init__(**kwargs)
|
@@ -153,6 +155,14 @@ class BaseSheet(DrawablePane):
|
|
153
155
|
def __str__(self):
|
154
156
|
return self.name
|
155
157
|
|
158
|
+
@property
|
159
|
+
def rows(self):
|
160
|
+
return self._rows
|
161
|
+
|
162
|
+
@rows.setter
|
163
|
+
def rows(self, rows):
|
164
|
+
self._rows = rows
|
165
|
+
|
156
166
|
@property
|
157
167
|
def nRows(self):
|
158
168
|
'Number of rows on this sheet. Override in subclass.'
|
@@ -165,11 +175,26 @@ class BaseSheet(DrawablePane):
|
|
165
175
|
return vs in self.source
|
166
176
|
return False
|
167
177
|
|
168
|
-
|
169
|
-
|
178
|
+
@property
|
179
|
+
def displaySource(self):
|
180
|
+
if isinstance(self.source, BaseSheet):
|
181
|
+
return f'the *{self.source}* sheet'
|
182
|
+
|
183
|
+
if isinstance(self.source, (list, tuple)):
|
184
|
+
if len(self.source) == 1:
|
185
|
+
return f'the **{self.source[0]}** sheet'
|
186
|
+
return f'{len(self.source)} sheets'
|
187
|
+
|
188
|
+
return f'**{self.source}**'
|
189
|
+
|
190
|
+
def execCommand(self, longname, vdglobals=None, keystrokes=None):
|
191
|
+
if ' ' in longname:
|
192
|
+
cmd, arg = longname.split(' ', maxsplit=1)
|
193
|
+
vd.injectInput(arg)
|
194
|
+
|
195
|
+
cmd = self.getCommand(longname or keystrokes)
|
170
196
|
if not cmd:
|
171
|
-
|
172
|
-
vd.status('no command for %s' % keystrokes)
|
197
|
+
vd.warning('no command for %s' % (longname or keystrokes))
|
173
198
|
return False
|
174
199
|
|
175
200
|
escaped = False
|
@@ -183,25 +208,30 @@ class BaseSheet(DrawablePane):
|
|
183
208
|
try:
|
184
209
|
for hookfunc in vd.beforeExecHooks:
|
185
210
|
hookfunc(self, cmd, '', keystrokes)
|
186
|
-
vd.debug(cmd.longname)
|
187
211
|
escaped = super().execCommand2(cmd, vdglobals=vdglobals)
|
188
212
|
except Exception as e:
|
189
213
|
vd.debug(cmd.execstr)
|
190
214
|
err = vd.exceptionCaught(e)
|
191
215
|
escaped = True
|
192
216
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
vd.cmdlog.afterExecSheet(vd.activeSheet, escaped, err)
|
197
|
-
except Exception as e:
|
198
|
-
vd.exceptionCaught(e)
|
217
|
+
if vd.cmdlog:
|
218
|
+
# sheet may have changed
|
219
|
+
vd.callNoExceptions(vd.cmdlog.afterExecSheet, vd.activeSheet, escaped, err)
|
199
220
|
|
200
|
-
self.
|
221
|
+
vd.callNoExceptions(self.checkCursor)
|
201
222
|
|
202
223
|
vd.clearCaches()
|
224
|
+
|
225
|
+
for t in self.currentThreads:
|
226
|
+
if not hasattr(t, 'lastCommand'):
|
227
|
+
t.lastCommand = True
|
228
|
+
|
203
229
|
return escaped
|
204
230
|
|
231
|
+
@property
|
232
|
+
def lastCommandThreads(self):
|
233
|
+
return [t for t in self.currentThreads if getattr(t, 'lastCommand', None)]
|
234
|
+
|
205
235
|
@property
|
206
236
|
def names(self):
|
207
237
|
return self._names
|
@@ -224,8 +254,7 @@ class BaseSheet(DrawablePane):
|
|
224
254
|
self._name = self.maybeClean(str(name))
|
225
255
|
|
226
256
|
def maybeClean(self, s):
|
227
|
-
|
228
|
-
s = cleanName(s)
|
257
|
+
'stub'
|
229
258
|
return s
|
230
259
|
|
231
260
|
def recalc(self):
|
@@ -233,10 +262,8 @@ class BaseSheet(DrawablePane):
|
|
233
262
|
pass
|
234
263
|
|
235
264
|
def refresh(self):
|
236
|
-
'
|
237
|
-
|
238
|
-
self._scr.clear()
|
239
|
-
self._scr.refresh()
|
265
|
+
'Recalculate any internal state needed for `draw()`. Overridable.'
|
266
|
+
pass
|
240
267
|
|
241
268
|
def ensureLoaded(self):
|
242
269
|
'Call ``reload()`` if not already loaded.'
|
@@ -257,30 +284,14 @@ class BaseSheet(DrawablePane):
|
|
257
284
|
'Check cursor and fix if out-of-bounds. Overridable.'
|
258
285
|
pass
|
259
286
|
|
260
|
-
def checkCursorNoExceptions(self):
|
261
|
-
try:
|
262
|
-
return self.checkCursor()
|
263
|
-
except Exception as e:
|
264
|
-
vd.exceptionCaught(e)
|
265
|
-
|
266
287
|
def evalExpr(self, expr, **kwargs):
|
267
288
|
'Evaluate Python expression *expr* in the context of *kwargs* (may vary by sheet type).'
|
268
|
-
return eval(expr, vd.getGlobals(),
|
289
|
+
return eval(expr, vd.getGlobals(), dict(sheet=self))
|
269
290
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
return self._sidebar
|
291
|
+
def formatString(self, fmt, **kwargs):
|
292
|
+
'Return formatted string with *sheet* and *vd* accessible to expressions. Missing expressions return empty strings instead of error.'
|
293
|
+
return MissingAttrFormatter().format(fmt, sheet=self, vd=vd, **kwargs)
|
274
294
|
|
275
|
-
@sidebar.setter
|
276
|
-
def sidebar(self, v):
|
277
|
-
'Default implementation just sets value. Overridable.'
|
278
|
-
self._sidebar = v
|
279
|
-
|
280
|
-
@property
|
281
|
-
def sidebar_title(self):
|
282
|
-
'Default implementation returns fixed value. Overridable.'
|
283
|
-
return 'sidebar'
|
284
295
|
|
285
296
|
|
286
297
|
@VisiData.api
|
@@ -288,10 +299,11 @@ def redraw(vd):
|
|
288
299
|
'Clear the terminal screen and let the next draw cycle recreate the windows and redraw everything.'
|
289
300
|
for vs in vd.sheets:
|
290
301
|
vs._scr = None
|
291
|
-
vd.
|
292
|
-
vd.
|
293
|
-
vd.
|
294
|
-
|
302
|
+
if vd.win1: vd.win1.clear()
|
303
|
+
if vd.win2: vd.win2.clear()
|
304
|
+
if vd.scrFull:
|
305
|
+
vd.scrFull.clear()
|
306
|
+
vd.setWindows(vd.scrFull)
|
295
307
|
|
296
308
|
|
297
309
|
@VisiData.property
|
@@ -299,7 +311,7 @@ def sheet(self):
|
|
299
311
|
return self.activeSheet
|
300
312
|
|
301
313
|
@VisiData.api
|
302
|
-
def isLongname(self, ks):
|
314
|
+
def isLongname(self, ks:str):
|
303
315
|
'Return True if *ks* is a longname.'
|
304
316
|
return ('-' in ks) and (ks[-1] != '-') or (len(ks) > 3 and ks.islower())
|
305
317
|
|