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
visidata/metasheets.py CHANGED
@@ -2,7 +2,7 @@ import collections
2
2
 
3
3
  from visidata import globalCommand, BaseSheet, Column, options, vd, anytype, ENTER, asyncthread, Sheet, IndexSheet
4
4
  from visidata import CellColorizer, RowColorizer, JsonLinesSheet, AttrDict
5
- from visidata import ColumnAttr, ColumnItem
5
+ from visidata import ColumnAttr, ItemColumn
6
6
  from visidata import TsvSheet, Path, Option
7
7
  from visidata import undoAttrFunc, VisiData, vlen
8
8
 
@@ -11,20 +11,30 @@ vd.option('visibility', 0, 'visibility level (0=low, 1=high)')
11
11
  vd_system_sep = '\t'
12
12
 
13
13
 
14
- @BaseSheet.lazy_property
15
- def optionsSheet(sheet):
16
- return OptionsSheet(sheet.name+"_options", source=sheet)
17
-
18
- @VisiData.lazy_property
19
- def globalOptionsSheet(vd):
20
- return OptionsSheet('global_options', source='global')
21
-
22
-
23
14
  class ColumnsSheet(Sheet):
24
15
  rowtype = 'columns'
25
16
  _rowtype = Column
26
17
  _coltype = ColumnAttr
27
18
  precious = False
19
+ guide = '''# Columns Sheet
20
+ This is a list of {sheet.nSourceCols} columns on {sheet.displaySource}.
21
+ Edit values on this sheet to change the appearance of the source sheet.
22
+ For example, edit the _{sheet.cursorCol.name}_ column to **{sheet.cursorCol.formatted_help}**.
23
+
24
+ Some new commands on this sheet operate on all selected columns on the source sheet:
25
+
26
+ - {help.commands.hide_selected}
27
+ - {help.commands.key_selected}
28
+ - {help.commands.key_off_selected}
29
+ - {help.commands.type_int_selected}
30
+ - or `g` with any standard type to set type of selected source columns to that type
31
+
32
+ ## As usual
33
+
34
+ - {help.commands.setcol_input}
35
+ {sheet.help_columns}
36
+ '''
37
+
28
38
  class ValueColumn(Column):
29
39
  'passthrough to the value on the source cursorRow'
30
40
  def calcValue(self, srcCol):
@@ -34,17 +44,18 @@ class ColumnsSheet(Sheet):
34
44
 
35
45
  columns = [
36
46
  ColumnAttr('sheet', type=str),
37
- ColumnAttr('name'),
47
+ ColumnAttr('name', help='rename the column on the source sheet'),
38
48
  ColumnAttr('keycol', type=int, width=0),
39
- ColumnAttr('width', type=int),
40
- ColumnAttr('height', type=int),
49
+ ColumnAttr('width', type=int, help='set the column width (`0` to hide completely)'),
50
+ ColumnAttr('height', type=int, max_help=1, help='set a maximum height for the row, if this column will fill it'),
41
51
  ColumnAttr('hoffset', type=int, width=0),
42
52
  ColumnAttr('voffset', type=int, width=0),
43
- ColumnAttr('type', 'typestr'),
44
- ColumnAttr('fmtstr'),
45
- ColumnAttr('formatter'),
46
- ValueColumn('value'),
47
- ColumnAttr('expr'),
53
+ ColumnAttr('type', 'typestr', help='convert all values to a specific type'),
54
+ ColumnAttr('fmtstr', help='use a custom format string, either C-style (`%0.4f`) or Python-style (`{{:0.4f}}`)'),
55
+ ColumnAttr('formatter', max_help=1, help='use a custom format function (**{col.help_formatters}**)'),
56
+ ColumnAttr('displayer', max_help=1, help='use a custom display function (**{col.help_displayers}**)'),
57
+ ValueColumn('value', help='change the value of this cell on the source sheet'),
58
+ ColumnAttr('expr', max_help=1, help='change the main column parameter'),
48
59
  ColumnAttr('ncalcs', type=int, width=0, cache=False),
49
60
  ColumnAttr('maxtime', type=float, width=0, cache=False),
50
61
  ColumnAttr('totaltime', type=float, width=0, cache=False),
@@ -54,13 +65,18 @@ class ColumnsSheet(Sheet):
54
65
  RowColorizer(7, 'color_key_col', lambda s,c,r,v: r and r.keycol),
55
66
  RowColorizer(8, 'color_hidden_col', lambda s,c,r,v: r and r.hidden),
56
67
  ]
