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,61 @@
1
+ '''
2
+ Specify the billing_project_id as the netloc, and the actual dataset_id as the path:
3
+
4
+ vdsql bigquery://<billing_project>/<dataset_id>''
5
+ '''
6
+
7
+
8
+ from visidata import vd, VisiData, Sheet, AttrColumn
9
+ from . import IbisTableSheet, IbisTableIndexSheet, IbisConnectionPool
10
+
11
+ import ibis
12
+ import ibis.expr.operations as ops
13
+
14
+ @VisiData.api
15
+ def openurl_bigquery(vd, p, filetype=None):
16
+ vd.configure_ibis()
17
+ vd.configure_bigquery()
18
+ return BigqueryDatabaseIndexSheet(p.base_stem, source=p, ibis_con=None)
19
+
20
+ vd.openurl_bq = vd.openurl_bigquery
21
+
22
+
23
+ @VisiData.api
24
+ def configure_bigquery(vd):
25
+ @ibis.bigquery.add_operation(ops.TimestampDiff)
26
+ def bq_timestamp_diff(t, expr):
27
+ op = expr.op()
28
+ left = t.translate(op.left)
29
+ right = t.translate(op.right)
30
+ return f"TIMESTAMP_DIFF({left}, {right}, SECOND)"
31
+
32
+
33
+ class BigqueryDatabaseIndexSheet(Sheet):
34
+ rowtype = 'databases' # rowdef: DatasetListItem
35
+ columns = [
36
+ # AttrColumn('project', width=0),
37
+ AttrColumn('dataset_id'),
38
+ AttrColumn('friendly_name'),
39
+ AttrColumn('full_dataset_id', width=0),
40
+ AttrColumn('labels'),
41
+ ]
42
+ nKeys = 1
43
+
44
+ @property
45
+ def con(self):
46
+ if not self.ibis_con:
47
+ import ibis
48
+ self.ibis_con = ibis.connect(self.source)
49
+ return self.ibis_con
50
+
51
+ def iterload(self):
52
+ yield from self.con.client.list_datasets(project=self.source.name)
53
+
54
+ def openRow(self, row):
55
+ return IbisTableIndexSheet(row.dataset_id,
56
+ database_name=self.source.name+'.'+row.dataset_id,
57
+ ibis_con=self.con,
58
+ ibis_conpool=IbisConnectionPool(f"{self.source}/{row.dataset_id}"),
59
+ source=row,
60
+ filetype=None,
61
+ sheet_type=IbisTableSheet)
@@ -0,0 +1,53 @@
1
+ import time
2
+
3
+ from visidata import BaseException, vd, VisiData, Progress
4
+
5
+ from ._ibis import IbisTableSheet, IbisTableIndexSheet, IbisConnectionPool
6
+
7
+
8
+ @VisiData.api
9
+ def openurl_clickhouse(vd, p, filetype=None):
10
+ vd.configure_ibis()
11
+ return IbisTableIndexSheet(p.base_stem, source=p, filetype=None, database_name=None,
12
+ ibis_conpool=IbisConnectionPool(p), sheet_type=ClickhouseSheet)
13
+
14
+ vd.openurl_clickhouses = vd.openurl_clickhouse
15
+
16
+
17
+ class ClickhouseSheet(IbisTableSheet):
18
+ @property
19
+ def countRows(self):
20
+ if self.total_rows is not None:
21
+ return self.total_rows
22
+ return super().countRows
23
+
24
+ def iterload(self):
25
+ with self.con as con:
26
+ qid = None
27
+ try:
28
+ if self.query is None:
29
+ self.query = self.baseQuery(con)
30
+
31
+ self.reloadColumns(self.query, start=0) # columns based on query without metadata
32
+ sqlstr = con.compile(self.query.limit(self.options.ibis_limit or None))
33
+
34
+ with Progress(gerund='clickhousing', sheet=self) as prog:
35
+ settings = {'max_block_size': 10000}
36
+ with con.con.query_rows_stream(sqlstr, settings) as stream:
37
+ prog.total = int(stream.source.summary['total_rows_to_read'])
38
+ prog.made = 0
39
+ for row in stream:
40
+ prog.made += 1
41
+ yield row
42
+ self.total_rows = prog.total
43
+
44
+ except Exception as e:
45
+ raise
46
+ except BaseException:
47
+ if qid:
48
+ con.con.cancel(qid)
49
+
50
+
51
+ ClickhouseSheet.init('total_rows', lambda: None)
52
+
53
+ #ClickhouseSheet.class_options.sql_always_count = True
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ from setuptools import setup, find_packages
4
+ from pathlib import Path
5
+
6
+
7
+ exec(Path('__about__.py').read_text())
8
+
9
+
10
+ def readme():
11
+ return Path("README.md").read_text()
12
+
13
+
14
+ def requirements():
15
+ return Path("requirements.txt").read_text().splitlines()
16
+
17
+ def requirements_extra():
18
+ return Path("requirements-extra.txt").read_text().splitlines()
19
+
20
+
21
+ setup(
22
+ name="vdsql",
23
+ version=__version__,
24
+ description=__description__,
25
+ long_description=readme(),
26
+ long_description_content_type="text/markdown",
27
+ classifiers=[
28
+ "Development Status :: 4 - Beta",
29
+ "Programming Language :: Python :: 3",
30
+ ],
31
+ keywords="visidata sql rdbms ibis substrait",
32
+ author="Saul Pwanson",
33
+ url="https://github.com/visidata/vdsql",
34
+ python_requires=">=3.9",
35
+ packages=find_packages(exclude=["tests"]),
36
+ entry_points={'visidata.plugins': 'vdsql=visidata.apps.vdsql'},
37
+ scripts=['vdsql'],
38
+ install_requires=requirements(),
39
+ extra_requires=requirements_extra(),
40
+ )
@@ -0,0 +1,67 @@
1
+ import time
2
+
3
+ from visidata import vd, BaseException, VisiData
4
+
5
+ from ._ibis import IbisTableSheet, IbisConnectionPool, IbisTableIndexSheet
6
+
7
+
8
+ @VisiData.api
9
+ def openurl_snowflake(vd, p, filetype=None):
10
+ return IbisTableIndexSheet(p.base_stem, source=p, filetype=None, database_name=None,
11
+ ibis_conpool=IbisConnectionPool(p),
12
+ sheet_type=SnowflakeSheet)
13
+
14
+
15
+ class SnowflakeSheet(IbisTableSheet):
16
+ @property
17
+ def countRows(self):
18
+ r = super().countRows
19
+ if r is None and self.cursor is None:
20
+ return None # no cursor yet
21
+ return r
22
+
23
+ def executeSql(self, sql):
24
+ assert self.cursor is None
25
+
26
+ with self.con as con:
27
+ con = con.con
28
+
29
+ if self.warehouse:
30
+ con.execute(f'USE WAREHOUSE {self.warehouse}')
31
+
32
+ with con.begin() as c:
33
+ snowflake_conn = c.connection.dbapi_connection
34
+ cursor = self.cursor = snowflake_conn.cursor()
35
+ cursor.execute_async(sql)
36
+ while snowflake_conn.is_still_running(snowflake_conn.get_query_status(cursor.sfqid)):
37
+ time.sleep(.1)
38
+
39
+ cursor.get_results_from_sfqid(cursor.sfqid)
40
+ yield from cursor.fetchall()
41
+
42
+ self.cursor = None
43
+
44
+ def iterload(self):
45
+ try:
46
+ with self.con as con:
47
+ if self.query is None:
48
+ self.query = self.baseQuery(con)
49
+ yield from self.executeSql(self.ibis_to_sql(self.withRowcount(self.baseQuery(con))))
50
+ except BaseException:
51
+ if self.cursor:
52
+ self.cancelQuery(self.cursor.sfqid)
53
+ raise
54
+
55
+ self.reloadColumns(self.query) # columns based on query without metadata
56
+
57
+ def cancelQuery(self, qid):
58
+ vd.status(f'canceling "{qid}"')
59
+ with self.con as con:
60
+ with con.begin() as con:
61
+ cursor = con.connection.dbapi_connection.cursor()
62
+ cursor.execute(f"SELECT SYSTEM$CANCEL_QUERY('{qid}')")
63
+ vd.status(cursor.fetchall())
64
+
65
+
66
+ SnowflakeSheet.init('cursor', lambda: None)
67
+ SnowflakeSheet.init('warehouse', str)
@@ -0,0 +1,13 @@
1
+ from . import abort, statusbar
2
+ from . import (
3
+ grep,
4
+ config,
5
+ branch,
6
+ remote,
7
+ blame,
8
+ status,
9
+ log,
10
+ diff,
11
+ stash,
12
+ repos,
13
+ )
@@ -4,7 +4,7 @@ from .gitsheet import GitSheet
4
4
 
