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/sheets.py CHANGED
@@ -3,87 +3,35 @@ import itertools
3
3
  from copy import copy, deepcopy
4
4
  import textwrap
5
5
 
6
- from visidata import VisiData, Extensible, globalCommand, ColumnAttr, ColumnItem, vd, ENTER, EscapeException, drawcache, drawcache_property, LazyChainMap, asyncthread, ExpectedException, setitem
6
+ from visidata import VisiData, Extensible, globalCommand, ColumnAttr, ColumnItem, vd, ENTER, EscapeException, drawcache, drawcache_property, LazyChainMap, asyncthread, ExpectedException
7
7
  from visidata import (options, Column, namedlist, SettableColumn,
8
8
  TypedExceptionWrapper, BaseSheet, UNLOADED,
9
- clipdraw, ColorAttr, update_attr, colors, undoAttrFunc, vlen)
9
+ clipdraw, clipdraw_chunks, ColorAttr, update_attr, colors, undoAttrFunc, vlen, dispwidth)
10
10
  import visidata
11
11
 
12
12
 
13
13
  vd.activePane = 1 # pane numbering starts at 1; pane 0 means active pane
14
14
 
15
15
 
16
- __all__ = ['RowColorizer', 'CellColorizer', 'ColumnColorizer', 'Sheet', 'TableSheet', 'IndexSheet', 'SheetsSheet', 'LazyComputeRow', 'SequenceSheet']
17
-
18
-
19
- vd.option('default_width', 20, 'default column width', replay=True) # TODO: make not replay and remove from markdown saver
20
- vd.option('default_height', 4, 'default column height')
21
- vd.option('textwrap_cells', True, 'wordwrap text for multiline rows')
22
-
23
- vd.option('quitguard', False, 'confirm before quitting modified sheet')
24
- vd.option('debug', False, 'exit on error and display stacktrace')
25
- vd.option('skip', 0, 'skip N rows before header', replay=True)
26
- vd.option('header', 1, 'parse first N rows as column names', replay=True)
27
- vd.option('load_lazy', False, 'load subsheets always (False) or lazily (True)')
28
-
29
- vd.option('force_256_colors', False, 'use 256 colors even if curses reports fewer')
30
-
31
- vd.option('disp_note_none', '⌀', 'visible contents of a cell whose value is None')
32
- vd.option('disp_truncator', '…', 'indicator that the contents are only partially visible')
33
- vd.option('disp_oddspace', '\u00b7', 'displayable character for odd whitespace')
34
- vd.option('disp_more_left', '<', 'header note indicating more columns to the left')
35
- vd.option('disp_more_right', '>', 'header note indicating more columns to the right')
36
- vd.option('disp_error_val', '', 'displayed contents for computation exception')
37
- vd.option('disp_ambig_width', 1, 'width to use for unicode chars marked ambiguous')
38
-
39
- vd.option('disp_pending', '', 'string to display in pending cells')
40
- vd.option('note_pending', '⌛', 'note to display for pending cells')
41
- vd.option('note_format_exc', '?', 'cell note for an exception during formatting')
42
- vd.option('note_getter_exc', '!', 'cell note for an exception during computation')
43
- vd.option('note_type_exc', '!', 'cell note for an exception during type conversion')
44
-
45
- vd.option('color_note_pending', 'bold magenta', 'color of note in pending cells')
46
- vd.option('color_note_type', '226 yellow', 'color of cell note for non-str types in anytype columns')
47
- vd.option('color_note_row', '220 yellow', 'color of row note on left edge')
48
- vd.option('scroll_incr', -3, 'amount to scroll with scrollwheel')
49
- vd.option('disp_column_sep', '│', 'separator between columns')
50
- vd.option('disp_keycol_sep', '║', 'separator between key columns and rest of columns')
51
- vd.option('disp_rowtop_sep', '│', '') # ╷│┬╽⌜⌐▇
52
- vd.option('disp_rowmid_sep', '⁝', '') # ┃┊│█
53
- vd.option('disp_rowbot_sep', '⁝', '') # ┊┴╿⌞█⍿╵⎢┴⌊ ⋮⁝
54
- vd.option('disp_rowend_sep', '║', '') # ┊┴╿⌞█⍿╵⎢┴⌊
55
- vd.option('disp_keytop_sep', '║', '') # ╽╿┃╖╟
56
- vd.option('disp_keymid_sep', '║', '') # ╽╿┃
57
- vd.option('disp_keybot_sep', '║', '') # ╽╿┃╜‖
58
- vd.option('disp_endtop_sep', '║', '') # ╽╿┃╖╢
59
- vd.option('disp_endmid_sep', '║', '') # ╽╿┃
60
- vd.option('disp_endbot_sep', '║', '') # ╽╿┃╜‖
61
- vd.option('disp_selected_note', '•', '') #
62
- vd.option('disp_sort_asc', '↑↟⇞⇡⇧⇑', 'characters for ascending sort') # ↑▲↟↥↾↿⇞⇡⇧⇈⤉⤒⥔⥘⥜⥠⍏˄ˆ
63
- vd.option('disp_sort_desc', '↓↡⇟⇣⇩⇓', 'characters for descending sort') # ↓▼↡↧⇂⇃⇟⇣⇩⇊⤈⤓⥕⥙⥝⥡⍖˅ˇ
64
- vd.option('color_default', 'white on black', 'the default fg and bg colors')
65
- vd.option('color_default_hdr', 'bold', 'color of the column headers')
66
- vd.option('color_bottom_hdr', 'underline', 'color of the bottom header row')
67
- vd.option('color_current_row', 'reverse', 'color of the cursor row')
68
- vd.option('color_current_col', 'bold', 'color of the cursor column')
69
- vd.option('color_current_hdr', 'bold reverse', 'color of the header for the cursor column')
70
- vd.option('color_column_sep', '246 blue', 'color of column separators')
71
- vd.option('color_key_col', '81 cyan', 'color of key columns')
72
- vd.option('color_hidden_col', '8', 'color of hidden columns on metasheets')
73
- vd.option('color_selected_row', '215 yellow', 'color of selected rows')
74
- vd.option('name_joiner', '_', 'string to join sheet or column names')
75
- vd.option('value_joiner', ' ', 'string to join display values')
16
+ vd.option('name_joiner', '_', 'string to join sheet or column names', max_help=0)
17
+ vd.option('value_joiner', ' ', 'string to join display values', max_help=0)
76
18
 