57
- def reload(self):
68
+
69
+ @property
70
+ def nSourceCols(self):
71
+ return sum(vs.nCols for vs in self.source)
72
+
73
+ def loader(self):
58
74
  if len(self.source) == 1:
59
75
  self.rows = self.source[0].columns
60
76
  self.cursorRowIndex = self.source[0].cursorColIndex
61
77
  self.columns[0].hide() # hide 'sheet' column if only one sheet
62
78
  else:
63
- self.rows = [col for vs in self.source for col in vs.visibleCols if vs is not self]
79
+ self.rows = [col for vs in self.source for col in vs.visibleCols if isinstance(vs, Sheet) and vs is not self]
64
80
 
65
81
  def newRow(self):
66
82
  c = type(self.source[0])._coltype()
@@ -81,110 +97,14 @@ VisiDataMetaSheet.options.row_delimiter = '\n'
81
97
  VisiDataMetaSheet.options.encoding = 'utf-8'
82
98
 
83
99
 
84
- class OptionsSheet(Sheet):
85
- _rowtype = Option # rowdef: Option
86
- rowtype = 'options'
87
- precious = False
88
- columns = (
89
- Column('option', getter=lambda col,row: row.name),
90
- Column('value',
91
- getter=lambda col,row: col.sheet.diffOption(row.name),
92
- setter=lambda col,row,val: options.set(row.name, val, col.sheet.source),
93
- ),
94
- Column('default', getter=lambda col,row: options.getdefault(row.name)),
95
- Column('description', width=40, getter=lambda col,row: options._get(row.name, 'default').helpstr),
96
- ColumnAttr('replayable'),
97
- )
98
- colorizers = [
99
- CellColorizer(3, None, lambda s,c,r,v: v.value if r and c in s.columns[1:3] and r.name.startswith('color_') else None),
100
- ]
101
- nKeys = 1
102
-
103
- def diffOption(self, optname):
104
- return options.getonly(optname, self.source, '')
105
-
106
- def editOption(self, row):
107
- currentValue = options.getobj(row.name, self.source)
108
- vd.addUndo(options.set, row.name, currentValue, self.source)
109
- if isinstance(row.value, bool):
110
- options.set(row.name, not currentValue, self.source)
111
- else:
112
- options.set(row.name, self.editCell(1, value=currentValue), self.source)
113
-
114
- def reload(self):
115
- self.rows = []
116
- for k in options.keys():
117
- opt = options._get(k)
118
- self.addRow(opt)
119
- self.columns[1].name = 'global_value' if self.source == 'global' else 'sheet_value'
120
-
121
-
122
- vd._lastInputs = collections.defaultdict(dict) # [input_type] -> {'input': anything}
123
-
124
- class LastInputsSheet(JsonLinesSheet):
125
- columns = [
126
- ColumnItem('type'),
127
- ColumnItem('input'),
128
- ]
129
- def __init__(self, *args, **kwargs):
130
- super().__init__(*args, **kwargs)
131
- self.colnames = {col.name:col for col in self.columns}
132
-
133
- def addRow(self, row, **kwargs):
134
- 'Update lastInputs before adding row.'
135
- row = AttrDict(row)
136
- if row.input in vd._lastInputs[row.type]:
137
- del vd._lastInputs[row.type][row.input] # so will be added as last entry
138
- vd._lastInputs[row.type][row.input] = 1
139
- return super().addRow(row, **kwargs)
140
-
141
- def appendRow(self, row):
142
- 'Append *row* (AttrDict with *type* and *input*) directly to source.'
143
- hist = self.history(row.type)
144
- if hist and hist[-1] == row.input:
145
- return
146
-
147
- self.addRow(row)
148
-
149
- if self.source:
150
- with self.source.open_text(mode='a', encoding=self.options.encoding) as fp:
151
- import json
152
- fp.write(json.dumps(row) + '\n')
153
-
154
- def history(self, t):
155
- 'Return list of inputs in category *t*, with last element being the most recently added.'
156
- return list(vd._lastInputs[t].keys())
157
-
158
-
159
- @VisiData.lazy_property
160
- def lastInputsSheet(vd):
161
- name = options.input_history
162
-
163
- if not name:
164
- return LastInputsSheet('last_inputs', source=None, rows=[])
165
-
166
- p = Path(name)
167
- if not p.is_absolute():
168
- p = Path(options.visidata_dir)/f'{name}.jsonl'
169
-
170
- vs = LastInputsSheet(name, source=p)
171
- try:
172
- vs.reload.__wrapped__(vs)
173
- except FileNotFoundError:
174
- pass
175
- except Exception as e:
176
- vd.exceptionCaught(e)
177
-
178
- return vs
179
-
180
-
181
100
  @VisiData.property
