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.
Files changed (256) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +78 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +63 -51
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +6 -2
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +22 -4
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +197 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +77 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +200 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +20 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +54 -12
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +302 -149
  187. visidata/man/vd.txt +291 -154
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +55 -205
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +239 -260
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +114 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/benchmark.csv +52 -0
  213. visidata/tests/conftest.py +3 -3
  214. visidata/tests/test_cliptext.py +39 -0
  215. visidata/tests/test_commands.py +65 -7
  216. visidata/tests/test_edittext.py +2 -2
  217. visidata/tests/test_features.py +28 -0
  218. visidata/tests/test_menu.py +14 -0
  219. visidata/tests/test_path.py +13 -4
  220. visidata/text_source.py +53 -0
  221. visidata/textsheet.py +10 -3
  222. visidata/theme.py +44 -0
  223. visidata/themes/__init__.py +0 -0
  224. visidata/themes/ascii8.py +84 -0
  225. visidata/themes/asciimono.py +84 -0
  226. visidata/themes/light.py +17 -0
  227. visidata/threads.py +89 -40
  228. visidata/tuiwin.py +22 -0
  229. visidata/type_currency.py +22 -3
  230. visidata/type_date.py +31 -9
  231. visidata/type_floatsi.py +5 -1
  232. visidata/undo.py +17 -5
  233. visidata/utils.py +106 -23
  234. visidata/vdobj.py +28 -17
  235. visidata/windows.py +10 -0
  236. visidata/wrappers.py +9 -3
  237. visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
  238. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
  239. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
  240. visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
  241. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
  242. visidata-3.0.1.dist-info/RECORD +258 -0
  243. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
  244. vgit/__init__.py +0 -1
  245. vgit/gitsheet.py +0 -164
  246. visidata/layout.py +0 -44
  247. visidata/misc.py +0 -5
  248. visidata-2.11.1.data/scripts/vgit +0 -9
  249. visidata-2.11.1.dist-info/RECORD +0 -155
  250. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  251. {vgit → visidata/apps/vgit}/abort.py +0 -0
  252. /visidata/{repeat.py → features/repeat.py} +0 -0
  253. {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
  256. {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
+ ''')
@@ -19,6 +19,8 @@ def branchStatus(sheet):
19
19
  @GitSheet.api
20
20
  def gitInProgress(sheet):
21
21
  p = sheet.gitPath
22
+ if not p:
23
+ return 'no repo'
22
24
  if (p/'rebase-merge').exists() or (p/'rebase-apply/rebasing').exists():
23
25
  return 'rebasing'
24
26
  elif p/'rebase-apply'.exists():
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, cleanName
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.names = names
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 = UNLOADED # list of opaque objects
116
- self._scr = mock.MagicMock(__bool__=mock.Mock(return_value=False)) # disable curses in batch mode
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
- def execCommand(self, cmd, vdglobals=None, keystrokes=None):
169
- cmd = self.getCommand(cmd or keystrokes)
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
- if keystrokes:
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
- try:
194
- if vd.cmdlog:
195
- # sheet may have changed
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.checkCursorNoExceptions()
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
- if self.options.clean_names:
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
- 'Clear the terminal screen and let the next draw cycle redraw everything.'
237
- if self._scr:
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(), None)
289
+ return eval(expr, vd.getGlobals(), dict(sheet=self))
269
290
 
270
- @property
271
- def sidebar(self):
272
- 'Default implementation just returns set value. Overridable.'
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.scrFull.clear()
292
- vd.win1.clear()
293
- vd.win2.clear()
294
- vd.setWindows(vd.scrFull)
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