77
19
 
78
- @VisiData.api
79
20
  @drawcache
80
- def splitcell(sheet, s, width=0):
21
+ def _splitcell(sheet, s, width=0, maxheight=1):
81
22
  if width <= 0 or not sheet.options.textwrap_cells:
82
23
  return [s]
83
24
 
84
25
  ret = []
85
- for L in s.splitlines():
86
- ret.extend(textwrap.wrap(L, width=width, break_long_words=False, replace_whitespace=False))
26
+ for attr, text in s:
27
+ for line in textwrap.wrap(
28
+ text, width=width, break_long_words=False, replace_whitespace=False
29
+ ):
30
+ if len(ret) >= maxheight:
31
+ ret[-1][0][1] += ' ' + line
32
+ break
33
+ else:
34
+ ret.append([[attr, line]])
87
35
  return ret
88
36
 
89
37
  disp_column_fill = ' ' # pad chars after column value
@@ -92,6 +40,7 @@ disp_column_fill = ' ' # pad chars after column value
92
40
  # coloropt is the color option name (like 'color_error')
93
41
  # func(sheet,col,row,value) should return a true value if coloropt should be applied
94
42
  # if coloropt is None, func() should return a coloropt (or None) instead
43
+ Colorizer = collections.namedtuple('Colorizer', 'precedence coloropt func')
95
44
  RowColorizer = collections.namedtuple('RowColorizer', 'precedence coloropt func')
96
45
  CellColorizer = collections.namedtuple('CellColorizer', 'precedence coloropt func')
97
46
  ColumnColorizer = collections.namedtuple('ColumnColorizer', 'precedence coloropt func')
@@ -169,6 +118,21 @@ class TableSheet(BaseSheet):
169
118
  _coltype = SettableColumn
170
119
 
171
120
  rowtype = 'rows'
121
+ guide = '# {sheet.help_title}\n{sheet.help_columns}\n'
122
+
123
+ @property
124
+ def help_title(self):
125
+ if isinstance(self.source, visidata.Path):
126
+ return 'Source Table'
127
+ else:
128
+ return 'Table Sheet'
129
+
130
+ @property
131
+ def help_columns(self):
132
+ hiddenCols = [c for c in self.columns if c.hidden]
133
+ if hiddenCols:
134
+ return f'- `gv` to unhide {len(hiddenCols)} hidden columns'
135
+ return ''
172
136
 
173
137
  columns = [] # list of Column
174
138
  colorizers = [ # list of Colorizer
@@ -176,14 +140,13 @@ class TableSheet(BaseSheet):
176
140
  ColumnColorizer(2, 'color_current_col', lambda s,c,r,v: c is s.cursorCol),
177
141
  ColumnColorizer(1, 'color_key_col', lambda s,c,r,v: c and c.keycol),
178
142
  CellColorizer(0, 'color_default', lambda s,c,r,v: True),
179
- RowColorizer(2, 'color_selected_row', lambda s,c,r,v: r is not None and s.isSelected(r)),
180
143
  RowColorizer(1, 'color_error', lambda s,c,r,v: isinstance(r, (Exception, TypedExceptionWrapper))),
144
+ CellColorizer(3, 'color_current_cell', lambda s,c,r,v: c is s.cursorCol and r is s.cursorRow)
181
145
  ]
182
146
  nKeys = 0 # columns[:nKeys] are key columns
183
147
 
184
- def __init__(self, *names, **kwargs):
185
- super().__init__(*names, **kwargs)
186
- self.rows = UNLOADED # list of opaque row objects (UNLOADED before first reload)
148
+ def __init__(self, *names, rows=UNLOADED, **kwargs):
149
+ super().__init__(*names, rows=rows, **kwargs)
187
150
  self.cursorRowIndex = 0 # absolute index of cursor into self.rows
188
151
  self.cursorVisibleColIndex = 0 # index of cursor into self.visibleCols
189
152
 
@@ -196,11 +159,11 @@ class TableSheet(BaseSheet):
196
159
  self._visibleColLayout = {} # [vcolidx] -> (x, w)
197
160
 
198
161
  # list of all columns in display order
199
- self.columns = kwargs.get('columns') or [copy(c) for c in self.columns] or [Column('_')]
200
- self._colorizers = []
201
- self.recalc() # set .sheet on columns and start caches
162
+ self.initialCols = kwargs.pop('columns', None) or type(self).columns
163
+ self.resetCols()
202
164
 
203
- self.setKeys(self.columns[:self.nKeys]) # initial list of key columns
165
+ self._colorizers = self.classColorizers
166
+ self.recalc() # set .sheet on columns and start caches
204
167
 
205
168
  self.__dict__.update(kwargs) # also done earlier in BaseSheet.__init__
206
169
 
@@ -216,33 +179,30 @@ class TableSheet(BaseSheet):
216
179
  def addColorizer(self, c):
217
180
  'Add Colorizer *c* to the list of colorizers for this sheet.'
218
181
  self._colorizers.append(c)
182
+ self._colorizers = sorted(self._colorizers, key=lambda x: x.precedence, reverse=True)
219
183
 
220
184
  def removeColorizer(self, c):
221
185
  'Remove Colorizer *c* from the list of colorizers for this sheet.'
222
186
  self._colorizers.remove(c)
223
187
 
224
- @drawcache_property
225
- def allColorizers(self):
188
+ @property
189
+ def classColorizers(self) -> list:
190
+ 'List of all colorizers from sheet class hierarchy in precedence order (highest precedence first)'
226
191
  # all colorizers must be in the same bucket
227
192
  # otherwise, precedence does not get applied properly
228
193
  _colorizers = set()
229
- def allParents(cls):
230
- yield from cls.__bases__
231
- for b in cls.__bases__:
232
- yield from allParents(b)
233
194
 
234
- for b in [self] + list(allParents(self.__class__)):
195
+ for b in [self] + list(type(self).superclasses()):
235
196
  for c in getattr(b, 'colorizers', []):
236
197
  _colorizers.add(c)
237
198
 
238
- _colorizers |= set(self._colorizers)
239
199
  return sorted(_colorizers, key=lambda x: x.precedence, reverse=True)
240
200
 
241
201
  def _colorize(self, col, row, value=None) -> ColorAttr:
242
- 'Returns ColorAttr for the given colorizers/col/row/value'
202
+ 'Return ColorAttr for the given colorizers/col/row/value'
243
203
 
244
204
  colorstack = []