182
101
  def allColumnsSheet(vd):
183
102
  return ColumnsSheet("all_columns", source=vd.stackedSheets)
184
103
 
104
+
185
105
  @VisiData.api
186
106
  def save_visidatarc(vd, p, vs):
187
- with p.open_text(mode='w') as fp:
107
+ with p.open(mode='w') as fp:
188
108
  for opt in vs.rows:
189
109
  rval = repr(opt.value)
190
110
  defopt = vd.options._get(opt.name, 'default')
@@ -192,7 +112,9 @@ def save_visidatarc(vd, p, vs):
192
112
  fp.write(f'{leading}options.{opt.name:25s} = {rval:10s} # {defopt.helpstr}\n')
193
113
 
194
114
 
195
- @ColumnsSheet.command('&', 'join-cols', 'add column from concatenating selected source columns')
115
+ ColumnsSheet.addCommand('', 'join-cols', 'sheet.join_cols()', 'add column from concatenating selected source columns')
116
+
117
+ @ColumnsSheet.api
196
118
  def join_cols(sheet):
197
119
  cols = sheet.onlySelectedRows
198
120
  destSheet = cols[0].sheet
@@ -209,40 +131,36 @@ def join_cols(sheet):
209
131
 
210
132
  # copy vd.sheets so that ColumnsSheet itself isn't included (for recalc in addRow)
211
133
  globalCommand('gC', 'columns-all', 'vd.push(vd.allColumnsSheet)', 'open Columns Sheet: edit column properties for all visible columns from all sheets on the sheets stack')
212
- globalCommand('O', 'options-global', 'vd.push(vd.globalOptionsSheet)', 'open Options Sheet: edit global options (apply to all sheets)')
213
-
214
- BaseSheet.addCommand('zO', 'options-sheet', 'vd.push(sheet.optionsSheet)', 'open Options Sheet: edit sheet options (apply to current sheet only)')
215
- BaseSheet.addCommand(None, 'open-inputs', 'vd.push(lastInputsSheet)', '')
216
134
 
217
135
  Sheet.addCommand('C', 'columns-sheet', 'vd.push(ColumnsSheet(name+"_columns", source=[sheet]))', 'open Columns Sheet: edit column properties for current sheet')
218
136
 
219
137
  # used ColumnsSheet, affecting the 'row' (source column)
220
- ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'toggle selected rows as key columns on source sheet')
221
- ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected rows as key columns on source sheet')
138
+ ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'toggle selected source columns as key columns')
139
+ ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected source columns as key columns')
222
140
 
223
- ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected columns on source sheet')
141
+ ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected source columns')
224
142
  ColumnsSheet.addCommand(None, 'resize-source-rows-max', 'for c in selectedRows or [cursorRow]: c.setWidth(c.getMaxWidth(c.sheet.visibleRows))', 'adjust widths of selected source columns')
