visidata 2.11.1__py3-none-any.whl → 3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) 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 +65 -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 +59 -50
  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 +5 -1
  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 +30 -5
  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 +163 -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} +75 -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 +180 -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 +17 -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 +48 -10
  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 +301 -148
  187. visidata/man/vd.txt +290 -153
  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 +50 -201
  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 +229 -257
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +113 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/conftest.py +3 -3
  213. visidata/tests/test_cliptext.py +39 -0
  214. visidata/tests/test_commands.py +62 -7
  215. visidata/tests/test_edittext.py +2 -2
  216. visidata/tests/test_features.py +17 -0
  217. visidata/tests/test_menu.py +14 -0
  218. visidata/tests/test_path.py +13 -4
  219. visidata/text_source.py +53 -0
  220. visidata/textsheet.py +10 -3
  221. visidata/theme.py +44 -0
  222. visidata/themes/__init__.py +0 -0
  223. visidata/themes/ascii8.py +84 -0
  224. visidata/themes/asciimono.py +84 -0
  225. visidata/themes/light.py +17 -0
  226. visidata/threads.py +87 -39
  227. visidata/tuiwin.py +22 -0
  228. visidata/type_currency.py +22 -3
  229. visidata/type_date.py +31 -9
  230. visidata/type_floatsi.py +5 -1
  231. visidata/undo.py +17 -5
  232. visidata/utils.py +106 -23
  233. visidata/vdobj.py +28 -17
  234. visidata/windows.py +10 -0
  235. visidata/wrappers.py +9 -3
  236. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  237. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
  238. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
  239. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  240. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
  241. visidata-3.0.dist-info/RECORD +257 -0
  242. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  243. vgit/__init__.py +0 -1
  244. vgit/gitsheet.py +0 -164
  245. visidata/layout.py +0 -44
  246. visidata/misc.py +0 -5
  247. visidata-2.11.1.data/scripts/vgit +0 -9
  248. visidata-2.11.1.dist-info/RECORD +0 -155
  249. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  250. {vgit → visidata/apps/vgit}/abort.py +0 -0
  251. /visidata/{repeat.py → features/repeat.py} +0 -0
  252. {visidata-2.11.1.data → visidata-3.0.data}/scripts/vd +0 -0
  253. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/column.py CHANGED
@@ -7,10 +7,11 @@ import re
7
7
  import time
8
8
  import json
9
9
 
10
- from visidata import options, anytype, stacktrace, vd
10
+ from visidata import options, anytype, stacktrace, vd, drawcache
11
11
  from visidata import asyncthread, dispwidth, clipstr, iterchars
12
12
  from visidata import wrapply, TypedWrapper, TypedExceptionWrapper
13
- from visidata import Extensible, AttrDict, undoAttrFunc
13
+ from visidata import Extensible, AttrDict, undoAttrFunc, ExplodingMock, MissingAttrFormatter
14
+ from visidata import getitem, setitem, getitemdef, getitemdeep, setitemdeep, getattrdeep, setattrdeep, iterchunks
14
15
 
15
16
  class InProgress(Exception):
16
17
  @property
@@ -19,31 +20,16 @@ class InProgress(Exception):
19
20
 
20
21
  INPROGRESS = TypedExceptionWrapper(None, exception=InProgress()) # sentinel
21
22
 
22
- vd.option('col_cache_size', 0, 'max number of cache entries in each cached column')
23
- vd.option('clean_names', False, 'clean column/sheet names to be valid Python identifiers', replay=True)
24
-
25
- __all__ = [
26
- 'clean_to_id',
27
- 'Column',
28
- 'setitem',
29
- 'getattrdeep',
30
- 'setattrdeep',
31
- 'getitemdef',
32
- 'ColumnAttr', 'AttrColumn',
33
- 'ColumnItem', 'ItemColumn',
34
- 'SettableColumn',
35
- 'SubColumnFunc',
36
- 'SubColumnItem',
37
- 'SubColumnAttr',
38
- 'ColumnExpr', 'ExprColumn',
39
- 'DisplayWrapper',
40
- ]
23
+ vd.option('col_cache_size', 0, 'max number of cache entries in each cached column', max_help=-1)
24
+ vd.option('disp_formatter', 'generic', 'formatter to create the text in each cell (also used by text savers)', replay=True, max_help=0)
25
+ vd.option('disp_displayer', 'generic', 'displayer to render the text in each cell', replay=False, max_help=0)
41
26
 
42
27
 
43
28
  class DisplayWrapper:
44
- def __init__(self, value=None, *, display=None, note=None, notecolor=None, error=None):
29
+ def __init__(self, value=None, *, typedval=None, text=None, note=None, notecolor=None, error=None):
45
30
  self.value = value # actual value (any type)
46
- self.display = display # displayed string
31
+ self.typedval = typedval # consistently typed value (or None)
32
+ self.text = text # displayed string
47
33
  self.note = note # single unicode character displayed in cell far right