5
5
 
6
6
  @VisiData.api
7
- def git_blame(vd, args, **kwargs):
7
+ def git_blame(vd, gitpath, args, **kwargs):
8
8
  if args and not args[-1].startswith('-'):
9
9
  fn = args[-1]
10
10
  return GitBlame('blame', fn, source=Path(fn), **kwargs)
@@ -20,6 +20,9 @@ class FormatColumn(Column):
20
20
  # source = GitSheet; .gitfile=GitFile
21
21
  class GitBlame(GitSheet):
22
22
  rowtype = 'lines'
23
+ guide = '''
24
+ # git blame
25
+ '''
23
26
  columns = [
24
27
  ItemColumn('sha', width=0),
25
28
  ItemColumn('orig_linenum', width=0, type=int),
@@ -70,4 +73,4 @@ class GitBlame(GitSheet):
70
73
 
71
74
  #GitBlame.addCommand(ENTER, 'diff-line', 'openDiff(str(gitfile), cursorRow[0]["sha"]+"^", cursorRow[0]["sha"])', 'open diff of the commit when this line changed')
72
75
 
73
- #GitStatus.addCommand(None, 'git-blame', 'vd.push(GitBlame(cursorRow, source=sheet))', 'push blame for this file'),
76
+ #GitStatus.addCommand(None, 'git-blame', 'vd.push(GitBlame(cursorRow, source=sheet))', 'push blame for this file')
@@ -4,17 +4,17 @@ from visidata import vd, Column, VisiData, ItemColumn, AttrColumn, Path, AttrDic
4
4
 
5
5
  from .gitsheet import GitSheet
6
6
 
7
- vd.option('color_git_current_branch', 'underline', 'color of current branch on branches sheet')
8
- vd.option('color_git_remote_branch', 'cyan', 'color of remote branches on branches sheet')
7
+ vd.theme_option('color_git_current_branch', 'underline', 'color of current branch on branches sheet')
8
+ vd.theme_option('color_git_remote_branch', 'cyan', 'color of remote branches on branches sheet')
9
9
 
10
10
 
11
11
  @VisiData.api
12
- def git_branch(vd, args):
12
+ def git_branch(vd, p, args):
13
13
  nonListArgs = '--track --no-track --set-upstream-to -u --unset-upstream -m -M -c -C -d -D --edit-description'.split()
14
14
  if any(x in args for x in nonListArgs):
15
15
  return
16
16
 
17
- return GitBranch('git-branch-list', source=Path('.'), git_args=args)
17
+ return GitBranch('git-branch-list', source=p, git_args=args)
18
18
 
19
19
 
20
20
  def _remove_prefix(text, prefix):
@@ -32,7 +32,7 @@ class GitBranchNameColumn(Column):
32
32
 
33
33
 
34
34
  class GitBranch(GitSheet):
35
- help = '''
35
+ guide = '''
36
36
  # git branch
37
37
  List of all branches, including relevant metadata.
38
38
 
@@ -61,16 +61,25 @@ class GitBranch(GitSheet):
61
61
  nKeys = 1
62
62
 
63
63
  def iterload(self):
64
- branches_lines = self.git_lines('branch',
65
- '--list',
66
- '-vv',
67
- '--no-color',
68
- *self.git_args)
69
- for line in branches_lines:
70
- if '->' in line:
71
- continue
64
+ branches_lines = self.git_lines(
65
+ 'branch',
66
+ '--list',
67
+ '--format',
68
+ ' '.join((
69
+ '%(if)%(symref)%(then)yes%(else)no%(end)',
70
+ '%(HEAD) %(refname:short) %(objectname:short)',
71
+ '%(if)%(upstream)%(then)[%(upstream:short)',
72
+ '%(if)%(upstream:track)%(then): %(upstream:track,nobracket)%(end)]',
73
+ '%(end)',
74
+ '%(contents:subject)'
75
+ )),
76
+ '-vv',
77
+ '--no-color',
78
+ *self.git_args)
72
79
 
73
- m = re.match(r'''(?P<current>\*?)\s+
80
+ for line in branches_lines:
81
+ m = re.match(r'''(?P<is_symref>(yes|no)?)\s+
82
+ (?P<current>\*?)\s+
74
83
  (?P<localbranch>\S+)\s+
75
84
  (?P<refid>\w+)\s+
76
85
  (?:\[
@@ -78,8 +87,12 @@ class GitBranch(GitSheet):
78
87
  \s*(?P<extra>.*?)
79
88
  \])?
80
89
  \s*(?P<msg>.*)''', line, re.VERBOSE)
81
- if m:
82
- yield AttrDict(m.groupdict())
90
+ if not m:
91
+ continue
92
+ branch_details = AttrDict(m.groupdict())
93
+ if branch_details.is_symref == 'yes':
94
+ continue
95
+ yield branch_details
83
96
 
84
97
  branch_stats = self.gitRootSheet.gitBranchStatuses
85
98
  for row in Progress(self.rows):
@@ -126,6 +139,7 @@ def gitBranchStatuses(sheet):
126
139
  return ret
127
140
 
128
141
 
142
+ GitSheet.addCommand('', 'git-open-branches', 'vd.push(git_branch(source, []))', 'push branches sheet')
129
143
  GitSheet.addCommand('', 'git-branch-create', 'git("branch", input("create branch: ", type="branch"))', 'create a new branch off the current checkout')
130
144
  GitBranch.addCommand('', 'git-branch-checkout', 'git("checkout", cursorRow.localbranch)', 'checkout this branch')
131
145
 
@@ -135,4 +149,5 @@ vd.addMenuItems('''
135
149
  Git > Branch > delete > git-branch-delete
136
150
  Git > Branch > rename > git-branch-rename
137
151
  Git > Branch > checkout > git-branch-checkout
152
+ Git > Open > branches > git-open-branches
138
153
  ''')
@@ -8,9 +8,9 @@ from .gitsheet import GitSheet
8
8
  CONFIG_CONTEXTS = ('local', 'global', 'system')
9
9
 
10
10
  @VisiData.api
11
- def git_config(vd, args):
11
+ def git_config(vd, p, args):
12
12
  if not args or '-l' in args:
13
- return GitConfig('git-config', source=Path('.'))
13
+ return GitConfig('git-config', source=p)
14
14
 
15
15
  vd.git_options = vd.git_config
16
16
 
@@ -37,7 +37,7 @@ class GitConfigColumn(Column):
37
37
 
38
38
 
39
39
  class GitConfig(GitSheet):
40
- help = '''
40
+ guide = '''
41
41
  # git config
42
42
  Add, edit, or delete git config options.
43
43
 
@@ -0,0 +1,169 @@
1
+ from visidata import vd, VisiData, ItemColumn, RowColorizer, AttrDict, Column
2
+
3
+ from .gitsheet import GitSheet
4
+
5
+ vd.option('git_diff_algo', 'minimal', 'algorithm to use for git diff')
6
+ vd.theme_option('color_git_hunk_add', 'green', 'color for added hunk lines')
7
+ vd.theme_option('color_git_hunk_del', 'red', 'color for deleted hunk lines')
8
+ vd.theme_option('color_git_hunk_diff', 'yellow', 'color for hunk diffs')
9
+
10
+ @VisiData.api
11
+ def git_diff(vd, p, args):
12
+ return GitDiffSheet('git-diff', source=p, gitargs=args)
13
+
14
+ def _parseStartCount(s):
15
+ sc = s.split(',')
16
+ if len(sc) == 2:
17
+ return sc
18
+ if len(sc) == 1:
19
+ return sc[0], 1
20
+
21
+
22
+ class GitDiffSheet(GitSheet):
23
+ columns = [
24
+ ItemColumn('a_fn', width=0),
25
+ ItemColumn('fn', 'b_fn', width=30, hoffset=-28),
26
+ ItemColumn('a_lineno', type=int, width=0),
27
+ ItemColumn('lineno', 'b_lineno', type=int, width=8),
28
+ Column('count', width=10, getter=lambda c,r: c.sheet.hunkCount(r)),
29
+ ItemColumn('context'),
30
+ ItemColumn('lines', type=''.join),
31
+ ]
32
+
33
+ guide = '''# {sheet.cursorRow.a_fn}
34
+ {sheet.cursorLines}'''
35
+
36
+ def hunkCount(self, row):
37
+ return f'-{row.a_count}/+{row.b_count}'
38
+
39
+ @property
40
+ def cursorLines(self):
41
+ r = ''
42
+ for line in self.cursorRow.lines[2:]:
43
+ if line.startswith('-'):
44
+ line = '[:git_hunk_del]' + line + '[/]'
45
+ elif line.startswith('+'):
46
+ line = '[:git_hunk_add]' + line + '[/]'
47
+
48
+ r += line + '\n'
49
+ r = r[4:]
50
+ return r
51
+
52
+ def iterload(self):
53
+ current_hunk = None
54
+
55
+ for line in self.git_lines('diff --patch --inter-hunk-context=2 --find-renames --no-color --no-prefix', *self.gitargs):
56
+ if line.startswith('diff'):
57
+ diff_started = True
58
+ continue
59
+ if not diff_started:
60
+ continue
61
+
62
+ if line.startswith('---'):
63
+ hunk_lines = [line] # new file
64
+ leftfn = line[4:]
65
+ elif line.startswith('+++'):
66
+ hunk_lines.append(line)
67
+ rightfn = line[4:]
68
+ elif line.startswith('@@'):
69
+ hunk_lines.append(line)
70
+ _, linenums, context = line.split('@@')
71
+ leftlinenums, rightlinenums = linenums.split()
72
+ leftstart, leftcount = _parseStartCount(leftlinenums[1:])
73
+ rightstart, rightcount = _parseStartCount(rightlinenums[1:])
74
+ current_hunk = AttrDict(
75
+ a_fn=leftfn,
76
+ b_fn=rightfn,
77
+ context=context,
78
+ a_lineno=int(leftstart),
79
+ a_count=0,
80
+ b_lineno=int(rightstart),
81
+ b_count=0,
82
+ lines=hunk_lines
83
+ )
84
+ yield current_hunk
85
+ hunk_lines = hunk_lines[:2] # keep file context only
86
+
87
+ elif line[0] in ' +-':
88
+ current_hunk.lines.append(line)
89
+ if line[0] == '+':
90
+ current_hunk.a_count += 1
91
+ elif line[0] == '-':
92
+ current_hunk.b_count += 1
93
+
94
+ def openRow(self, row):
95
+ return HunkViewer(f'{row.a_fn}:{row.a_lineno}', source=self.source, hunks=[row])
96
+
97
+
98
+ class HunkViewer(GitSheet):
99
+ colorizers = [
100
+ RowColorizer(4, 'color_git_hunk_add', lambda s,c,r,v: r and r.old != r.new and r.old is None),
101
+ RowColorizer(4, 'color_git_hunk_del', lambda s,c,r,v: r and r.old != r.new and r.new is None),
102
+ RowColorizer(5, 'color_git_hunk_diff', lambda s,c,r,v: r and r.old != r.new and r.new is not None and r.old is not None),
103
+ ]
104
+ columns = [
105
+ ItemColumn('1', 'old', width=40),
106
+ ItemColumn('2', 'new', width=40),
107
+ ]
108
+
109
+ def draw(self, scr):
110
+ self.column('1').width=self.windowWidth//2-1
111
+ self.column('2').width=self.windowWidth//2-1
112
+ super().draw(scr)
113
+
114
+ def iterload(self):
115
+ nextDelIdx = None
116
+ for hunk in self.hunks:
117
+ for line in hunk.lines[3:]: # diff without the patch headers
118
+ typech = line[0]
119
+ line = line[1:]
120
+ if typech == '-': # deleted
121
+ yield AttrDict(hunk=hunk, type=typech, old=line)
122
+ if nextDelIdx is None:
123
+ nextDelIdx = len(self.rows)-1
124
+ elif typech == '+': # added
125
+ if nextDelIdx is not None:
126
+ if nextDelIdx < len(self.rows):
127
+ self.rows[nextDelIdx].new = line
128
+ nextDelIdx += 1
129
+ continue
130
+
131
+ yield AttrDict(hunk=hunk, type=typech, new=line)
132
+ nextDelIdx = None
133
+ elif typech == ' ': # unchanged
134
+ yield AttrDict(hunk=hunk, type=typech, old=line, new=line)
135
+ nextDelIdx = None
136
+ else:
137
+ continue # header
138
+
139
+
140
+ HunkViewer.addCommand('2', 'git-apply-hunk', 'source.git_apply(cursorRow.hunk, "--cached"); reload()', 'apply this hunk to the index and move to the next hunk')
141
+ HunkViewer.addCommand('1', 'git-remove-hunk', 'source.git_apply(cursorRow.hunk, "--reverse"); reload()', 'remove this hunk from staging')
142
+ HunkViewer.addCommand('Enter', 'git-skip-hunk', 'hunks.pop(0); reload()', 'move to the next hunk without applying this hunk')
143
+ HunkViewer.addCommand('d', 'delete-line', 'source[7].pop(cursorRow[3]); reload()', 'delete a line from the patch')
144
+
145
+ #HunksSheet.addCommand('g^J', 'git-diff-selected', 'vd.push(HunkViewer(selectedRows or rows, source=sheet))', 'view the diffs for the selected hunks (or all hunks)')
146
+
147
+ @GitDiffSheet.api
148
+ def git_apply(sheet, row, *args):
149
+ sheet.git('apply -p0 -', *args, _in='\n'.join(row.lines)+'\n')
150
+
151
+ c = sheet.hunkCount(row)
152
+ vd.status(f'applied hunk ({c})')
153
+ sheet.reload()
154
+
155
+
156
+ #DiffSheet.addCommand('[', '', 'cursorRowIndex = findDiffRow(cursorCol.refnum, cursorRowIndex, -1)', 'go to previous diff')
157
+ #DiffSheet.addCommand(']', '', 'cursorRowIndex = findDiffRow(cursorCol.refnum, cursorRowIndex, +1)', 'go to next diff')
158
+
159
+ GitDiffSheet.addCommand('a', 'git-add-hunk', 'git_apply(cursorRow, "--cached")', 'apply this hunk to the index')
160
+
161
+ vd.addMenuItems('''
162
+ Git > Stage > current hunk > git-add-hunk
163
+ Git > Stage > current hunk > git-add-hunk
164
+ ''')
165
+
166
+ vd.addGlobals(
167
+ GitDiffSheet=GitDiffSheet,
168
+ HunkViewer=HunkViewer
169
+ )