225
143
 
226
- ColumnsSheet.addCommand('g%', 'type-float-selected', 'onlySelectedRows.type=float', 'set type of selected columns to float')
227
- ColumnsSheet.addCommand('g#', 'type-int-selected', 'onlySelectedRows.type=int', 'set type of selected columns to int')
228
- ColumnsSheet.addCommand('gz#', 'type-len-selected', 'onlySelectedRows.type=vlen', 'set type of selected columns to len')
229
- ColumnsSheet.addCommand('g@', 'type-date-selected', 'onlySelectedRows.type=date', 'set type of selected columns to date')
230
- ColumnsSheet.addCommand('g$', 'type-currency-selected', 'onlySelectedRows.type=currency', 'set type of selected columns to currency')
231
- ColumnsSheet.addCommand('g~', 'type-string-selected', 'onlySelectedRows.type=str', 'set type of selected columns to str')
232
- ColumnsSheet.addCommand('gz~', 'type-any-selected', 'onlySelectedRows.type=anytype', 'set type of selected columns to anytype')
233
- ColumnsSheet.addCommand('gz%', 'type-floatsi-selected', 'onlySelectedRows.type=floatsi', 'set type of selected columns to floatsi')
234
- ColumnsSheet.addCommand('', 'type-floatlocale-selected', 'onlySelectedRows.type=floatlocale', 'set type of selected columns to float using system locale')
235
-
236
- OptionsSheet.addCommand('d', 'unset-option', 'options.unset(cursorRow.name, str(source))', 'remove option override for this context')
237
- OptionsSheet.addCommand(None, 'edit-option', 'editOption(cursorRow)', 'edit option at current row')
238
- OptionsSheet.bindkey('e', 'edit-option')
239
- OptionsSheet.bindkey(ENTER, 'edit-option')
144
+ ColumnsSheet.addCommand('g%', 'type-float-selected', 'onlySelectedRows.type=float', 'set type of selected source columns to float')
145
+ ColumnsSheet.addCommand('g#', 'type-int-selected', 'onlySelectedRows.type=int', 'set type of selected source columns to int')
146
+ ColumnsSheet.addCommand('gz#', 'type-len-selected', 'onlySelectedRows.type=vlen', 'set type of selected source columns to len')
147
+ ColumnsSheet.addCommand('g@', 'type-date-selected', 'onlySelectedRows.type=date', 'set type of selected source columns to date')
148
+ ColumnsSheet.addCommand('g$', 'type-currency-selected', 'onlySelectedRows.type=currency', 'set type of selected source columns to currency')
149
+ ColumnsSheet.addCommand('g~', 'type-string-selected', 'onlySelectedRows.type=str', 'set type of selected source columns to str')
150
+ ColumnsSheet.addCommand('gz~', 'type-any-selected', 'onlySelectedRows.type=anytype', 'set type of selected source columns to anytype')
151
+ ColumnsSheet.addCommand('gz%', 'type-floatsi-selected', 'onlySelectedRows.type=floatsi', 'set type of selected source columns to floatsi')
152
+ ColumnsSheet.addCommand('', 'type-floatlocale-selected', 'onlySelectedRows.type=floatlocale', 'set type of selected source columns to float using system locale')
153
+
240
154
  MetaSheet.options.header = 0
241
155
 
242
156
 
243
157
  vd.addGlobals({
244
158
  'ColumnsSheet': ColumnsSheet,
245
159
  'MetaSheet': MetaSheet,
246
- 'OptionsSheet': OptionsSheet,
247
160
  'VisiDataMetaSheet': VisiDataMetaSheet,
248
161
  })
162
+
163
+ vd.addMenuItems('''
164
+ View > Columns > this sheet > columns-sheet
165
+ View > Columns > all sheets > columns-all
166
+ ''')
visidata/modify.py CHANGED
@@ -1,9 +1,37 @@
1
1
  from copy import copy
2
- from visidata import *
3
2
 