245
- for colorizer in self.allColorizers:
205
+ for colorizer in self._colorizers:
246
206
  try:
247
207
  r = colorizer.func(self, col, row, value)
248
208
  if r:
@@ -283,7 +243,33 @@ class TableSheet(BaseSheet):
283
243
 
284
244
  @asyncthread
285
245
  def reload(self):
286
- 'Load rows and/or columns from ``self.source``. Async. Override in subclass.'
246
+ 'Load or reload rows and columns from ``self.source``. Async. Override resetCols() or loader() in subclass.'
247
+ with visidata.ScopedSetattr(self, 'loading', True):
248
+ self.resetCols()
249
+ self.beforeLoad()
250
+ try:
251
+ self.loader()
252
+ vd.debug(f'finished loading {self}')
253
+ finally:
254
+ self.afterLoad()
255
+
256
+ self.recalc()
257
+
258
+ def beforeLoad(self):
259
+ pass
260
+
261
+ def resetCols(self):
262
+ 'Reset columns to class settings'
263
+ self.columns = []
264
+ for c in self.initialCols:
265
+ self.addColumn(deepcopy(c))
266
+ if self.options.disp_help > c.max_help:
267
+ c.hide()
268
+
269
+ self.setKeys(self.columns[:self.nKeys])
270
+
271
+ def loader(self):
272
+ 'Reset rows and sync load ``source`` via iterload. Overrideable.'
287
273
  self.rows = []
288
274
  try:
289
275
  with vd.Progress(gerund='loading', total=0):
@@ -292,15 +278,17 @@ class TableSheet(BaseSheet):
292
278
  except FileNotFoundError:
293
279
  return # let it be a blank sheet without error
294
280
 
295
- # if an ordering has been specified, sort the sheet
296
- if self._ordering:
297
- vd.sync(self.sort())
298
-
299
281
  def iterload(self):
300
282
  'Generate rows from ``self.source``. Override in subclass.'
301
283
  if False:
302
284
  yield vd.fail('no iterload for this loader yet')
303
285
 
286
+ def afterLoad(self):
287
+ 'hook for after loading has finished. Overrideable (be sure to call super).'
288
+ # if an ordering has been specified, sort the sheet
289
+ if self._ordering:
290
+ vd.sync(self.sort())
291
+
304
292
  def iterrows(self):
305
293
  if self.rows is UNLOADED:
306
294
  try:
@@ -324,10 +312,24 @@ class TableSheet(BaseSheet):
324
312
  'Copy sheet design but remain unloaded. Deepcopy columns so their attributes (width, type, name) may be adjusted independently of the original.'
325
313
  ret = super().__copy__()
326
314
  ret.rows = UNLOADED
327
- ret.columns = [copy(c) for c in self.keyCols]
328
- ret.setKeys(ret.columns)
329
- ret.columns.extend(copy(c) for c in self.columns if c not in self.keyCols)
330
- ret.recalc() # set .sheet on columns
315
+
316
+ ret.columns = []
317
+
318
+ col_mapping = {}
319
+ for c in self.columns:
320
+ new_col = copy(c)
321
+ col_mapping[c] = new_col
322
+ ret.addColumn(new_col)
323
+
324
+ ret.setKeys([col_mapping[c] for c in self.columns if c.keycol])
325
+
326
+ ret._ordering = []
327
+ for sortcol,reverse in self._ordering:
328
+ if isinstance(sortcol, str):
329
+ ret._ordering.append((sortcol,reverse))
330
+ else:
331
+ ret._ordering.append((col_mapping[sortcol],reverse))
332
+
331
333
  ret.topRowIndex = ret.cursorRowIndex = 0
332
334
  return ret
333
335
 
@@ -350,9 +352,12 @@ class TableSheet(BaseSheet):
350
352
  memo[id(self)] = ret
351
353
  return ret
352
354
 
353
- def __repr__(self):
355
+ def __str__(self):
354
356
  return self.name
355
357
 
358
+ def __repr__(self):
359
+ return f'<{type(self).__name__}: {self.name}>'
360
+
356
361
  def evalExpr(self, expr, row=None, col=None):
357
362
  if row is not None:
358
363
  # contexts are cached by sheet/rowid for duration of drawcycle
@@ -390,7 +395,8 @@ class TableSheet(BaseSheet):
390
395
  @property
391
396
  def cursorRow(self):
392
397
  'The row object at the row cursor.'
393
- return self.rows[self.cursorRowIndex] if self.nRows > 0 else None
398
+ idx = self.cursorRowIndex
399
+ return self.rows[idx] if self.nRows > idx else None
394
400
 
395
401
  @property
396
402
  def visibleRows(self): # onscreen rows
@@ -402,16 +408,6 @@ class TableSheet(BaseSheet):
402
408
  'List of non-hidden columns in display order.'
403
409
  return self.keyCols + [c for c in self.columns if not c.hidden and not c.keycol]
404
410
 
405
- def visibleColAtX(self, x):
406
- for vcolidx, (colx, w) in self._visibleColLayout.items():
407
- if colx <= x <= colx+w:
408
- return vcolidx
409
-
410
- def visibleRowAtY(self, y):
411
- for rowidx, (rowy, h) in self._rowLayout.items():
412
- if rowy <= y <= rowy+h-1:
413
- return rowidx
414
-
415
411
  @drawcache_property
416
412
  def keyCols(self):
417
413
  'List of visible key columns.'
@@ -430,7 +426,10 @@ class TableSheet(BaseSheet):
430
426
  @property
431
427
  def cursorColIndex(self):
432
428
  'Index of current column into `Sheet.columns`. Linear search; prefer `cursorCol` or `cursorVisibleColIndex`.'
433
- return self.columns.index(self.cursorCol)
429
+ try:
430
+ return self.columns.index(self.cursorCol)
431
+ except ValueError:
432
+ return None
434
433
 
435
434
  @property
436
435
  def nonKeyVisibleCols(self):
@@ -442,6 +441,13 @@ class TableSheet(BaseSheet):
442
441
  'String of key column names, for SheetsSheet convenience.'
443
442
  return ' '.join(c.name for c in self.keyCols)
444
443
 
444
+ @keyColNames.setter
445
+ def keyColNames(self, v): #2122
446
+ 'Set key columns on this sheet to the space-separated list of column names.'
447
+ newkeys = [self.column(colname) for colname in v.split()]
448
+ self.unsetKeys(self.keyCols)
449
+ self.setKeys(newkeys)
450
+
445
451
  @property