48
34
  self.notecolor = notecolor # configurable color name (like 'color_warning')
49
35
  self.error = error # list of strings for stacktrace
@@ -54,11 +40,6 @@ class DisplayWrapper:
54
40
  def __eq__(self, other):
55
41
  return self.value == other
56
42
 
57
-
58
- def clean_to_id(s): # [Nas Banov] https://stackoverflow.com/a/3305731
59
- return re.sub(r'\W|^(?=\d)', '_', str(s)).strip('_')
60
-
61
-
62
43
  def _default_colnames():
63
44
  'A B C .. Z AA AB .. ZZ AAA .. to infinity'
64
45
  i=0
@@ -88,14 +69,14 @@ class Column(Extensible):
88
69
  - *kwargs*: other attributes to be set on this column.
89
70
  '''
90
71
  def __init__(self, name=None, *, type=anytype, cache=False, **kwargs):
91
- self.sheet = None # owning Sheet, set in .recalc() via Sheet.addColumn
72
+ self.sheet = ExplodingMock('use addColumn() on all columns') # owning Sheet, set in .recalc() via Sheet.addColumn
92
73
  if name is None:
93
74
  name = next(default_colnames)
94
75
  self.name = str(name) # display visible name
95
76
  self.fmtstr = '' # by default, use str()
96
77
  self._type = type # anytype/str/int/float/date/func
97
78
  self.getter = lambda col, row: row
98
- self.setter = lambda col, row, value: vd.fail(col.name+' column cannot be changed')
79
+ self.setter = None
99
80
  self._width = None # == 0 if hidden, None if auto-compute next time
100
81
  self.hoffset = 0 # starting horizontal (char) offset of displayed column value
101
82
  self.voffset = 0 # starting vertical (line) offset of displayed column value
@@ -103,7 +84,9 @@ class Column(Extensible):
103
84
  self.keycol = 0 # keycol index (or 0 if not key column)
104
85
  self.expr = None # Column-type-dependent parameter
105
86
  self.formatter = ''
87
+ self.displayer = ''
106
88
  self.defer = False
89
+ self.max_help = 10 # auto-hide above this disp_help level
107
90
 
108
91
  self.setCache(cache)
109
92
  for k, v in kwargs.items():
@@ -118,13 +101,17 @@ class Column(Extensible):
118
101
  ret._cachedValues = collections.OrderedDict() # an unrelated cache for copied columns
119
102
  return ret
120
103
 
104
+ def __str__(self):
105
+ return f'{type(self).__name__}:{self.name}'
106
+
107
+ def __repr__(self):
108
+ return f'<{type(self).__name__}: {self.name}>'
109
+
121
110
  def __deepcopy__(self, memo):
122
111
  return self.__copy__() # no separate deepcopy
123
112
 
124
113
  def __getstate__(self):
125
- d = {k:getattr(self, k) for k in 'name width height expr keycol formatter fmtstr voffset hoffset aggstr'.split() if hasattr(self, k)}
126
- d['type'] = self.type.__name__
127
- return d
114
+ return {k:getattr(self, k) for k in 'name typestr width height expr keycol formatter fmtstr voffset hoffset aggstr'.split() if hasattr(self, k)}
128
115
 
129
116
  def __setstate__(self, d):
130
117
  for attr, v in d.items():
@@ -145,6 +132,9 @@ class Column(Extensible):
145
132
 
146
133
  @name.setter
147
134
  def name(self, name):
135
+ self.setName(name)
136
+
137
+ def setName(self, name):
148
138
  if name is None:
149
139
  name = ''
150
140
  if isinstance(name, str):
@@ -194,6 +184,20 @@ class Column(Extensible):
194
184
  vd.addUndo(setattr, self, '_width', self.width)
195
185
  self._width = w
196
186
 
187
+ @property
188
+ def formatted_help(self):
189
+ return MissingAttrFormatter().format(self.help, sheet=self.sheet, col=self, vd=vd)
190
+
191
+ @property
192
+ def help_formatters(self):
193
+ formatters = [k[10:] for k in dir(self) if k.startswith('formatter_')]
194
+ return ' '.join(formatters)
195
+
196
+ @property
197
+ def help_displayers(self):
198
+ displayers = [k[10:] for k in dir(self) if k.startswith('displayer_')]
199
+ return ' '.join(displayers)
200
+
197
201
  @property
198
202
  def _formatdict(col):
199
203
  if '=' in col.fmtstr:
@@ -229,6 +233,7 @@ class Column(Extensible):
229
233
  def formatter_python(self, fmtstr):
230
234
  return lambda v,*args,**kwargs: str(v)
231
235
 
236
+ @drawcache
232
237
  def make_formatter(self):
233
238
  'Return function for format(v) from the current formatter and fmtstr'
234
239
  _formatMaker = getattr(self, 'formatter_'+(self.formatter or self.sheet.options.disp_formatter))
@@ -250,7 +255,34 @@ class Column(Extensible):
250
255
  if isinstance(typedval, bytes):
251
256
  typedval = typedval.decode(options.encoding, options.encoding_errors)
252
257
 
253
- return vd.getType(self.type).formatter(self.fmtstr, typedval)
258
+ gt = vd.getType(self._type)
259
+ return gt.formatter(self._fmtstr or gt.fmtstr, typedval)
260
+
261
+ def displayer_generic(self, dw:DisplayWrapper, width=None):
262
+ '''Fit *dw.text* into *width* charcells.
263
+ Generate list of (attr:str, text:str) suitable for clipdraw_chunks.
264
+
265
+ The 'generic' displayer does not do any formatting.
266
+ '''
267
+ if width is not None and width > 1 and vd.isNumeric(self):
268
+ yield ('', dw.text.rjust(width-2))
269
+ else:
270
+ yield ('', dw.text)
271
+
272
+ def displayer_full(self, dw:DisplayWrapper, width=None):
273
+ '''Fit *dw.text* into *width* charcells.
274
+ Generate list of (attr:str, text:str) suitable for clipdraw_chunks.
275
+
276
+ The 'full' displayer allows formatting like [:color].
277
+ '''
278
+ if width is not None and width > 1 and vd.isNumeric(self):
279
+ yield from iterchunks(text.rjust(width-2))
280
+ else:
281
+ yield from iterchunks(dw.text)
282
+
283
+ def display(self, *args, **kwargs):
284
+ f = getattr(self, 'displayer_'+(self.displayer or self.sheet.options.disp_displayer), self.displayer_generic)
285
+ return f(*args, **kwargs)
254
286
 
255
287
  def hide(self, hide=True):
256
288
  if hide:
@@ -284,7 +316,7 @@ class Column(Extensible):
284
316
 
285
317
  @asyncthread
286
318
  def _calcIntoCacheAsync(self, row):
287
- # causes isues when moved into _calcIntoCache gen case
319
+ # causes issues when moved into _calcIntoCache gen case
288
320
  self._cachedValues[self.sheet.rowid(row)] = INPROGRESS
289
321
  self._calcIntoCache(row)
290
322
 
@@ -335,34 +367,35 @@ class Column(Extensible):
335
367
  else:
336
368
  dispval = options.disp_error_val
337
369
  return DisplayWrapper(cellval.val, error=exc.stacktrace,
338
- display=dispval,
370
+ text=dispval,
339
371
  note=options.note_getter_exc,
340
372
  notecolor='color_error')
341
373
  elif typedval.val is None: # early out for strict None
342
- return DisplayWrapper(None, display='', # force empty display for None
374
+ return DisplayWrapper(None, text='', # force empty display for None
343
375
  note=options.disp_note_none,
344
376
  notecolor='color_note_type')
345
377
  elif isinstance(typedval, TypedExceptionWrapper): # calc succeeded, type failed
346
- return DisplayWrapper(typedval.val, display=str(cellval),
378
+ return DisplayWrapper(typedval.val, text=str(cellval),
347
379
  error=typedval.stacktrace,
348
380
  note=options.note_type_exc,
349
381
  notecolor='color_warning')
350
382
  else:
351
- return DisplayWrapper(typedval.val, display=str(typedval.val),
383
+ return DisplayWrapper(typedval.val, text=str(typedval.val),
352
384
  error='unknown',
353
385
  note=options.note_type_exc,
354
386
  notecolor='color_warning')
355
387
 
356
388
  elif isinstance(typedval, threading.Thread):
357
389
  return DisplayWrapper(None,
358
- display=options.disp_pending,
390
+ text=options.disp_pending,
359
391
  note=options.note_pending,
360
392
  notecolor='color_note_pending')
361
393
 
362
394
  dw = DisplayWrapper(cellval)
395
+ dw.typedval = typedval
363
396
 
364
397
  try:
365
- dw.display = self.format(typedval, width=(self.width or 0)*2) or ''
398
+ dw.text = self.format(typedval, width=(self.width or 0)*2) or ''
366
399
 
367
400
  # annotate cells with raw value type in anytype columns, except for strings
368
401
  if self.type is anytype and type(cellval) is not str:
@@ -375,21 +408,23 @@ class Column(Extensible):
375
408
  e.stacktrace = stacktrace()
376
409
  dw.error = e.stacktrace
377
410
  try:
378
- dw.display = str(cellval)
411
+ dw.text = str(cellval)
379
412
  except Exception as e:
380
- dw.display = str(e)
413
+ dw.text = str(e)
381
414
  dw.note = options.note_format_exc
382
415
  dw.notecolor = 'color_warning'
383
416
 
417
+ # dw.display = self.display(dw) # set during draw() when colwidth is known
384
418
  return dw
385
419
 
386
420
  def getDisplayValue(self, row):
387
421
  'Return string displayed in this column for given *row*.'
388
- return self.getCell(row).display
422
+ return self.getCell(row).text
389
423
 
390
424
  def putValue(self, row, val):
391
425
  'Change value for *row* in this column to *val* immediately. Does not check the type. Overridable; by default calls ``.setter(row, val)``.'
392
- return self.setter(self, row, val)
426
+ if self.setter:
427
+ return self.setter(self, row, val)
393
428
 
394
429
  def setValue(self, row, val, setModified=True):
395
430
  'Change value for *row* in this column to *val*. Call ``putValue`` immediately if not a deferred column (added to deferred parent at load-time); otherwise cache until later ``putChanges``. Caller must add undo function.'
@@ -400,19 +435,14 @@ class Column(Extensible):
400
435
  if setModified: #1800
401
436
  self.sheet.setModified()
402
437
 
403
- def setValueSafe(self, row, value):
404
- 'setValue and ignore exceptions.'
405
- try:
406
- return self.setValue(row, value)
407
- except Exception as e:
408
- vd.exceptionCaught(e)
409
-
410
438
  @asyncthread
411
439
  def setValues(self, rows, *values):
412
440
  'Set values in this column for *rows* to *values*, recycling values as needed to fill *rows*.'
413
441
  vd.addUndoSetValues([self], rows)
442
+
414
443
  for r, v in zip(rows, itertools.cycle(values)):
415
- self.setValueSafe(r, v)
444
+ vd.callNoExceptions(self.setValue, r, v)
445
+
416
446
  self.recalc()
417
447
  return vd.status('set %d cells to %d values' % (len(rows), len(values)))
418
448
 
@@ -420,7 +450,7 @@ class Column(Extensible):
420
450
  'Set values on this column for *rows* to *values*, coerced to column type, recycling values as needed to fill *rows*. Abort on type exception.'
421
451
  vd.addUndoSetValues([self], rows)
422
452
  for r, v in zip(rows, itertools.cycle(self.type(val) for val in values)):
423
- self.setValueSafe(r, v)
453
+ vd.callNoExceptions(self.setValue, r, v)
424
454
 
425
455
  self.recalc()
426
456
 
@@ -437,7 +467,7 @@ class Column(Extensible):
437
467
  if w_max < row_w:
438
468
  w_max = row_w
439
469
  if w_max >= self.sheet.windowWidth:
440
- break
470
+ break #1747 early out to speed up wide columns
441
471
  w = w_max
442
472
  w = max(w, nlen)+2
443
473
  w = min(w, self.sheet.windowWidth)
@@ -445,88 +475,33 @@ class Column(Extensible):
445
475
 
446
476
 
447
477
 
448
- # ---- Column makers
478
+ # ---- basic Columns
449
479
 
450
- def setitem(r, i, v): # function needed for use in lambda
451
- r[i] = v
452
- return True
453
-
454
-
455
- def getattrdeep(obj, attr, *default, getter=getattr):
456
- try:
457
- 'Return dotted attr (like "a.b.c") from obj, or default if any of the components are missing.'
458
- if not isinstance(attr, str):
459
- return getter(obj, attr, *default)
460
-
461
- try: # if attribute exists, return toplevel value, even if dotted
462
- if attr in obj:
463
- return getter(obj, attr)
464
- except Exception as e:
465
- pass
466
-
467
- attrs = attr.split('.')
468
- for a in attrs[:-1]:
469
- obj = getter(obj, a)
470
-
471
- return getter(obj, attrs[-1])
472
- except Exception as e:
473
- if not default: raise
474
- return default[0]
475
-
476
-
477
- def setattrdeep(obj, attr, val, getter=getattr, setter=setattr):
478
- 'Set dotted attr (like "a.b.c") on obj to val.'
479
- if not isinstance(attr, str):
480
- return setter(obj, attr, val)
481
-
482
- try: # if attribute exists, overwrite toplevel value, even if dotted
483
- getter(obj, attr)
484
- return setter(obj, attr, val)
485
- except Exception as e:
486
- pass
487
-
488
- attrs = attr.split('.')
489
- for a in attrs[:-1]:
490
- try:
491
- obj = getter(obj, a)
492
- except Exception as e:
493
- obj = obj[a] = type(obj)() # assume homogeneous nesting
494
-
495
- setter(obj, attrs[-1], val)
496
-
497
-
498
- def getitemdeep(obj, k, *default):
499
- return getattrdeep(obj, k, *default, getter=getitem)
500
-
501
- def setitemdeep(obj, k, val):
502
- return setattrdeep(obj, k, val, getter=getitemdef, setter=setitem)
503
-
504
- def AttrColumn(name='', attr=None, **kwargs):
480
+ class AttrColumn(Column):
505
481
  'Column using getattr/setattr with *attr*.'
506
- return Column(name,
507
- expr=attr if attr is not None else name,
508
- getter=lambda col,row: getattrdeep(row, col.expr),
509
- setter=lambda col,row,val: setattrdeep(row, col.expr, val),
510
- **kwargs)
482
+ def __init__(self, name=None, expr=None, **kwargs):
483
+ super().__init__(name,
484
+ expr=expr if expr is not None else name,
485
+ getter=lambda col,row: getattrdeep(row, col.expr, None),
486
+ **kwargs)
511
487
 
512
- def getitem(o, k, default=None):
513
- return default if o is None else o[k]
488
+ def putValue(self, row, val):
489
+ super().putValue(row, val)
490
+ setattrdeep(row, self.expr, val)
514
491
 
515
- def getitemdef(o, k, default=None):
516
- try:
517
- return default if o is None else o[k]
518
- except Exception:
519
- return default
520
492
 
521
493
  class ItemColumn(Column):
522
- 'Column using getitem/setitem with *key*.'
494
+ 'Column using getitem/setitem with *expr*.'
523
495
  def __init__(self, name=None, expr=None, **kwargs):
524
496
  super().__init__(name,
525
497
  expr=expr if expr is not None else name,
526
498
  getter=lambda col,row: getitemdeep(row, col.expr, None),
527
- setter=lambda col,row,val: setitemdeep(row, col.expr, val),
528
499
  **kwargs)
529
500
 
501
+ def putValue(self, row, val):
502
+ super().putValue(row, val)
503
+ setitemdeep(row, self.expr, val)
504
+
530
505
 
531
506
  class SubColumnFunc(Column):
532
507
  'Column compositor; preprocess row with *subfunc*(row, *expr*) before passing to *origcol*.getValue and *origcol*.setValue.'
@@ -584,7 +559,7 @@ class ExprColumn(Column):
584
559
  a = self.getDisplayValue(row)
585
560
  b = self.format(self.type(val))
586
561
  if a != b:
587
- vd.warning('%s calced %s not %s' % (self.name, a, b))
562
+ vd.warning("Cannot change value of calculated column. Use `'` to freeze column.")
588
563
 
589
564
  @property
590
565
  def expr(self):
@@ -608,7 +583,24 @@ class SettableColumn(Column):
608
583
  SettableColumn.init('_store', dict, copy=True)
609
584
 
610
585
 
586
+ vd.addGlobals(
587
+ INPROGRESS=INPROGRESS,
588
+ Column=Column,
589
+ setitem=setitem,
590
+ getattrdeep=getattrdeep,
591
+ setattrdeep=setattrdeep,
592
+ getitemdef=getitemdef,
593
+ AttrColumn=AttrColumn,
594
+ ItemColumn=ItemColumn,
595
+ ExprColumn=ExprColumn,
596
+ SettableColumn=SettableColumn,
597
+ SubColumnFunc=SubColumnFunc,
598
+ SubColumnItem=SubColumnItem,
599
+ SubColumnAttr=SubColumnAttr,
600
+
611
601
  # synonyms
612
- ColumnItem = ItemColumn
613
- ColumnAttr = AttrColumn
614
- ColumnExpr = ExprColumn
602
+ ColumnItem=ItemColumn,
603
+ ColumnAttr=AttrColumn,
604
+ ColumnExpr=ExprColumn,
605
+ DisplayWrapper=DisplayWrapper
606
+ )
visidata/ddw/input.ddw CHANGED
@@ -1,79 +1,67 @@
1
- {"x": 1, "y": 7, "text": "Ctrl+A or Ctrl+E", "color": "black on black", "tags": [], "group": "", "frame": "advanced"}
2
- {"x": 0, "y": 6, "text": "Ctrl+B or Ctrl+F", "color": "black on black", "tags": [], "group": "", "frame": "advanced"}
3
- {"x": 0, "y": 6, "text": "\u2190", "color": "bold white on 237", "tags": [], "group": ""}
4
- {"x": 11, "y": 6, "text": "go to prev char", "color": "", "tags": [], "group": ""}
5
- {"x": 0, "y": 4, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
6
- {"x": 9, "y": 4, "text": "cancel input (abort command)", "color": "", "tags": [], "group": ""}
7
- {"x": 45, "y": 7, "text": "delete one char at cursor", "color": "", "tags": [], "group": ""}
8
- {"x": 45, "y": 8, "text": "delete one char before cursor", "color": "", "tags": [], "group": ""}
9
- {"x": 0, "y": 16, "text": "Shift", "color": "bold white on 237", "tags": [], "group": ""}
10
- {"x": 0, "y": 3, "text": "Enter", "color": "bold white on 237", "tags": [], "group": ""}
11
- {"x": 9, "y": 3, "text": "accept input (continue command)", "color": "", "tags": [], "group": ""}
12
- {"x": 39, "y": 9, "text": "K", "color": "bold white on 237", "tags": [], "group": ""}
13
- {"x": 45, "y": 9, "text": "delete all chars after cursor", "color": "", "tags": [], "group": ""}
14
- {"x": 39, "y": 15, "text": "O", "color": "bold white on 237", "tags": [], "group": ""}
15
- {"x": 45, "y": 15, "text": "open input in external editor", "color": "", "tags": [], "group": ""}
16
- {"x": 39, "y": 16, "text": "R", "color": "bold white on 237", "tags": [], "group": ""}
17
- {"x": 45, "y": 16, "text": "reload default value", "color": "", "tags": [], "group": ""}
18
- {"x": 39, "y": 12, "text": "T", "color": "bold white on 237", "tags": [], "group": ""}
19
- {"x": 45, "y": 12, "text": "swap last two chars", "color": "", "tags": [], "group": ""}
20
- {"x": 39, "y": 10, "text": "U", "color": "bold white on 237", "tags": [], "group": ""}
21
- {"x": 45, "y": 10, "text": "delete all chars before cursor", "color": "", "tags": [], "group": ""}
22
- {"x": 39, "y": 13, "text": "V", "color": "bold white on 237", "tags": [], "group": ""}
23
- {"x": 45, "y": 13, "text": "insert literal char", "color": "", "tags": [], "group": ""}
24
- {"x": 39, "y": 11, "text": "W", "color": "bold white on 237", "tags": [], "group": ""}
25
- {"x": 45, "y": 11, "text": "delete one word before cursor", "color": "", "tags": [], "group": ""}
26
- {"x": 39, "y": 14, "text": "Y", "color": "bold white on 237", "tags": [], "group": ""}
27
- {"x": 45, "y": 14, "text": "insert clipboard text at cursor", "color": "", "tags": [], "group": ""}
28
- {"x": 5, "y": 10, "text": "\u2190", "color": "bold white on 237", "tags": [], "group": ""}
29
- {"x": 11, "y": 10, "text": "go to prev word", "color": "", "tags": [], "group": ""}
30
- {"x": 0, "y": 0, "text": "Start typing to erase the default value.", "color": "", "tags": [], "group": ""}
31
- {"x": 0, "y": 1, "text": "To edit the default value, press a movement key first.", "color": "", "tags": [], "group": ""}
32
- {"x": 0, "y": 13, "text": "\u2191", "color": "bold white on 237", "tags": [], "group": ""}
33
- {"x": 0, "y": 14, "text": "\u2193", "color": "bold white on 237", "tags": [], "group": ""}
34
- {"x": 0, "y": 8, "text": "Home", "color": "bold white on 237", "tags": [], "group": ""}
35
- {"x": 11, "y": 8, "text": "go to start of input", "color": "", "tags": [], "group": ""}
36
- {"x": 0, "y": 9, "text": "End", "color": "bold white on 237", "tags": [], "group": ""}
37
- {"x": 0, "y": 7, "text": "\u2192", "color": "bold white on 237", "tags": [], "group": ""}
38
- {"x": 34, "y": 7, "text": "Delete", "color": "bold white on 237", "tags": [], "group": ""}
39
- {"x": 34, "y": 8, "text": "Backspace", "color": "bold white on 237", "tags": [], "group": ""}
40
- {"x": 0, "y": 9, "text": "Ctrl+D", "color": "", "tags": [], "group": "", "frame": "advanced"}
41
- {"x": 0, "y": 10, "text": "Ctrl+H", "color": "", "tags": [], "group": "", "frame": "advanced"}
42
- {"x": 34, "y": 6, "text": "Insert", "color": "bold white on 237", "tags": [], "group": ""}
43
- {"x": 45, "y": 6, "text": "toggle insert mode", "color": "", "tags": [], "group": ""}
44
- {"x": 0, "y": 15, "text": "Tab", "color": "bold white on 237", "tags": [], "group": ""}
45
- {"x": 0, "y": 10, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
46
- {"x": 34, "y": 9, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
47
- {"x": 34, "y": 10, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
48
- {"x": 34, "y": 12, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
49
- {"x": 34, "y": 11, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
50
- {"x": 34, "y": 13, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
51
- {"x": 34, "y": 14, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
52
- {"x": 34, "y": 15, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
53
- {"x": 34, "y": 16, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
54
- {"x": 4, "y": 10, "text": "+", "color": "", "tags": [], "group": ""}
55
- {"x": 38, "y": 9, "text": "+", "color": "", "tags": [], "group": ""}
56
- {"x": 38, "y": 10, "text": "+", "color": "", "tags": [], "group": ""}
57
- {"x": 38, "y": 11, "text": "+", "color": "", "tags": [], "group": ""}
58
- {"x": 38, "y": 12, "text": "+", "color": "", "tags": [], "group": ""}
59
- {"x": 38, "y": 13, "text": "+", "color": "", "tags": [], "group": ""}
60
- {"x": 38, "y": 14, "text": "+", "color": "", "tags": [], "group": ""}
61
- {"x": 38, "y": 15, "text": "+", "color": "", "tags": [], "group": ""}
62
- {"x": 38, "y": 16, "text": "+", "color": "", "tags": [], "group": ""}
63
- {"x": 0, "y": 11, "text": "Ctrl", "color": "bold white on 237", "tags": [], "group": ""}
64
- {"x": 4, "y": 11, "text": "+", "color": "", "tags": [], "group": ""}
65
- {"x": 5, "y": 11, "text": "\u2192", "color": "bold white on 237", "tags": [], "group": ""}
66
- {"x": 11, "y": 11, "text": "go to next word", "color": "", "tags": [], "group": ""}
67
- {"x": 11, "y": 9, "text": "go to end of input", "color": "", "tags": [], "group": ""}
68
- {"x": 11, "y": 7, "text": "go to next char", "color": "", "tags": [], "group": ""}
69
- {"x": 11, "y": 13, "text": "next input history", "color": "", "tags": [], "group": ""}
70
- {"x": 11, "y": 14, "text": "prev input history", "color": "", "tags": [], "group": ""}
71
- {"x": 11, "y": 15, "text": "next autocomplete", "color": "", "tags": [], "group": ""}
72
- {"x": 11, "y": 16, "text": "prev autocomplete", "color": "", "tags": [], "group": ""}
73
- {"x": 4, "y": 4, "text": "+", "color": "", "tags": [], "group": ""}
74
- {"x": 5, "y": 4, "text": "C", "color": "bold white on 237", "tags": [], "group": ""}
75
- {"x": 5, "y": 16, "text": "+", "color": "", "tags": [], "group": ""}
76
- {"x": 6, "y": 16, "text": "Tab", "color": "bold white on 237", "tags": [], "group": ""}
1
+ {"id": null, "type": null, "x": 6, "y": 7, "text": "\u2190", "color": "keystrokes", "tags": [], "group": "", "frame": null, "rows": null, "duration_ms": null, "ref": null, "onclick": null}
2
+ {"x": 11, "y": 7, "text": "go to prev/next char", "color": "", "tags": [], "group": ""}
3
+ {"x": 3, "y": 4, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
4
+ {"x": 11, "y": 4, "text": "cancel input (abort)", "color": "", "tags": [], "group": ""}
5
+ {"x": 45, "y": 4, "text": "delete one char at cursor", "color": "", "tags": [], "group": ""}
6
+ {"x": 45, "y": 5, "text": "delete one char before cursor", "color": "", "tags": [], "group": ""}
7
+ {"x": 0, "y": 13, "text": "Shift", "color": "keystrokes", "tags": [], "group": ""}
8
+ {"x": 4, "y": 3, "text": "Enter", "color": "keystrokes", "tags": [], "group": ""}
9
+ {"x": 11, "y": 3, "text": "accept input", "color": "", "tags": [], "group": ""}
10
+ {"x": 42, "y": 6, "text": "K", "color": "keystrokes", "tags": [], "group": ""}
11
+ {"x": 45, "y": 6, "text": "delete all before/after cursor", "color": "", "tags": [], "group": ""}
12
+ {"x": 42, "y": 12, "text": "O", "color": "keystrokes", "tags": [], "group": ""}
13
+ {"x": 45, "y": 12, "text": "open input in external editor", "color": "", "tags": [], "group": ""}
14
+ {"x": 42, "y": 13, "text": "R", "color": "keystrokes", "tags": [], "group": ""}
15
+ {"x": 45, "y": 13, "text": "restore starting value", "color": "", "tags": [], "group": ""}
16
+ {"x": 42, "y": 8, "text": "T", "color": "keystrokes", "tags": [], "group": ""}
17
+ {"x": 45, "y": 8, "text": "swap last two chars", "color": "", "tags": [], "group": ""}
18
+ {"x": 40, "y": 6, "text": "U", "color": "keystrokes", "tags": [], "group": ""}
19
+ {"x": 42, "y": 9, "text": "V", "color": "keystrokes", "tags": [], "group": ""}
20
+ {"x": 45, "y": 9, "text": "insert literal char", "color": "", "tags": [], "group": ""}
21
+ {"x": 42, "y": 7, "text": "W", "color": "keystrokes", "tags": [], "group": ""}
22
+ {"x": 45, "y": 7, "text": "delete one word before cursor", "color": "", "tags": [], "group": ""}
23
+ {"x": 42, "y": 11, "text": "Y", "color": "keystrokes", "tags": [], "group": ""}
24
+ {"x": 45, "y": 11, "text": "insert clipboard text at cursor", "color": "", "tags": [], "group": ""}
25
+ {"x": 6, "y": 8, "text": "\u2190", "color": "keystrokes", "tags": [], "group": ""}
26
+ {"x": 11, "y": 8, "text": "go to prev/next word", "color": "", "tags": [], "group": ""}
27
+ {"x": 0, "y": 0, "text": "Begin typing to overwrite the starting value.", "color": "", "tags": [], "group": ""}
28
+ {"x": 0, "y": 1, "text": "(To edit the starting value, press a movement key first.)", "color": "", "tags": [], "group": ""}
29
+ {"x": 6, "y": 11, "text": "\u2191", "color": "keystrokes", "tags": [], "group": ""}
30
+ {"x": 1, "y": 9, "text": "Home", "color": "keystrokes", "tags": [], "group": ""}
31
+ {"x": 11, "y": 9, "text": "go to start/end", "color": "", "tags": [], "group": ""}
32
+ {"x": 6, "y": 9, "text": "End", "color": "keystrokes", "tags": [], "group": ""}
33
+ {"x": 8, "y": 7, "text": "\u2192", "color": "keystrokes", "tags": [], "group": ""}
34
+ {"x": 37, "y": 4, "text": "Delete", "color": "keystrokes", "tags": [], "group": ""}
35
+ {"x": 34, "y": 5, "text": "Backspace", "color": "keystrokes", "tags": [], "group": ""}
36
+ {"x": 37, "y": 3, "text": "Insert", "color": "keystrokes", "tags": [], "group": ""}
37
+ {"x": 45, "y": 3, "text": "toggle insert mode", "color": "", "tags": [], "group": ""}
38
+ {"x": 6, "y": 12, "text": "Tab", "color": "keystrokes", "tags": [], "group": ""}
39
+ {"x": 1, "y": 8, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
40
+ {"x": 35, "y": 6, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
41
+ {"x": 37, "y": 8, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
42
+ {"x": 37, "y": 7, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
43
+ {"x": 37, "y": 9, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
44
+ {"x": 37, "y": 11, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
45
+ {"x": 37, "y": 12, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
46
+ {"x": 37, "y": 13, "text": "Ctrl", "color": "keystrokes", "tags": [], "group": ""}
47
+ {"x": 5, "y": 8, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
48
+ {"x": 39, "y": 6, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
49
+ {"x": 41, "y": 7, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
50
+ {"x": 41, "y": 8, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
51
+ {"x": 41, "y": 9, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
52
+ {"x": 41, "y": 11, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
53
+ {"x": 41, "y": 12, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
54
+ {"x": 41, "y": 13, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
55
+ {"x": 11, "y": 11, "text": "prev/next in history", "color": "", "tags": [], "group": ""}
56
+ {"x": 11, "y": 12, "text": "next autocomplete", "color": "", "tags": [], "group": ""}
57
+ {"x": 11, "y": 13, "text": "prev autocomplete", "color": "", "tags": [], "group": ""}
58
+ {"x": 7, "y": 4, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
59
+ {"x": 8, "y": 4, "text": "C", "color": "keystrokes", "tags": [], "group": ""}
60
+ {"x": 5, "y": 13, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
61
+ {"x": 6, "y": 13, "text": "Tab", "color": "keystrokes", "tags": [], "group": ""}
62
+ {"x": 32, "y": 3, "text": "\u2502", "color": "", "tags": [], "group": ""}
63
+ {"x": 32, "y": 4, "text": "\u2502", "color": "", "tags": [], "group": ""}
64
+ {"x": 32, "y": 5, "text": "\u2502", "color": "", "tags": [], "group": ""}
77
65
  {"x": 32, "y": 6, "text": "\u2502", "color": "", "tags": [], "group": ""}
78
66
  {"x": 32, "y": 7, "text": "\u2502", "color": "", "tags": [], "group": ""}
79
67
  {"x": 32, "y": 8, "text": "\u2502", "color": "", "tags": [], "group": ""}
@@ -82,6 +70,13 @@
82
70
  {"x": 32, "y": 11, "text": "\u2502", "color": "", "tags": [], "group": ""}
83
71
  {"x": 32, "y": 12, "text": "\u2502", "color": "", "tags": [], "group": ""}
84
72
  {"x": 32, "y": 13, "text": "\u2502", "color": "", "tags": [], "group": ""}
85
- {"x": 32, "y": 14, "text": "\u2502", "color": "", "tags": [], "group": ""}
86
- {"x": 32, "y": 15, "text": "\u2502", "color": "", "tags": [], "group": ""}
87
- {"x": 32, "y": 16, "text": "\u2502", "color": "", "tags": [], "group": ""}
73
+ {"x": 7, "y": 7, "text": "/", "color": "", "tags": [], "group": ""}
74
+ {"x": 5, "y": 9, "text": "/", "color": "", "tags": [], "group": ""}
75
+ {"x": 8, "y": 8, "text": "\u2192", "color": "keystrokes", "tags": [], "group": ""}
76
+ {"x": 8, "y": 11, "text": "\u2193", "color": "keystrokes", "tags": [], "group": ""}
77
+ {"x": 41, "y": 6, "text": "/", "color": "", "tags": [], "group": ""}
78
+ {"x": 7, "y": 11, "text": "/", "color": "", "tags": [], "group": ""}
79
+ {"x": 7, "y": 8, "text": "/", "color": "", "tags": [], "group": ""}
80
+ {"x": 75, "y": 0, "text": "?", "color": "bold 117 cyan", "tags": [], "group": "", "onclick": "https://visidata.org/input"}
81
+ {"x": 3, "y": 5, "text": "Ctrl+G", "color": "keystrokes", "tags": [], "group": ""}
82
+ {"x": 11, "y": 5, "text": "cycle help sidebar", "color": "", "tags": [], "group": ""}