4
- vd.option('color_add_pending', 'green', 'color for rows pending add')
5
- vd.option('color_change_pending', 'reverse yellow', 'color for cells pending modification')
6
- vd.option('color_delete_pending', 'red', 'color for rows pending delete')
3
+ from visidata import vd, VisiData, asyncthread
4
+ from visidata import Sheet, RowColorizer, CellColorizer, Column, BaseSheet, Progress
5
+
6
+ vd.theme_option('color_add_pending', 'green', 'color for rows pending add')
7
+ vd.theme_option('color_change_pending', 'reverse yellow', 'color for cells pending modification')
8
+ vd.theme_option('color_delete_pending', 'red', 'color for rows pending delete')
9
+ vd.option('overwrite', 'c', 'overwrite existing files {y=yes|c=confirm|n=no}')
10
+
11
+ vd.optalias('readonly', 'overwrite', 'n')
12
+ vd.optalias('ro', 'overwrite', 'n')
13
+ vd.optalias('y', 'overwrite', 'y')
14
+
15
+
16
+ @VisiData.api
17
+ def couldOverwrite(vd) -> bool:
18
+ 'Return True if overwrite might be allowed.'
19
+ return vd.options.overwrite.startswith(('y','c'))
20
+
21
+
22
+ @VisiData.api
23
+ def confirmOverwrite(vd, path, msg:str=''):
24
+ 'Fail if file exists and overwrite not allowed.'
25
+ if path is None or path.exists():
26
+ msg = msg or f'{path.given} exists. overwrite? '
27
+ ow = vd.options.overwrite
28
+ if ow.startswith('c'): # confirm
29
+ vd.confirm(msg)
30
+ elif ow.startswith('y'): # yes/always
31
+ pass
32
+ else: #1805 empty/no/never/readonly
33
+ vd.fail('overwrite disabled')
34
+ return True
7
35
 
8
36
  # deferred cached
9
37
  @Sheet.lazy_property
@@ -20,7 +48,7 @@ def _deferredDels(sheet):
20
48
 
21
49
  Sheet.colorizers += [
22
50
  RowColorizer(9, 'color_add_pending', lambda s,c,r,v: s.rowid(r) in s._deferredAdds),
23
- CellColorizer(8, 'color_change_pending', lambda s,c,r,v: s.isChanged(c, r)),
51
+ CellColorizer(8, 'color_change_pending', lambda s,c,r,v: c and (r is not None) and s.isChanged(c, r)),
24
52
  RowColorizer(9, 'color_delete_pending', lambda s,c,r,v: s.isDeleted(r)),
25
53
  ]
26
54
 
@@ -89,9 +117,9 @@ def addRows(sheet, rows, index=None, undo=True):
89
117
  'Add *rows* after row at *index*.'
90
118
  addedRows = {}
91
119
  if index is None: index=len(sheet.rows)
92
- for row in Progress(rows, gerund='adding'):
120
+ for i, row in enumerate(Progress(rows, gerund='adding')):
93
121
  addedRows[sheet.rowid(row)] = row
94
- sheet.addRow(row, index=index+1)
122
+ sheet.addRow(row, index=index+i+1)
95
123
 
96
124
  if sheet.defer:
97
125
  sheet.rowAdded(row)
@@ -137,8 +165,11 @@ def deleteBy(sheet, func, commit=False, undo=True):
137
165
  if r is newCursorRow:
138
166
  sheet.cursorRowIndex = len(sheet.rows)-1
139
167
  else:
140
- sheet.deleteSourceRow(r)
141
- ndeleted += 1
168
+ try:
169
+ sheet.commitDeleteRow(r)
170
+ ndeleted += 1
171
+ except Exception as e:
172
+ vd.exceptionCaught(e)
142
173
 
143
174
  if undo:
144
175
  vd.addUndo(setattr, sheet, 'rows', oldrows)
@@ -177,17 +208,30 @@ def getSourceValue(col, row):
177
208
  @Sheet.api
178
209
  def commitAdds(self):
179
210
  'Return the number of rows that have been marked for deferred add-row. Clear the marking.'