446
452
  def cursorCell(self):
447
453
  'Displayed value (DisplayWrapper) at current row and column.'
@@ -449,7 +455,7 @@ class TableSheet(BaseSheet):
449
455
 
450
456
  @property
451
457
  def cursorDisplay(self):
452
- 'Displayed value (DisplayWrapper.display) at current row and column.'
458
+ 'Displayed value (DisplayWrapper.text) at current row and column.'
453
459
  return self.cursorCol.getDisplayValue(self.cursorRow)
454
460
 
455
461
  @property
@@ -506,12 +512,10 @@ class TableSheet(BaseSheet):
506
512
 
507
513
  if index is not None:
508
514
  self.setModified()
509
- else:
510
- for col in cols:
511
- col.defer = self.defer
512
515
 
513
516
  for i, col in enumerate(cols):
514
517
  col.name = self.maybeClean(col.name)
518
+ col.defer = self.defer
515
519
 
516
520
  vd.addUndo(self.columns.remove, col)
517
521
  idx = len(self.columns) if index is None else index
@@ -607,7 +611,7 @@ class TableSheet(BaseSheet):
607
611
  break
608
612
  mincolidx, maxcolidx = min(self._visibleColLayout.keys()), max(self._visibleColLayout.keys())
609
613
  if self.cursorVisibleColIndex < mincolidx:
610
- self.leftVisibleColIndex -= max((self.cursorVisibleColIndex - mincolid)//2, 1)
614
+ self.leftVisibleColIndex -= max((self.cursorVisibleColIndex - mincolidx)//2, 1)
611
615
  continue
612
616
  elif self.cursorVisibleColIndex > maxcolidx:
613
617
  self.leftVisibleColIndex += max((maxcolidx - self.cursorVisibleColIndex)//2, 1)
@@ -620,16 +624,25 @@ class TableSheet(BaseSheet):
620
624
 
621
625
  def calcColLayout(self):
622
626
  'Set right-most visible column, based on calculation.'
623
- minColWidth = len(self.options.disp_more_left)+len(self.options.disp_more_right)+2
624
- sepColWidth = len(self.options.disp_column_sep)
627
+ minColWidth = dispwidth(self.options.disp_more_left)+dispwidth(self.options.disp_more_right)+2
628
+ sepColWidth = dispwidth(self.options.disp_column_sep)
625
629
  winWidth = self.windowWidth
626
630
  self._visibleColLayout = {}
627
631
  x = 0
628
632
  vcolidx = 0
629
633
  for vcolidx in range(0, self.nVisibleCols):
634
+ width = self.calcSingleColLayout(vcolidx, x, minColWidth)
635
+ if width:
636
+ x += width+sepColWidth
637
+ if x > winWidth-1:
638
+ break
639
+
640
+ self.rightVisibleColIndex = vcolidx
641
+
642
+ def calcSingleColLayout(self, vcolidx:int, x:int=0, minColWidth:int=4):
630
643
  col = self.visibleCols[vcolidx]
631
644
  if col.width is None and len(self.visibleRows) > 0:
632
- vrows = self.visibleRows if self.nRows > 1000 else self.rows
645
+ vrows = self.visibleRows if self.nRows > 1000 else self.rows[:1000] #1964
633
646
  # handle delayed column width-finding
634
647
  col.width = max(col.getMaxWidth(vrows), minColWidth)
635
648
  if vcolidx != self.nVisibleCols-1: # let last column fill up the max width
@@ -638,12 +651,9 @@ class TableSheet(BaseSheet):
638
651
  if col in self.keyCols:
639
652
  width = max(width, 1) # keycols must all be visible
640
653
  if col in self.keyCols or vcolidx >= self.leftVisibleColIndex: # visible columns
641
- self._visibleColLayout[vcolidx] = [x, min(width, winWidth-x)]
642
- x += width+sepColWidth
643
- if x > winWidth-1:
644
- break
654
+ self._visibleColLayout[vcolidx] = [x, min(width, self.windowWidth-x)]
655
+ return width
645
656
 
646
- self.rightVisibleColIndex = vcolidx
647
657
 
648
658
  def drawColHeader(self, scr, y, h, vcolidx):
649
659
  'Compose and draw column header for given vcolidx.'
@@ -670,24 +680,23 @@ class TableSheet(BaseSheet):
670
680
 
671
681
  hdrs = col.name.split('\n')
672
682
  for i in range(h):
673
- name = ' ' # save room at front for LeftMore or sorted arrow
683
+ name = ''
684
+ if colwidth > 2:
685
+ name = ' ' # save room at front for LeftMore or sorted arrow
674
686
 
675
687
  if h-i-1 < len(hdrs):
676
688
  name += hdrs[::-1][h-i-1]
677
689
 
678
- if len(name) > colwidth-1:
679
- name = name[:colwidth-len(self.options.disp_truncator)] + self.options.disp_truncator
680
-
681
690
  if i == h-1:
682
691
  hdrcattr = update_attr(hdrcattr, colors.color_bottom_hdr, 5)
683
692
 
684
- clipdraw(scr, y+i, x, name, hdrcattr.attr, colwidth)
685
- vd.onMouse(scr, y+i, x, 1, colwidth, BUTTON3_RELEASED='rename-col')
693
+ clipdraw(scr, y+i, x, name, hdrcattr, w=colwidth)
694
+ vd.onMouse(scr, x, y+i, colwidth, 1, BUTTON3_RELEASED='rename-col')
686
695
 
687
696
  if C and x+colwidth+len(C) < self.windowWidth and y+i < self.windowWidth:
688
697
  scr.addstr(y+i, x+colwidth, C, sepcattr.attr)
689
698
 
690
- clipdraw(scr, y+h-1, x+colwidth-len(T), T, hdrcattr.attr)
699
+ clipdraw(scr, y+h-1, x+colwidth-len(T), T, hdrcattr)
691
700
 
692
701
  try:
693
702
  if vcolidx == self.leftVisibleColIndex and col not in self.keyCols and self.nonKeyVisibleCols.index(col) > 0:
@@ -699,6 +708,8 @@ class TableSheet(BaseSheet):
699
708
  try:
700
709
  A = ''
701
710
  for j, (sortcol, sortdir) in enumerate(self._ordering):
711
+ if isinstance(sortcol, str):
712
+ sortcol = self.column(sortcol)
702
713
  if col is sortcol:
703
714
  A = self.options.disp_sort_desc[j] if sortdir else self.options.disp_sort_asc[j]
704
715
  scr.addstr(y+h-1, x, A, hdrcattr.attr)
@@ -713,10 +724,7 @@ class TableSheet(BaseSheet):
713
724
  def draw(self, scr):
714
725
  'Draw entire screen onto the `scr` curses object.'
715
726
  if not self.columns:
716
- if self.options.debug:
717
- self.addColumn(Column())
718
- else:
719
- return
727
+ return
720
728
 
721
729
  drawparams = {
722
730
  'isNull': self.isNullFunc(),
@@ -751,7 +759,7 @@ class TableSheet(BaseSheet):
751
759
  y = headerRow + numHeaderRows
752
760
 
753
761
  rows = self.rows[self.topRowIndex:min(self.topRowIndex+self.nScreenRows+1, self.nRows)]
754
- self.checkCursorNoExceptions()
762
+ vd.callNoExceptions(self.checkCursor)
755
763
 
756
764
  for rowidx, row in enumerate(rows):
757
765
  if y >= self.windowHeight-1:
@@ -762,9 +770,10 @@ class TableSheet(BaseSheet):
762
770
  y += self.drawRow(scr, row, self.topRowIndex+rowidx, y, rowcattr, maxheight=self.windowHeight-y-1, **drawparams)
763
771
 
764
772
  if vcolidx+1 < self.nVisibleCols:
765
- scr.addstr(headerRow, self.windowWidth-2, self.options.disp_more_right, colors.color_column_sep)
773
+ scr.addstr(headerRow, self.windowWidth-2, self.options.disp_more_right, colors.color_column_sep.attr)
766
774
 
767
- def calc_height(self, row, displines=None, isNull=None):
775
+ def calc_height(self, row, displines=None, isNull=None, maxheight=1):
776
+ 'render cell contents ifor row into displines'
768
777
  if displines is None:
769
778
  displines = {} # [vcolidx] -> list of lines in that cell
770
779
 
@@ -775,8 +784,8 @@ class TableSheet(BaseSheet):
775
784
  continue
776
785
  col = vcols[vcolidx]
777
786
  cellval = col.getCell(row)
778
- if colwidth > 1 and vd.isNumeric(col):
779
- cellval.display = cellval.display.rjust(colwidth-2)
787
+
788
+ cellval.display = col.display(cellval, colwidth)
780
789
 
781
790
  try:
782
791
  if isNull and isNull(cellval.value):
@@ -785,13 +794,15 @@ class TableSheet(BaseSheet):
785
794
  except (TypeError, ValueError):
786
795
  pass
787
796
 
788
- if col.voffset or col.height > 1:
789
- lines = splitcell(self, cellval.display, width=colwidth-2)
797
+ if maxheight > 1:
798
+ lines = _splitcell(self, cellval.display, width=colwidth-2, maxheight=maxheight)
790
799
  else:
791
800
  lines = [cellval.display]
792
801
  displines[vcolidx] = (col, cellval, lines)
793
802
 
794
- return self.rowHeight
803
+ if len(displines) == 0:
804
+ return 0
805
+ return max(len(lines) for _, _, lines in displines.values())
795
806
 
796
807
  def drawRow(self, scr, row, rowidx, ybase, rowcattr: ColorAttr, maxheight,
797
808
  isNull='',
@@ -820,8 +831,11 @@ class TableSheet(BaseSheet):
820
831
  else:
821
832
  basecellcattr = rowcattr
822
833
 
834
+ # calc_height renders cell contents into displines
823
835
  displines = {} # [vcolidx] -> list of lines in that cell
824
- height = min(self.calc_height(row, displines), maxheight) or 1 # display even empty rows
836
+ self.calc_height(row, displines, maxheight=self.rowHeight)
837
+
838
+ height = min(self.rowHeight, maxheight) or 1 # display even empty rows
825
839
  self._rowLayout[rowidx] = (ybase, height)
826
840
 
827
841
  for vcolidx, (col, cellval, lines) in displines.items():
@@ -838,23 +852,16 @@ class TableSheet(BaseSheet):
838
852
  notewidth = 1 if note else 0
839
853
  if note:
840
854
  notecattr = update_attr(cattr, colors.get_color(cellval.notecolor), 10)
841
- clipdraw(scr, ybase, x+colwidth-notewidth, note, notecattr.attr)
842
-
843
- if voffset >= 0:
844
- if len(lines)-voffset > height:
845
- # last line should always include as much as possible
846
- firstn = sum(len(i)+1 for i in lines[:voffset+height-1])
847
- lines = lines[:voffset+height]
848
- lines[-1] = cellval.display[firstn:][:col.width]
855
+ scr.addstr(ybase, x+colwidth-notewidth, note, notecattr.attr)
849
856
 
850
857
  lines = lines[voffset:]
851
858
 
852
859
  if len(lines) > height:
853
860
  lines = lines[:height]
854
861
  elif len(lines) < height:
855
- lines.extend(['']*(height-len(lines)))
862
+ lines.extend([[('', '')]]*(height-len(lines)))
856
863
 
857
- for i, line in enumerate(lines):
864
+ for i, chunks in enumerate(lines):
858
865
  y = ybase+i
859
866
 
860
867
  if vcolidx == self.rightVisibleColIndex: # right edge of sheet
@@ -889,8 +896,15 @@ class TableSheet(BaseSheet):
889
896
  sepchars = midsep
890
897
 
891
898
  pre = disp_truncator if hoffset != 0 else disp_column_fill
892
- clipdraw(scr, y, x, (pre if colwidth > 2 else '')+line[hoffset:], cattr.attr, w=colwidth-notewidth)
893
- vd.onMouse(scr, y, x, 1, colwidth, BUTTON3_RELEASED='edit-cell')
899
+ prechunks = []
900
+ if colwidth > 2:
901
+ prechunks.append(('', pre))
902
+
903
+ for attr, text in chunks:
904
+ prechunks.append((attr, text[hoffset:]))
905
+
906
+ clipdraw_chunks(scr, y, x, prechunks, cattr, w=colwidth-notewidth)
907
+ vd.onMouse(scr, x, y, colwidth, 1, BUTTON3_RELEASED='edit-cell')
894
908
 
895
909
  if x+colwidth+len(sepchars) <= self.windowWidth:
896
910
  scr.addstr(y, x+colwidth, sepchars, sepcattr.attr)
@@ -904,7 +918,7 @@ class TableSheet(BaseSheet):
904
918
  return height
905
919
 
906
920
  vd.rowNoters = [
907
- lambda sheet, row: sheet.isSelected(row) and sheet.options.disp_selected_note,
921
+ # f(sheet, row) -> character to be displayed on the left side of row
908
922
  ]
909
923
 
910
924
  Sheet = TableSheet # deprecated in 2.0 but still widely used internally
@@ -914,6 +928,7 @@ class SequenceSheet(Sheet):
914
928
  'Sheets with ``ColumnItem`` columns, and rows that are Python sequences (list, namedtuple, etc).'
915
929
  def setCols(self, headerrows):
916
930
  self.columns = []
931
+ vd.clearCaches() #1997
917
932
  for i, colnamelines in enumerate(itertools.zip_longest(*headerrows, fillvalue='')):
918
933
  colnamelines = ['' if c is None else c for c in colnamelines]
919
934
  self.addColumn(ColumnItem(''.join(map(str, colnamelines)), i))
@@ -939,8 +954,7 @@ class SequenceSheet(Sheet):
939
954
  except StopIteration:
940
955
  break
941
956
 
942
- @asyncthread
943
- def reload(self):
957
+ def loader(self):
944
958
  'Skip first options.skip rows; set columns from next options.header rows.'
945
959
 
946
960
  itsource = self.iterload()
@@ -956,72 +970,6 @@ class SequenceSheet(Sheet):
956
970
  for r in vd.Progress(itsource, gerund='loading', total=0):
957
971
  self.addRow(r)
958
972
 
959
- # if an ordering has been specified, sort the sheet
960
- if self._ordering:
961
- vd.sync(self.sort())
962
-
963
-
964
- class IndexSheet(Sheet):
965
- 'Base class for tabular sheets with rows that are Sheets.'
966
- rowtype = 'sheets' # rowdef: Sheet
967
-
968
- columns = [
969
- Column('name', getter=lambda c,r: r.names[-1], setter=lambda c,r,v: setitem(r.names, -1, v)),
970
- ColumnAttr('rows', 'nRows', type=int, width=9),
971
- ColumnAttr('cols', 'nCols', type=int),
972
- ColumnAttr('keys', 'keyColNames'),
973
- ColumnAttr('source'),
974
- ]
975
- nKeys = 1
976
-
977
- def newRow(self):
978
- return Sheet('', columns=[ColumnItem('', 0)], rows=[])
979
-
980
- def openRow(self, row):
981
- return row # rowdef is Sheet
982
-
983
- def getSheet(self, k):
984
- for vs in self.rows:
985
- if vs.name == k:
986
- return vs
987
-
988
- def addRow(self, sheet, **kwargs):
989
- super().addRow(sheet, **kwargs)
990
- if not self.options.load_lazy and not sheet.options.load_lazy:
991
- sheet.ensureLoaded()
992
-
993
- @asyncthread
994
- def reloadSheets(self, sheets):
995
- for vs in vd.Progress(sheets):
996
- vs.reload()
997
-
998
-
999
- class SheetsSheet(IndexSheet):
1000
- columns = [
1001
- ColumnAttr('name'),
1002
- ColumnAttr('type', '__class__.__name__'),
1003
- ColumnAttr('pane', type=int),
1004
- Column('shortcut', getter=lambda c,r: getattr(r, 'shortcut'), setter=lambda c,r,v: setattr(r, '_shortcut', v)),
1005
- ColumnAttr('nRows', type=int),
1006
- ColumnAttr('nCols', type=int),
1007
- ColumnAttr('nVisibleCols', type=int),
1008
- ColumnAttr('cursorDisplay'),
1009
- ColumnAttr('keyColNames'),
1010
- ColumnAttr('source'),
1011
- ColumnAttr('progressPct'),
1012
- # ColumnAttr('threads', 'currentThreads', type=vlen),
1013
- ]
1014
- precious = False
1015
- nKeys = 1
1016
- def reload(self):
1017
- self.rows = self.source
1018
-
1019
- def sort(self):
1020
- self.rows[1:] = sorted(self.rows[1:], key=self.sortkey)
1021
-
1022
- class GlobalSheetsSheet(SheetsSheet): #1620
1023
- def sort(self):
1024
- IndexSheet.sort(self)
1025
973
 
1026
974
  @VisiData.property
1027
975
  @drawcache
@@ -1077,15 +1025,6 @@ def push(vd, vs, pane=0, load=True):
1077
1025
  vs.ensureLoaded()
1078
1026
 
1079
1027
 
1080
- @VisiData.lazy_property
1081
- def allSheetsSheet(vd):
1082
- return GlobalSheetsSheet("sheets_all", source=vd.allSheets)
1083
-
1084
- @VisiData.lazy_property
1085
- def sheetsSheet(vd):
1086
- return SheetsSheet("sheets", source=vd.sheets)
1087
-
1088
-
1089
1028
  @VisiData.api
1090
1029
  def quit(vd, *sheets):
1091
1030
  'Remove *sheets* from sheets stack, asking for confirmation if needed.'
@@ -1098,10 +1037,10 @@ def quit(vd, *sheets):
1098
1037
 
1099
1038
  @BaseSheet.api
1100
1039
  def confirmQuit(vs, verb='quit'):
1101
- if vs.options.quitguard and vs.precious and vs.hasBeenModified:
1040
+ if vs.options.quitguard and vs.precious and vs.hasBeenModified and not vd._nextCommands:
1102
1041
  vd.draw_all()
1103
1042
  vd.confirm(f'{verb} modified sheet "{vs.name}"? ')
1104
- elif vs.options.getonly('quitguard', vs, False): # if this sheet is specifically guarded
1043
+ elif vs.options.getonly('quitguard', vs, False) and not vd._nextCommands: # if this sheet is specifically guarded
1105
1044
  vd.draw_all()
1106
1045
  vd.confirm(f'{verb} guarded sheet "{vs.name}"? ')
1107
1046
 
@@ -1126,7 +1065,9 @@ def quitAndReleaseMemory(vs):
1126
1065
  vs.source.lines.clear() # clear cache of read lines
1127
1066
 
1128
1067
  if vs.precious: # only precious sheets have meaningful data
1068
+ vs.confirmQuit('quit')
1129
1069
  vs.rows.clear()
1070
+ vs.rows = UNLOADED
1130
1071
  vd.remove(vs)
1131
1072
  vd.allSheets.remove(vs)
1132
1073
 
@@ -1161,18 +1102,14 @@ def async_deepcopy(sheet, rowlist):
1161
1102
  return ret
1162
1103
 
1163
1104
 
1164
- IndexSheet.options.header = 0
1165
- IndexSheet.options.skip = 0
1166
1105
 
1167
1106
  BaseSheet.init('pane', lambda: 1)
1168
1107
 
1169
- Sheet.init('_ordering', list, copy=True) # (col:Column, reverse:bool)
1108
+ Sheet.init('_ordering', list, copy=False) # (col:Column, reverse:bool)
1170
1109
 
1171
- globalCommand('S', 'sheets-stack', 'vd.push(vd.sheetsSheet)', 'open Sheets Stack: join or jump between the active sheets on the current stack')
1172
- globalCommand('gS', 'sheets-all', 'vd.push(vd.allSheetsSheet)', 'open Sheets Sheet: join or jump between all sheets from current session')
1173
1110
 
1174
- BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload(); recalc(); status("reloaded")', 'Reload current sheet'),
1175
- Sheet.addCommand('^G', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line'),
1111
+ BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload()', 'Reload current sheet')
1112
+ Sheet.addCommand('^G', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line')
1176
1113
 
1177
1114
  Sheet.addCommand('!', 'key-col', 'toggleKeys([cursorCol])', 'toggle current column as a key column')
1178
1115
  Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current column as a key column')
@@ -1180,10 +1117,10 @@ Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current c
1180
1117
  Sheet.addCommand('e', 'edit-cell', 'cursorCol.setValues([cursorRow], editCell(cursorVisibleColIndex)) if not (cursorRow is None) else fail("no rows to edit")', 'edit contents of current cell')
1181
1118
  Sheet.addCommand('ge', 'setcol-input', 'cursorCol.setValuesTyped(selectedRows, input("set selected to: ", value=cursorDisplay))', 'set contents of current column for selected rows to same input')
1182
1119
 
1183
- Sheet.addCommand('"', 'dup-selected', 'vs=copy(sheet); vs.name += "_selectedref"; vs.reload=lambda vs=vs,rows=selectedRows: setattr(vs, "rows", list(rows)); vd.push(vs)', 'open duplicate sheet with only selected rows'),
1184
- Sheet.addCommand('g"', 'dup-rows', 'vs=copy(sheet); vs.name+="_copy"; vs.rows=list(rows); status("copied "+vs.name); vs.select(selectedRows); vd.push(vs)', 'open duplicate sheet with all rows'),
1185
- Sheet.addCommand('z"', 'dup-selected-deep', 'vs = deepcopy(sheet); vs.name += "_selecteddeepcopy"; vs.rows = vs.async_deepcopy(selectedRows); vd.push(vs); status("pushed sheet with async deepcopy of selected rows")', 'open duplicate sheet with deepcopy of selected rows'),
1186
- Sheet.addCommand('gz"', 'dup-rows-deep', 'vs = deepcopy(sheet); vs.name += "_deepcopy"; vs.rows = vs.async_deepcopy(rows); vd.push(vs); status("pushed sheet with async deepcopy of all rows")', 'open duplicate sheet with deepcopy of all rows'),
1120
+ Sheet.addCommand('"', 'dup-selected', 'vs=copy(sheet); vs.name += "_selectedref"; vs.reload=lambda vs=vs,rows=selectedRows: setattr(vs, "rows", list(rows)); vd.push(vs)', 'open a duplicate sheet with only the selected rows')
1121
+ Sheet.addCommand('g"', 'dup-rows', 'vs=copy(sheet); vs.name+="_copy"; vs.rows=list(rows); status("copied "+vs.name); vs.select(selectedRows); vd.push(vs)', 'open a duplicate sheet with all rows')
1122
+ Sheet.addCommand('z"', 'dup-selected-deep', 'vs = deepcopy(sheet); vs.name += "_selecteddeepcopy"; vs.rows = vs.async_deepcopy(selectedRows); vd.push(vs); status("pushed sheet with async deepcopy of selected rows")', 'open duplicate sheet with deepcopy of selected rows')
1123
+ Sheet.addCommand('gz"', 'dup-rows-deep', 'vs = deepcopy(sheet); vs.name += "_deepcopy"; vs.rows = vs.async_deepcopy(rows); vd.push(vs); status("pushed sheet with async deepcopy of all rows")', 'open duplicate sheet with deepcopy of all rows')
1187
1124
 
1188
1125
  Sheet.addCommand('z~', 'type-any', 'cursorCol.type = anytype', 'set type of current column to anytype')
1189
1126
  Sheet.addCommand('~', 'type-string', 'cursorCol.type = str', 'set type of current column to str')
@@ -1192,14 +1129,6 @@ Sheet.addCommand('z#', 'type-len', 'cursorCol.type = vlen', 'set type of current
1192
1129
  Sheet.addCommand('%', 'type-float', 'cursorCol.type = float', 'set type of current column to float')
1193
1130
  Sheet.addCommand('', 'type-floatlocale', 'cursorCol.type = floatlocale', 'set type of current column to float using system locale set in LC_NUMERIC')
1194
1131
 
1195
- # when diving into a sheet, remove the index unless it is precious
1196
- IndexSheet.addCommand('g^R', 'reload-selected', 'reloadSheets(selectedRows or rows)', 'reload all selected sheets')
1197
- SheetsSheet.addCommand('gC', 'columns-selected', 'vd.push(ColumnsSheet("all_columns", source=selectedRows))', 'open Columns Sheet with all visible columns from selected sheets')
1198
- SheetsSheet.addCommand('gI', 'describe-selected', 'vd.push(DescribeSheet("describe_all", source=selectedRows))', 'open Describe Sheet with all visible columns from selected sheets')
1199
- SheetsSheet.addCommand('z^C', 'cancel-row', 'cancelThread(*cursorRow.currentThreads)', 'abort async thread for current sheet')
1200
- SheetsSheet.addCommand('gz^C', 'cancel-rows', 'for vs in selectedRows: cancelThread(*vs.currentThreads)', 'abort async threads for selected sheets')
1201
- SheetsSheet.addCommand(ENTER, 'open-row', 'dest=cursorRow; vd.sheets.remove(sheet) if not sheet.precious else None; vd.push(openRow(dest))', 'open sheet referenced in current row')
1202
-
1203
1132
  BaseSheet.addCommand('q', 'quit-sheet', 'vd.quit(sheet)', 'quit current sheet')
1204
1133
  BaseSheet.addCommand('Q', 'quit-sheet-free', 'quitAndReleaseMemory()', 'discard current sheet and free memory')
1205
1134
  globalCommand('gq', 'quit-all', 'vd.quit(*vd.sheets)', 'quit all sheets (clean exit)')
@@ -1210,20 +1139,19 @@ BaseSheet.addCommand('^I', 'splitwin-swap', 'vd.activePane = 1 if sheet.pane ==
1210
1139
  BaseSheet.addCommand('g^I', 'splitwin-swap-pane', 'vd.options.disp_splitwin_pct=-vd.options.disp_splitwin_pct', 'swap panes onscreen')
1211
1140
  BaseSheet.addCommand('zZ', 'splitwin-input', 'vd.options.disp_splitwin_pct = input("% height for split window: ", value=vd.options.disp_splitwin_pct)', 'set split pane to specific size')
1212
1141
 
1213
- BaseSheet.addCommand('^L', 'redraw', 'vd.redraw(); sheet.refresh()', 'Refresh screen')
1142
+ BaseSheet.addCommand('^L', 'redraw', 'sheet.refresh(); vd.redraw()', 'Refresh screen')
1214
1143
  BaseSheet.addCommand(None, 'guard-sheet', 'options.set("quitguard", True, sheet); status("guarded")', 'Set quitguard on current sheet to confirm before quit')
1215
1144
  BaseSheet.addCommand(None, 'guard-sheet-off', 'options.set("quitguard", False, sheet); status("unguarded")', 'Unset quitguard on current sheet to not confirm before quit')
1216
- BaseSheet.addCommand(None, 'open-source', 'vd.push(source)', 'jump to the source of this sheet')
1145
+ BaseSheet.addCommand(None, 'open-source', 'vd.replace(source)', 'jump to the source of this sheet')
1217
1146
 
1218
1147
  BaseSheet.bindkey('KEY_RESIZE', 'redraw')
1219
1148
 
1220
1149
  BaseSheet.addCommand('A', 'open-new', 'vd.push(vd.newSheet("unnamed", 1))', 'Open new empty sheet')
1221
1150
 
1222
- Sheet.addCommand('^', 'rename-col', 'vd.addUndoColNames([cursorCol]); cursorCol.name = editCell(cursorVisibleColIndex, -1)', 'rename current column')
1223
- Sheet.addCommand('z^', 'rename-col-selected', 'updateColNames(selectedRows or [cursorRow], [sheet.cursorCol], overwrite=True)', 'rename current column to combined contents of current cell in selected rows (or current row)')
1224
- Sheet.addCommand('g^', 'rename-cols-row', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols)', 'rename all unnamed visible columns to contents of selected rows (or current row)')
1225
- Sheet.addCommand('gz^', 'rename-cols-selected', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols, overwrite=True)', 'rename all visible columns to combined contents of selected rows (or current row)')
1226
- BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=sheet.name)', 'Rename current sheet')
1151
+ BaseSheet.addCommand('`', 'open-source', 'vd.push(source)', 'open source sheet')
1152
+ BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=cleanName(sheet.name))', 'Rename current sheet')
1153
+
1154
+ Sheet.addCommand('', 'addcol-source', 'source .addColumn(copy(cursorCol)) if isinstance (source, BaseSheet) else error("source must be sheet")', 'add copy of current column to source sheet') #988 frosencrantz
1227
1155
 
1228
1156
 
1229
1157
  @Column.api
@@ -1232,3 +1160,47 @@ def formatter_enum(col, fmtdict):
1232
1160
 
1233
1161
  Sheet.addCommand('', 'setcol-formatter', 'cursorCol.formatter=input("set formatter to: ", value=cursorCol.formatter or "generic")', 'set formatter for current column (generic, json, python)')
1234
1162
  Sheet.addCommand('', 'setcol-format-enum', 'cursorCol.fmtstr=input("format replacements (k=v): ", value=f"{cursorDisplay}=", i=len(cursorDisplay)+1); cursorCol.formatter="enum"', 'add secondary type translator to current column from input enum (space-separated)')
1163
+
1164
+
1165
+ vd.addGlobals(
1166
+ RowColorizer=RowColorizer,
1167
+ CellColorizer=CellColorizer,
1168
+ ColumnColorizer=ColumnColorizer,
1169
+ RecursiveExprException=RecursiveExprException,
1170
+ LazyComputeRow=LazyComputeRow,
1171
+ Sheet=Sheet,
1172
+ TableSheet=TableSheet,
1173
+ SequenceSheet=SequenceSheet)
1174
+
1175
+ vd.addMenuItems('''
1176
+ File > New > open-new
1177
+ File > Rename > rename-sheet
1178
+ File > Guard > on > guard-sheet
1179
+ File > Guard > off > guard-sheet-off
1180
+ File > Duplicate > selected rows by ref > dup-selected
1181
+ File > Duplicate > all rows by ref > dup-rows
1182
+ File > Duplicate > selected rows deep > dup-selected-deep
1183
+ File > Duplicate > all rows deep > dup-rows-deep
1184
+ File > Reload > rows and columns > reload-sheet
1185
+ File > Quit > top sheet > quit-sheet
1186
+ File > Quit > all sheets > quit-all
1187
+ Edit > Modify > current cell > input > edit-cell
1188
+ Edit > Modify > selected cells > from input > setcol-input
1189
+ View > Sheets > stack > sheets-stack
1190
+ View > Sheets > all > sheets-all
1191
+ View > Other sheet > source sheet > open-source
1192
+ View > Split pane > in half > splitwin-half
1193
+ View > Split pane > in percent > splitwin-input
1194
+ View > Split pane > unsplit > splitwin-close
1195
+ View > Split pane > swap panes > splitwin-swap-pane
1196
+ View > Split pane > goto other pane > splitwin-swap
1197
+ View > Refresh screen > redraw
1198
+ Column > Type as > anytype > type-any
1199
+ Column > Type as > string > type-string
1200
+ Column > Type as > integer > type-int
1201
+ Column > Type as > float > type-float
1202
+ Column > Type as > locale float > type-floatlocale
1203
+ Column > Type as > length > type-len
1204
+ Column > Key > toggle current column > key-col
1205
+ Column > Key > unkey current column > key-col-off
1206
+ ''')