180
- nadded = len(self._deferredAdds.values())
181
- if nadded:
182
- vd.status('added %s %s' % (nadded, self.rowtype))
211
+ nadded = 0
212
+ nerrors = 0
213
+ for row in self._deferredAdds.values():
214
+ try:
215
+ self.commitAddRow(row)
216
+ nadded += 1
217
+ except Exception as e:
218
+ vd.exceptionCaught(e)
219
+ nerrors += 1
220
+
221
+ if nadded or nerrors:
222
+ vd.status(f'added {nadded} {self.rowtype} ({nerrors} errors)')
223
+
183
224
  self._deferredAdds.clear()
184
225
  return nadded
185
226
 
227
+
186
228
  @Sheet.api
187
- def commitMods(self):
188
- 'Return the number of modifications (that are not deferred deletes or adds) that been marked for defer mod. Change value to mod for row in col. Clear the marking.'
229
+ def commitMods(sheet):
230
+ 'Commit all deferred modifications (that are not from rows added or deleted in this commit. Return number of cells changed.'
231
+ _, deferredmods, _ = sheet.getDeferredChanges()
232
+
189
233
  nmods = 0
190
- for row, rowmods in self._deferredMods.values():
234
+ for row, rowmods in deferredmods.values():
191
235
  for col, val in rowmods.items():
192
236
  try:
193
237
  col.putValue(row, val)
@@ -195,7 +239,7 @@ def commitMods(self):
195
239
  except Exception as e:
196
240
  vd.exceptionCaught(e)
197
241
 
198
- self._deferredMods.clear()
242
+ sheet._deferredMods.clear()
199
243
  return nmods
200
244
 
201
245
  @Sheet.api
@@ -207,9 +251,16 @@ def commitDeletes(self):
207
251
  vd.status('deleted %s %s' % (ndeleted, self.rowtype))
208
252
  return ndeleted
209
253
 
254
+
255
+ @Sheet.api
256
+ def commitAddRow(self, row):
257
+ 'To commit an added row. Override per sheet type.'
258
+
259
+
210
260
  @Sheet.api
211
- def deleteSourceRow(sheet, row):
212
- pass
261
+ def commitDeleteRow(self, row):
262
+ 'To commit a deleted row. Override per sheet type.'
263
+
213
264
 
214
265
  @asyncthread
215
266
  @Sheet.api
@@ -219,8 +270,6 @@ def putChanges(sheet):
219
270
  sheet.commitMods()
220
271
  sheet.commitDeletes()
221
272
 
222
- vd.saveSheets(Path(sheet.source), sheet, confirm_overwrite=False)
223
-
224
273
  # clear after save, to ensure cstr (in commit()) is aware of deletes
225
274
  sheet._deferredDels.clear()
226
275
 
@@ -269,10 +318,8 @@ def commit(sheet, *rows):
269
318
 
270
319
  adds, mods, deletes = sheet.getDeferredChanges()
271
320
  cstr = sheet.changestr(adds, mods, deletes)
272
- path = sheet.source
273
321
 
274
- if sheet.options.confirm_overwrite:
275
- vd.confirm('really %s? ' % cstr)
322
+ vd.confirmOverwrite(sheet.rootSheet().source, 'really ' + cstr + '? ')
276
323
 
277
324
  sheet.putChanges()
278
325
  sheet.hasBeenModified = False
@@ -288,3 +335,11 @@ Sheet.addCommand('za', 'addcol-new', 'addColumnAtCursor(SettableColumn(input("co
288
335
  Sheet.addCommand('gza', 'addcol-bulk', 'addColumnAtCursor(*(SettableColumn() for c in range(int(input("add columns: ")))))', 'append N empty columns')
289
336
 
290
337
  Sheet.addCommand('z^S', 'commit-sheet', 'commit()', 'commit changes back to source. not undoable!')
338
+
339
+ vd.addMenuItems('''
340
+ File > Save > changes to source > commit-sheet
341
+ Row > Add > one row
342
+ Row > Add > multiple rows
343
+ Column > Add column > empty > one column > addcol-new
344
+ Column > Add column > empty > multiple columns > addcol-bulk
345
+ ''')
visidata/motd.py CHANGED
@@ -5,7 +5,7 @@ a motd file from a url. The file may be text or unheaded TSV, with one message
5
5
 
6
6
  Any Exception ends the thread silently.
7
7
 
8
- options.motd_url may be set to another URL, or empty to disable entirely.
8
+ options.motd_url may be set to another URL, or empty string to disable entirely.
9
9
  '''
10
10
 
11
11
  import random
@@ -15,7 +15,7 @@ from visidata import options, asyncsingle, vd, VisiData
15
15
  vd.option('motd_url', 'https://visidata.org/motd-'+vd.version, 'source of randomized startup messages', sheettype=None)
16
16
 
17
17
 
18
- vd.motd = ''
18
+ vd.motd = 'Support VisiData: https://github.com/sponsors/saulpw'
19
19
 
20
20
  @VisiData.api
21
21
  @asyncsingle
@@ -25,6 +25,6 @@ def domotd(vd):
25
25
  p = vd.urlcache(options.motd_url, days=1)
26
26
  line = random.choice(list(p))
27
27
  vd.motd = line.split('\t')[0]
28
- vd.status(vd.motd, priority=-1)
29
28
  except Exception:
30
29
  pass
30
+ vd.status(vd.motd, priority=-1)
visidata/mouse.py ADDED
@@ -0,0 +1,137 @@
1
+ import curses
2
+
3
+ from visidata import vd, VisiData, BaseSheet, Sheet, AttrDict
4
+
5
+
6
+ # registry of mouse events. cleared before every draw cycle.
7
+ vd.mousereg = [] # list of AttrDict(y=, x=, h=, w=, buttonfuncs=dict)
8
+
9
+ # sheet mouse position for current mouse event
10
+ BaseSheet.init('mouseX', int)
11
+ BaseSheet.init('mouseY', int)
12
+
13
+
14
+ @VisiData.after
15
+ def initCurses(vd):
16
+ curses.MOUSE_ALL = 0xffffffff
17
+ curses.mousemask(curses.MOUSE_ALL if vd.options.mouse_interval else 0)
18
+ curses.mouseinterval(vd.options.mouse_interval)
19
+ curses.mouseEvents = {}
20
+
21
+ for k in dir(curses):
22
+ if k.startswith('BUTTON') or k in ('REPORT_MOUSE_POSITION', '2097152'):
23
+ curses.mouseEvents[getattr(curses, k)] = k
24
+
25
+
26
+ @VisiData.after
27
+ def clearCaches(vd):
28
+ vd.mousereg = []
29
+
30
+
31
+ @VisiData.api
32
+ def onMouse(vd, scr, x, y, w, h, **kwargs):
33
+ px, py = vd.getrootxy(scr)
34
+ e = AttrDict(x=x+px, y=y+py, w=w, h=h, buttonfuncs=kwargs)
35
+ vd.mousereg.append(e)
36
+
37
+
38
+ @VisiData.api
39
+ def getMouse(vd, _x, _y, button):
40
+ for reg in vd.mousereg[::-1]:
41
+ if reg.x <= _x < reg.x+reg.w and reg.y <= _y < reg.y+reg.h and button in reg.buttonfuncs:
42
+ return reg.buttonfuncs[button]
43
+
44
+
45
+ @VisiData.api
46
+ def parseMouse(vd, **kwargs):
47
+ 'Return list of mouse interactions (clicktype, y, x, name, scr) for curses screens given in kwargs as name:scr.'
48
+
49
+ devid, x, y, z, bstate = curses.getmouse()
50
+
51
+ clicktype = ''
52
+ if bstate & curses.BUTTON_CTRL:
53
+ clicktype += "Ctrl+"
54
+ bstate &= ~curses.BUTTON_CTRL
55
+ if bstate & curses.BUTTON_ALT:
56
+ clicktype += "Alt+"
57
+ bstate &= ~curses.BUTTON_ALT
58
+ if bstate & curses.BUTTON_SHIFT:
59
+ clicktype += "Shift+"
60
+ bstate &= ~curses.BUTTON_SHIFT
61
+
62
+ keystroke = clicktype + curses.mouseEvents.get(bstate, str(bstate))
63
+ ret = AttrDict(keystroke=keystroke, y=y, x=x, found=[])
64
+ for winname, winscr in kwargs.items():
65
+ px, py = vd.getrootxy(winscr)
66
+ mh, mw = winscr.getmaxyx()
67
+ if py <= y < py+mh and px <= x < px+mw:
68
+ ret.found.append(winname)
69
+ # vd.debug(f'{keystroke} at ({x-px}, {y-py}) in window {winname} {winscr}')
70
+
71
+ return ret
72
+
73
+
74
+ @VisiData.api
75
+ def handleMouse(vd, sheet):
76
+ try:
77
+ vd.keystrokes = ''
78
+ pct = vd.windowConfig['pct']
79
+ topPaneActive = ((vd.activePane == 2 and pct < 0) or (vd.activePane == 1 and pct > 0))
80
+ bottomPaneActive = ((vd.activePane == 1 and pct < 0) or (vd.activePane == 2 and pct > 0))
81
+ r = None
82
+ r = vd.parseMouse(top=vd.winTop, bot=vd.winBottom, menu=vd.scrMenu)
83
+ if (bottomPaneActive and 'top' in r.found) or (topPaneActive and 'bot' in r.found):
84
+ vd.activePane = 1 if vd.activePane == 2 else 2
85
+ sheet = vd.activeSheet
86
+
87
+ f = vd.getMouse(r.x, r.y, r.keystroke)
88
+ winx, winy = vd.getrootxy(sheet._scr)
89
+ sheet.mouseX, sheet.mouseY = r.x-winx, r.y-winy
90
+ if f:
91
+ if isinstance(f, str):
92
+ if f.startswith('onclick'):
93
+ if '://' in f:
94
+ vd.launchBrowser(f[8:])
95
+ else:
96
+ sheet.execCommand(f[8:])
97
+ else:
98
+ for cmd in f.split():
99
+ sheet.execCommand(cmd)
100
+ else:
101
+ f(r.y, r.x, r.keystroke)
102
+
103
+ vd.keystrokes = vd.prettykeys(r.keystroke)
104
+ return '' # handled
105
+ except curses.error:
106
+ pass
107
+
108
+ return r.keystroke if r else ''
109
+
110
+
111
+ @Sheet.api
112
+ def visibleColAtX(sheet, x):
113
+ for vcolidx, (colx, w) in sheet._visibleColLayout.items():
114
+ if colx <= x <= colx+w:
115
+ return vcolidx
116
+
117
+
118
+ @Sheet.api
119
+ def visibleRowAtY(sheet, y):
120
+ for rowidx, (rowy, h) in sheet._rowLayout.items():
121
+ if rowy <= y <= rowy+h-1:
122
+ return rowidx
123
+
124
+
125
+ @Sheet.command('BUTTON1_PRESSED', 'go-mouse', 'set cursor to row and column where mouse was clicked')
126
+ def go_mouse(sheet):
127
+ ridx = sheet.visibleRowAtY(sheet.mouseY)
128
+ if ridx is not None:
129
+ sheet.cursorRowIndex = ridx
130
+ cidx = sheet.visibleColAtX(sheet.mouseX)
131
+ if cidx is not None:
132
+ sheet.cursorVisibleColIndex = cidx
133
+
134
+ Sheet.addCommand(None, 'scroll-mouse', 'sheet.topRowIndex=cursorRowIndex-mouseY+1', 'scroll to mouse cursor location')
135
+
136
+ Sheet.addCommand('ScrollUp', 'scroll-up', 'cursorDown(options.scroll_incr); sheet.topRowIndex += options.scroll_incr', 'scroll one row up')
137
+ Sheet.addCommand('ScrollDown', 'scroll-down', 'cursorDown(-options.scroll_incr); sheet.topRowIndex -= options.scroll_incr', 'scroll one row down')