visidata 2.11.1__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +72 -91
- visidata/_input.py +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +78 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- {vgit → visidata/apps/vgit}/blame.py +5 -2
- {vgit → visidata/apps/vgit}/branch.py +31 -16
- {vgit → visidata/apps/vgit}/config.py +3 -3
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- {vgit → visidata/apps/vgit}/grep.py +6 -5
- visidata/apps/vgit/log.py +81 -0
- {vgit → visidata/apps/vgit}/main.py +18 -5
- {vgit → visidata/apps/vgit}/remote.py +8 -4
- visidata/apps/vgit/repos.py +71 -0
- {vgit → visidata/apps/vgit}/setup.py +6 -4
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- {vgit → visidata/apps/vgit}/statusbar.py +2 -0
- visidata/basesheet.py +63 -51
- visidata/canvas.py +208 -93
- visidata/choose.py +6 -6
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +73 -17
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +88 -114
- visidata/color.py +142 -56
- visidata/column.py +121 -129
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +6 -2
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +22 -4
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +197 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +3 -1
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +32 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +77 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +200 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +20 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +2 -4
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +54 -12
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macros.py +129 -42
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -155
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -149
- visidata/man/vd.txt +291 -154
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +78 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +113 -32
- visidata/pivot.py +73 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +55 -205
- visidata/rename_col.py +20 -0
- visidata/save.py +37 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -25
- visidata/sheets.py +239 -260
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +114 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/benchmark.csv +52 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +65 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +28 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +89 -40
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +17 -5
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
- visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
- visidata-3.0.1.dist-info/RECORD +258 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
- vgit/__init__.py +0 -1
- vgit/gitsheet.py +0 -164
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.1.data/scripts/vgit +0 -9
- visidata-2.11.1.dist-info/RECORD +0 -155
- {vgit → visidata/apps/vgit}/__main__.py +0 -0
- {vgit → visidata/apps/vgit}/abort.py +0 -0
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.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
|
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
|
-
|
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
|
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
|
86
|
-
|
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')
|
@@ -116,8 +65,15 @@ class LazyComputeRow:
|
|
116
65
|
lcmobj._lcm = LazyChainMap(self.sheet, self.col, *vd.contexts)
|
117
66
|
return lcmobj._lcm
|
118
67
|
|
68
|
+
def __iter__(self):
|
69
|
+
yield from self.sheet._ordered_colnames
|
70
|
+
yield from self._lcm.keys()
|
71
|
+
yield 'row'
|
72
|
+
yield 'sheet'
|
73
|
+
yield 'col'
|
74
|
+
|
119
75
|
def keys(self):
|
120
|
-
return self.
|
76
|
+
return list(self.__iter__())
|
121
77
|
|
122
78
|
def __str__(self):
|
123
79
|
return str(self.as_dict())
|
@@ -169,6 +125,21 @@ class TableSheet(BaseSheet):
|
|
169
125
|
_coltype = SettableColumn
|
170
126
|
|
171
127
|
rowtype = 'rows'
|
128
|
+
guide = '# {sheet.help_title}\n{sheet.help_columns}\n'
|
129
|
+
|
130
|
+
@property
|
131
|
+
def help_title(self):
|
132
|
+
if isinstance(self.source, visidata.Path):
|
133
|
+
return 'Source Table'
|
134
|
+
else:
|
135
|
+
return 'Table Sheet'
|
136
|
+
|
137
|
+
@property
|
138
|
+
def help_columns(self):
|
139
|
+
hiddenCols = [c for c in self.columns if c.hidden]
|
140
|
+
if hiddenCols:
|
141
|
+
return f'- `gv` to unhide {len(hiddenCols)} hidden columns'
|
142
|
+
return ''
|
172
143
|
|
173
144
|
columns = [] # list of Column
|
174
145
|
colorizers = [ # list of Colorizer
|
@@ -176,14 +147,13 @@ class TableSheet(BaseSheet):
|
|
176
147
|
ColumnColorizer(2, 'color_current_col', lambda s,c,r,v: c is s.cursorCol),
|
177
148
|
ColumnColorizer(1, 'color_key_col', lambda s,c,r,v: c and c.keycol),
|
178
149
|
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
150
|
RowColorizer(1, 'color_error', lambda s,c,r,v: isinstance(r, (Exception, TypedExceptionWrapper))),
|
151
|
+
CellColorizer(3, 'color_current_cell', lambda s,c,r,v: c is s.cursorCol and r is s.cursorRow)
|
181
152
|
]
|
182
153
|
nKeys = 0 # columns[:nKeys] are key columns
|
183
154
|
|
184
|
-
def __init__(self, *names, **kwargs):
|
185
|
-
super().__init__(*names, **kwargs)
|
186
|
-
self.rows = UNLOADED # list of opaque row objects (UNLOADED before first reload)
|
155
|
+
def __init__(self, *names, rows=UNLOADED, **kwargs):
|
156
|
+
super().__init__(*names, rows=rows, **kwargs)
|
187
157
|
self.cursorRowIndex = 0 # absolute index of cursor into self.rows
|
188
158
|
self.cursorVisibleColIndex = 0 # index of cursor into self.visibleCols
|
189
159
|
|
@@ -196,11 +166,13 @@ class TableSheet(BaseSheet):
|
|
196
166
|
self._visibleColLayout = {} # [vcolidx] -> (x, w)
|
197
167
|
|
198
168
|
# list of all columns in display order
|
199
|
-
self.
|
200
|
-
self.
|
169
|
+
self.initialCols = kwargs.pop('columns', None) or type(self).columns
|
170
|
+
self.resetCols()
|
171
|
+
|
172
|
+
self._colorizers = self.classColorizers
|
201
173
|
self.recalc() # set .sheet on columns and start caches
|
202
174
|
|
203
|
-
self.
|
175
|
+
self._ordering = [] # list of (col:Column, reverse:bool)
|
204
176
|
|
205
177
|
self.__dict__.update(kwargs) # also done earlier in BaseSheet.__init__
|
206
178
|
|
@@ -216,33 +188,30 @@ class TableSheet(BaseSheet):
|
|
216
188
|
def addColorizer(self, c):
|
217
189
|
'Add Colorizer *c* to the list of colorizers for this sheet.'
|
218
190
|
self._colorizers.append(c)
|
191
|
+
self._colorizers = sorted(self._colorizers, key=lambda x: x.precedence, reverse=True)
|
219
192
|
|
220
193
|
def removeColorizer(self, c):
|
221
194
|
'Remove Colorizer *c* from the list of colorizers for this sheet.'
|
222
195
|
self._colorizers.remove(c)
|
223
196
|
|
224
|
-
@
|
225
|
-
def
|
197
|
+
@property
|
198
|
+
def classColorizers(self) -> list:
|
199
|
+
'List of all colorizers from sheet class hierarchy in precedence order (highest precedence first)'
|
226
200
|
# all colorizers must be in the same bucket
|
227
201
|
# otherwise, precedence does not get applied properly
|
228
202
|
_colorizers = set()
|
229
|
-
def allParents(cls):
|
230
|
-
yield from cls.__bases__
|
231
|
-
for b in cls.__bases__:
|
232
|
-
yield from allParents(b)
|
233
203
|
|
234
|
-
for b in [self] + list(
|
204
|
+
for b in [self] + list(type(self).superclasses()):
|
235
205
|
for c in getattr(b, 'colorizers', []):
|
236
206
|
_colorizers.add(c)
|
237
207
|
|
238
|
-
_colorizers |= set(self._colorizers)
|
239
208
|
return sorted(_colorizers, key=lambda x: x.precedence, reverse=True)
|
240
209
|
|
241
210
|
def _colorize(self, col, row, value=None) -> ColorAttr:
|
242
|
-
'
|
211
|
+
'Return ColorAttr for the given colorizers/col/row/value'
|
243
212
|
|
244
213
|
colorstack = []
|
245
|
-
for colorizer in self.
|
214
|
+
for colorizer in self._colorizers:
|
246
215
|
try:
|
247
216
|
r = colorizer.func(self, col, row, value)
|
248
217
|
if r:
|
@@ -283,7 +252,33 @@ class TableSheet(BaseSheet):
|
|
283
252
|
|
284
253
|
@asyncthread
|
285
254
|
def reload(self):
|
286
|
-
'Load rows and
|
255
|
+
'Load or reload rows and columns from ``self.source``. Async. Override resetCols() or loader() in subclass.'
|
256
|
+
with visidata.ScopedSetattr(self, 'loading', True):
|
257
|
+
self.resetCols()
|
258
|
+
self.beforeLoad()
|
259
|
+
try:
|
260
|
+
self.loader()
|
261
|
+
vd.debug(f'finished loading {self}')
|
262
|
+
finally:
|
263
|
+
self.afterLoad()
|
264
|
+
|
265
|
+
self.recalc()
|
266
|
+
|
267
|
+
def beforeLoad(self):
|
268
|
+
pass
|
269
|
+
|
270
|
+
def resetCols(self):
|
271
|
+
'Reset columns to class settings'
|
272
|
+
self.columns = []
|
273
|
+
for c in self.initialCols:
|
274
|
+
self.addColumn(deepcopy(c))
|
275
|
+
if self.options.disp_help > c.max_help:
|
276
|
+
c.hide()
|
277
|
+
|
278
|
+
self.setKeys(self.columns[:self.nKeys])
|
279
|
+
|
280
|
+
def loader(self):
|
281
|
+
'Reset rows and sync load ``source`` via iterload. Overrideable.'
|
287
282
|
self.rows = []
|
288
283
|
try:
|
289
284
|
with vd.Progress(gerund='loading', total=0):
|
@@ -292,15 +287,17 @@ class TableSheet(BaseSheet):
|
|
292
287
|
except FileNotFoundError:
|
293
288
|
return # let it be a blank sheet without error
|
294
289
|
|
295
|
-
# if an ordering has been specified, sort the sheet
|
296
|
-
if self._ordering:
|
297
|
-
vd.sync(self.sort())
|
298
|
-
|
299
290
|
def iterload(self):
|
300
291
|
'Generate rows from ``self.source``. Override in subclass.'
|
301
292
|
if False:
|
302
293
|
yield vd.fail('no iterload for this loader yet')
|
303
294
|
|
295
|
+
def afterLoad(self):
|
296
|
+
'hook for after loading has finished. Overrideable (be sure to call super).'
|
297
|
+
# if an ordering has been specified, sort the sheet
|
298
|
+
if self._ordering:
|
299
|
+
vd.sync(self.sort())
|
300
|
+
|
304
301
|
def iterrows(self):
|
305
302
|
if self.rows is UNLOADED:
|
306
303
|
try:
|
@@ -324,10 +321,24 @@ class TableSheet(BaseSheet):
|
|
324
321
|
'Copy sheet design but remain unloaded. Deepcopy columns so their attributes (width, type, name) may be adjusted independently of the original.'
|
325
322
|
ret = super().__copy__()
|
326
323
|
ret.rows = UNLOADED
|
327
|
-
|
328
|
-
ret.
|
329
|
-
|
330
|
-
|
324
|
+
|
325
|
+
ret.columns = []
|
326
|
+
|
327
|
+
col_mapping = {}
|
328
|
+
for c in self.columns:
|
329
|
+
new_col = copy(c)
|
330
|
+
col_mapping[c] = new_col
|
331
|
+
ret.addColumn(new_col)
|
332
|
+
|
333
|
+
ret.setKeys([col_mapping[c] for c in self.columns if c.keycol])
|
334
|
+
|
335
|
+
ret._ordering = []
|
336
|
+
for sortcol,reverse in self._ordering:
|
337
|
+
if isinstance(sortcol, str):
|
338
|
+
ret._ordering.append((sortcol,reverse))
|
339
|
+
else:
|
340
|
+
ret._ordering.append((col_mapping[sortcol],reverse))
|
341
|
+
|
331
342
|
ret.topRowIndex = ret.cursorRowIndex = 0
|
332
343
|
return ret
|
333
344
|
|
@@ -350,15 +361,18 @@ class TableSheet(BaseSheet):
|
|
350
361
|
memo[id(self)] = ret
|
351
362
|
return ret
|
352
363
|
|
353
|
-
def
|
364
|
+
def __str__(self):
|
354
365
|
return self.name
|
355
366
|
|
367
|
+
def __repr__(self):
|
368
|
+
return f'<{type(self).__name__}: {self.name}>'
|
369
|
+
|
356
370
|
def evalExpr(self, expr, row=None, col=None):
|
357
371
|
if row is not None:
|
358
372
|
# contexts are cached by sheet/rowid for duration of drawcycle
|
359
373
|
contexts = vd._evalcontexts.setdefault((self, self.rowid(row), col), LazyComputeRow(self, row, col=col))
|
360
374
|
else:
|
361
|
-
contexts =
|
375
|
+
contexts = dict(sheet=self)
|
362
376
|
|
363
377
|
return eval(expr, vd.getGlobals(), contexts)
|
364
378
|
|
@@ -390,7 +404,8 @@ class TableSheet(BaseSheet):
|
|
390
404
|
@property
|
391
405
|
def cursorRow(self):
|
392
406
|
'The row object at the row cursor.'
|
393
|
-
|
407
|
+
idx = self.cursorRowIndex
|
408
|
+
return self.rows[idx] if self.nRows > idx else None
|
394
409
|
|
395
410
|
@property
|
396
411
|
def visibleRows(self): # onscreen rows
|
@@ -402,16 +417,6 @@ class TableSheet(BaseSheet):
|
|
402
417
|
'List of non-hidden columns in display order.'
|
403
418
|
return self.keyCols + [c for c in self.columns if not c.hidden and not c.keycol]
|
404
419
|
|
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
420
|
@drawcache_property
|
416
421
|
def keyCols(self):
|
417
422
|
'List of visible key columns.'
|
@@ -430,7 +435,10 @@ class TableSheet(BaseSheet):
|
|
430
435
|
@property
|
431
436
|
def cursorColIndex(self):
|
432
437
|
'Index of current column into `Sheet.columns`. Linear search; prefer `cursorCol` or `cursorVisibleColIndex`.'
|
433
|
-
|
438
|
+
try:
|
439
|
+
return self.columns.index(self.cursorCol)
|
440
|
+
except ValueError:
|
441
|
+
return None
|
434
442
|
|
435
443
|
@property
|
436
444
|
def nonKeyVisibleCols(self):
|
@@ -442,6 +450,13 @@ class TableSheet(BaseSheet):
|
|
442
450
|
'String of key column names, for SheetsSheet convenience.'
|
443
451
|
return ' '.join(c.name for c in self.keyCols)
|
444
452
|
|
453
|
+
@keyColNames.setter
|
454
|
+
def keyColNames(self, v): #2122
|
455
|
+
'Set key columns on this sheet to the space-separated list of column names.'
|
456
|
+
newkeys = [self.column(colname) for colname in v.split()]
|
457
|
+
self.unsetKeys(self.keyCols)
|
458
|
+
self.setKeys(newkeys)
|
459
|
+
|
445
460
|
@property
|
446
461
|
def cursorCell(self):
|
447
462
|
'Displayed value (DisplayWrapper) at current row and column.'
|
@@ -449,7 +464,7 @@ class TableSheet(BaseSheet):
|
|
449
464
|
|
450
465
|
@property
|
451
466
|
def cursorDisplay(self):
|
452
|
-
'Displayed value (DisplayWrapper.
|
467
|
+
'Displayed value (DisplayWrapper.text) at current row and column.'
|
453
468
|
return self.cursorCol.getDisplayValue(self.cursorRow)
|
454
469
|
|
455
470
|
@property
|
@@ -506,12 +521,10 @@ class TableSheet(BaseSheet):
|
|
506
521
|
|
507
522
|
if index is not None:
|
508
523
|
self.setModified()
|
509
|
-
else:
|
510
|
-
for col in cols:
|
511
|
-
col.defer = self.defer
|
512
524
|
|
513
525
|
for i, col in enumerate(cols):
|
514
526
|
col.name = self.maybeClean(col.name)
|
527
|
+
col.defer = self.defer
|
515
528
|
|
516
529
|
vd.addUndo(self.columns.remove, col)
|
517
530
|
idx = len(self.columns) if index is None else index
|
@@ -607,7 +620,7 @@ class TableSheet(BaseSheet):
|
|
607
620
|
break
|
608
621
|
mincolidx, maxcolidx = min(self._visibleColLayout.keys()), max(self._visibleColLayout.keys())
|
609
622
|
if self.cursorVisibleColIndex < mincolidx:
|
610
|
-
self.leftVisibleColIndex -= max((self.cursorVisibleColIndex -
|
623
|
+
self.leftVisibleColIndex -= max((self.cursorVisibleColIndex - mincolidx)//2, 1)
|
611
624
|
continue
|
612
625
|
elif self.cursorVisibleColIndex > maxcolidx:
|
613
626
|
self.leftVisibleColIndex += max((maxcolidx - self.cursorVisibleColIndex)//2, 1)
|
@@ -620,16 +633,25 @@ class TableSheet(BaseSheet):
|
|
620
633
|
|
621
634
|
def calcColLayout(self):
|
622
635
|
'Set right-most visible column, based on calculation.'
|
623
|
-
minColWidth =
|
624
|
-
sepColWidth =
|
636
|
+
minColWidth = dispwidth(self.options.disp_more_left)+dispwidth(self.options.disp_more_right)+2
|
637
|
+
sepColWidth = dispwidth(self.options.disp_column_sep)
|
625
638
|
winWidth = self.windowWidth
|
626
639
|
self._visibleColLayout = {}
|
627
640
|
x = 0
|
628
641
|
vcolidx = 0
|
629
642
|
for vcolidx in range(0, self.nVisibleCols):
|
643
|
+
width = self.calcSingleColLayout(vcolidx, x, minColWidth)
|
644
|
+
if width:
|
645
|
+
x += width+sepColWidth
|
646
|
+
if x > winWidth-1:
|
647
|
+
break
|
648
|
+
|
649
|
+
self.rightVisibleColIndex = vcolidx
|
650
|
+
|
651
|
+
def calcSingleColLayout(self, vcolidx:int, x:int=0, minColWidth:int=4):
|
630
652
|
col = self.visibleCols[vcolidx]
|
631
653
|
if col.width is None and len(self.visibleRows) > 0:
|
632
|
-
vrows = self.visibleRows if self.nRows > 1000 else self.rows
|
654
|
+
vrows = self.visibleRows if self.nRows > 1000 else self.rows[:1000] #1964
|
633
655
|
# handle delayed column width-finding
|
634
656
|
col.width = max(col.getMaxWidth(vrows), minColWidth)
|
635
657
|
if vcolidx != self.nVisibleCols-1: # let last column fill up the max width
|
@@ -638,12 +660,9 @@ class TableSheet(BaseSheet):
|
|
638
660
|
if col in self.keyCols:
|
639
661
|
width = max(width, 1) # keycols must all be visible
|
640
662
|
if col in self.keyCols or vcolidx >= self.leftVisibleColIndex: # visible columns
|
641
|
-
self._visibleColLayout[vcolidx] = [x, min(width,
|
642
|
-
|
643
|
-
if x > winWidth-1:
|
644
|
-
break
|
663
|
+
self._visibleColLayout[vcolidx] = [x, min(width, self.windowWidth-x)]
|
664
|
+
return width
|
645
665
|
|
646
|
-
self.rightVisibleColIndex = vcolidx
|
647
666
|
|
648
667
|
def drawColHeader(self, scr, y, h, vcolidx):
|
649
668
|
'Compose and draw column header for given vcolidx.'
|
@@ -670,24 +689,23 @@ class TableSheet(BaseSheet):
|
|
670
689
|
|
671
690
|
hdrs = col.name.split('\n')
|
672
691
|
for i in range(h):
|
673
|
-
name = '
|
692
|
+
name = ''
|
693
|
+
if colwidth > 2:
|
694
|
+
name = ' ' # save room at front for LeftMore or sorted arrow
|
674
695
|
|
675
696
|
if h-i-1 < len(hdrs):
|
676
697
|
name += hdrs[::-1][h-i-1]
|
677
698
|
|
678
|
-
if len(name) > colwidth-1:
|
679
|
-
name = name[:colwidth-len(self.options.disp_truncator)] + self.options.disp_truncator
|
680
|
-
|
681
699
|
if i == h-1:
|
682
700
|
hdrcattr = update_attr(hdrcattr, colors.color_bottom_hdr, 5)
|
683
701
|
|
684
|
-
clipdraw(scr, y+i, x, name, hdrcattr
|
685
|
-
vd.onMouse(scr, y+i,
|
702
|
+
clipdraw(scr, y+i, x, name, hdrcattr, w=colwidth)
|
703
|
+
vd.onMouse(scr, x, y+i, colwidth, 1, BUTTON3_RELEASED='rename-col')
|
686
704
|
|
687
705
|
if C and x+colwidth+len(C) < self.windowWidth and y+i < self.windowWidth:
|
688
706
|
scr.addstr(y+i, x+colwidth, C, sepcattr.attr)
|
689
707
|
|
690
|
-
clipdraw(scr, y+h-1, x+colwidth-len(T), T, hdrcattr
|
708
|
+
clipdraw(scr, y+h-1, x+colwidth-len(T), T, hdrcattr)
|
691
709
|
|
692
710
|
try:
|
693
711
|
if vcolidx == self.leftVisibleColIndex and col not in self.keyCols and self.nonKeyVisibleCols.index(col) > 0:
|
@@ -699,6 +717,8 @@ class TableSheet(BaseSheet):
|
|
699
717
|
try:
|
700
718
|
A = ''
|
701
719
|
for j, (sortcol, sortdir) in enumerate(self._ordering):
|
720
|
+
if isinstance(sortcol, str):
|
721
|
+
sortcol = self.column(sortcol)
|
702
722
|
if col is sortcol:
|
703
723
|
A = self.options.disp_sort_desc[j] if sortdir else self.options.disp_sort_asc[j]
|
704
724
|
scr.addstr(y+h-1, x, A, hdrcattr.attr)
|
@@ -713,10 +733,7 @@ class TableSheet(BaseSheet):
|
|
713
733
|
def draw(self, scr):
|
714
734
|
'Draw entire screen onto the `scr` curses object.'
|
715
735
|
if not self.columns:
|
716
|
-
|
717
|
-
self.addColumn(Column())
|
718
|
-
else:
|
719
|
-
return
|
736
|
+
return
|
720
737
|
|
721
738
|
drawparams = {
|
722
739
|
'isNull': self.isNullFunc(),
|
@@ -751,7 +768,7 @@ class TableSheet(BaseSheet):
|
|
751
768
|
y = headerRow + numHeaderRows
|
752
769
|
|
753
770
|
rows = self.rows[self.topRowIndex:min(self.topRowIndex+self.nScreenRows+1, self.nRows)]
|
754
|
-
self.
|
771
|
+
vd.callNoExceptions(self.checkCursor)
|
755
772
|
|
756
773
|
for rowidx, row in enumerate(rows):
|
757
774
|
if y >= self.windowHeight-1:
|
@@ -762,9 +779,10 @@ class TableSheet(BaseSheet):
|
|
762
779
|
y += self.drawRow(scr, row, self.topRowIndex+rowidx, y, rowcattr, maxheight=self.windowHeight-y-1, **drawparams)
|
763
780
|
|
764
781
|
if vcolidx+1 < self.nVisibleCols:
|
765
|
-
scr.addstr(headerRow, self.windowWidth-2, self.options.disp_more_right, colors.color_column_sep)
|
782
|
+
scr.addstr(headerRow, self.windowWidth-2, self.options.disp_more_right, colors.color_column_sep.attr)
|
766
783
|
|
767
|
-
def calc_height(self, row, displines=None, isNull=None):
|
784
|
+
def calc_height(self, row, displines=None, isNull=None, maxheight=1):
|
785
|
+
'render cell contents ifor row into displines'
|
768
786
|
if displines is None:
|
769
787
|
displines = {} # [vcolidx] -> list of lines in that cell
|
770
788
|
|
@@ -775,8 +793,8 @@ class TableSheet(BaseSheet):
|
|
775
793
|
continue
|
776
794
|
col = vcols[vcolidx]
|
777
795
|
cellval = col.getCell(row)
|
778
|
-
|
779
|
-
|
796
|
+
|
797
|
+
cellval.display = col.display(cellval, colwidth)
|
780
798
|
|
781
799
|
try:
|
782
800
|
if isNull and isNull(cellval.value):
|
@@ -785,13 +803,15 @@ class TableSheet(BaseSheet):
|
|
785
803
|
except (TypeError, ValueError):
|
786
804
|
pass
|
787
805
|
|
788
|
-
if
|
789
|
-
lines =
|
806
|
+
if maxheight > 1:
|
807
|
+
lines = _splitcell(self, cellval.display, width=colwidth-2, maxheight=maxheight)
|
790
808
|
else:
|
791
809
|
lines = [cellval.display]
|
792
810
|
displines[vcolidx] = (col, cellval, lines)
|
793
811
|
|
794
|
-
|
812
|
+
if len(displines) == 0:
|
813
|
+
return 0
|
814
|
+
return max(len(lines) for _, _, lines in displines.values())
|
795
815
|
|
796
816
|
def drawRow(self, scr, row, rowidx, ybase, rowcattr: ColorAttr, maxheight,
|
797
817
|
isNull='',
|
@@ -820,8 +840,11 @@ class TableSheet(BaseSheet):
|
|
820
840
|
else:
|
821
841
|
basecellcattr = rowcattr
|
822
842
|
|
843
|
+
# calc_height renders cell contents into displines
|
823
844
|
displines = {} # [vcolidx] -> list of lines in that cell
|
824
|
-
|
845
|
+
self.calc_height(row, displines, maxheight=self.rowHeight)
|
846
|
+
|
847
|
+
height = min(self.rowHeight, maxheight) or 1 # display even empty rows
|
825
848
|
self._rowLayout[rowidx] = (ybase, height)
|
826
849
|
|
827
850
|
for vcolidx, (col, cellval, lines) in displines.items():
|
@@ -838,26 +861,19 @@ class TableSheet(BaseSheet):
|
|
838
861
|
notewidth = 1 if note else 0
|
839
862
|
if note:
|
840
863
|
notecattr = update_attr(cattr, colors.get_color(cellval.notecolor), 10)
|
841
|
-
|
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]
|
864
|
+
scr.addstr(ybase, x+colwidth-notewidth, note, notecattr.attr)
|
849
865
|
|
850
866
|
lines = lines[voffset:]
|
851
867
|
|
852
868
|
if len(lines) > height:
|
853
869
|
lines = lines[:height]
|
854
870
|
elif len(lines) < height:
|
855
|
-
lines.extend(['']*(height-len(lines)))
|
871
|
+
lines.extend([[('', '')]]*(height-len(lines)))
|
856
872
|
|
857
|
-
for i,
|
873
|
+
for i, chunks in enumerate(lines):
|
858
874
|
y = ybase+i
|
859
875
|
|
860
|
-
if vcolidx == self.
|
876
|
+
if vcolidx == self.nVisibleCols-1: # right edge of sheet
|
861
877
|
if len(lines) == 1:
|
862
878
|
sepchars = endsep
|
863
879
|
else:
|
@@ -889,8 +905,15 @@ class TableSheet(BaseSheet):
|
|
889
905
|
sepchars = midsep
|
890
906
|
|
891
907
|
pre = disp_truncator if hoffset != 0 else disp_column_fill
|
892
|
-
|
893
|
-
|
908
|
+
prechunks = []
|
909
|
+
if colwidth > 2:
|
910
|
+
prechunks.append(('', pre))
|
911
|
+
|
912
|
+
for attr, text in chunks:
|
913
|
+
prechunks.append((attr, text[hoffset:]))
|
914
|
+
|
915
|
+
clipdraw_chunks(scr, y, x, prechunks, cattr, w=colwidth-notewidth)
|
916
|
+
vd.onMouse(scr, x, y, colwidth, 1, BUTTON3_RELEASED='edit-cell')
|
894
917
|
|
895
918
|
if x+colwidth+len(sepchars) <= self.windowWidth:
|
896
919
|
scr.addstr(y, x+colwidth, sepchars, sepcattr.attr)
|
@@ -904,7 +927,7 @@ class TableSheet(BaseSheet):
|
|
904
927
|
return height
|
905
928
|
|
906
929
|
vd.rowNoters = [
|
907
|
-
|
930
|
+
# f(sheet, row) -> character to be displayed on the left side of row
|
908
931
|
]
|
909
932
|
|
910
933
|
Sheet = TableSheet # deprecated in 2.0 but still widely used internally
|
@@ -914,6 +937,7 @@ class SequenceSheet(Sheet):
|
|
914
937
|
'Sheets with ``ColumnItem`` columns, and rows that are Python sequences (list, namedtuple, etc).'
|
915
938
|
def setCols(self, headerrows):
|
916
939
|
self.columns = []
|
940
|
+
vd.clearCaches() #1997
|
917
941
|
for i, colnamelines in enumerate(itertools.zip_longest(*headerrows, fillvalue='')):
|
918
942
|
colnamelines = ['' if c is None else c for c in colnamelines]
|
919
943
|
self.addColumn(ColumnItem(''.join(map(str, colnamelines)), i))
|
@@ -939,8 +963,7 @@ class SequenceSheet(Sheet):
|
|
939
963
|
except StopIteration:
|
940
964
|
break
|
941
965
|
|
942
|
-
|
943
|
-
def reload(self):
|
966
|
+
def loader(self):
|
944
967
|
'Skip first options.skip rows; set columns from next options.header rows.'
|
945
968
|
|
946
969
|
itsource = self.iterload()
|
@@ -956,72 +979,6 @@ class SequenceSheet(Sheet):
|
|
956
979
|
for r in vd.Progress(itsource, gerund='loading', total=0):
|
957
980
|
self.addRow(r)
|
958
981
|
|
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
982
|
|
1026
983
|
@VisiData.property
|
1027
984
|
@drawcache
|
@@ -1077,15 +1034,6 @@ def push(vd, vs, pane=0, load=True):
|
|
1077
1034
|
vs.ensureLoaded()
|
1078
1035
|
|
1079
1036
|
|
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
1037
|
@VisiData.api
|
1090
1038
|
def quit(vd, *sheets):
|
1091
1039
|
'Remove *sheets* from sheets stack, asking for confirmation if needed.'
|
@@ -1098,10 +1046,10 @@ def quit(vd, *sheets):
|
|
1098
1046
|
|
1099
1047
|
@BaseSheet.api
|
1100
1048
|
def confirmQuit(vs, verb='quit'):
|
1101
|
-
if vs.options.quitguard and vs.precious and vs.hasBeenModified:
|
1049
|
+
if vs.options.quitguard and vs.precious and vs.hasBeenModified and not vd._nextCommands:
|
1102
1050
|
vd.draw_all()
|
1103
1051
|
vd.confirm(f'{verb} modified sheet "{vs.name}"? ')
|
1104
|
-
elif vs.options.getonly('quitguard', vs, False): # if this sheet is specifically guarded
|
1052
|
+
elif vs.options.getonly('quitguard', vs, False) and not vd._nextCommands: # if this sheet is specifically guarded
|
1105
1053
|
vd.draw_all()
|
1106
1054
|
vd.confirm(f'{verb} guarded sheet "{vs.name}"? ')
|
1107
1055
|
|
@@ -1126,7 +1074,9 @@ def quitAndReleaseMemory(vs):
|
|
1126
1074
|
vs.source.lines.clear() # clear cache of read lines
|
1127
1075
|
|
1128
1076
|
if vs.precious: # only precious sheets have meaningful data
|
1077
|
+
vs.confirmQuit('quit')
|
1129
1078
|
vs.rows.clear()
|
1079
|
+
vs.rows = UNLOADED
|
1130
1080
|
vd.remove(vs)
|
1131
1081
|
vd.allSheets.remove(vs)
|
1132
1082
|
|
@@ -1161,18 +1111,12 @@ def async_deepcopy(sheet, rowlist):
|
|
1161
1111
|
return ret
|
1162
1112
|
|
1163
1113
|
|
1164
|
-
IndexSheet.options.header = 0
|
1165
|
-
IndexSheet.options.skip = 0
|
1166
1114
|
|
1167
1115
|
BaseSheet.init('pane', lambda: 1)
|
1168
1116
|
|
1169
|
-
Sheet.init('_ordering', list, copy=True) # (col:Column, reverse:bool)
|
1170
1117
|
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
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'),
|
1118
|
+
BaseSheet.addCommand('^R', 'reload-sheet', 'preloadHook(); reload()', 'Reload current sheet')
|
1119
|
+
Sheet.addCommand('^G', 'show-cursor', 'status(statusLine)', 'show cursor position and bounds of current sheet on status line')
|
1176
1120
|
|
1177
1121
|
Sheet.addCommand('!', 'key-col', 'toggleKeys([cursorCol])', 'toggle current column as a key column')
|
1178
1122
|
Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current column as a key column')
|
@@ -1180,10 +1124,10 @@ Sheet.addCommand('z!', 'key-col-off', 'unsetKeys([cursorCol])', 'unset current c
|
|
1180
1124
|
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
1125
|
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
1126
|
|
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')
|
1127
|
+
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')
|
1128
|
+
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')
|
1129
|
+
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')
|
1130
|
+
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
1131
|
|
1188
1132
|
Sheet.addCommand('z~', 'type-any', 'cursorCol.type = anytype', 'set type of current column to anytype')
|
1189
1133
|
Sheet.addCommand('~', 'type-string', 'cursorCol.type = str', 'set type of current column to str')
|
@@ -1192,14 +1136,6 @@ Sheet.addCommand('z#', 'type-len', 'cursorCol.type = vlen', 'set type of current
|
|
1192
1136
|
Sheet.addCommand('%', 'type-float', 'cursorCol.type = float', 'set type of current column to float')
|
1193
1137
|
Sheet.addCommand('', 'type-floatlocale', 'cursorCol.type = floatlocale', 'set type of current column to float using system locale set in LC_NUMERIC')
|
1194
1138
|
|
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
1139
|
BaseSheet.addCommand('q', 'quit-sheet', 'vd.quit(sheet)', 'quit current sheet')
|
1204
1140
|
BaseSheet.addCommand('Q', 'quit-sheet-free', 'quitAndReleaseMemory()', 'discard current sheet and free memory')
|
1205
1141
|
globalCommand('gq', 'quit-all', 'vd.quit(*vd.sheets)', 'quit all sheets (clean exit)')
|
@@ -1210,20 +1146,19 @@ BaseSheet.addCommand('^I', 'splitwin-swap', 'vd.activePane = 1 if sheet.pane ==
|
|
1210
1146
|
BaseSheet.addCommand('g^I', 'splitwin-swap-pane', 'vd.options.disp_splitwin_pct=-vd.options.disp_splitwin_pct', 'swap panes onscreen')
|
1211
1147
|
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
1148
|
|
1213
|
-
BaseSheet.addCommand('^L', 'redraw', '
|
1149
|
+
BaseSheet.addCommand('^L', 'redraw', 'sheet.refresh(); vd.redraw()', 'Refresh screen')
|
1214
1150
|
BaseSheet.addCommand(None, 'guard-sheet', 'options.set("quitguard", True, sheet); status("guarded")', 'Set quitguard on current sheet to confirm before quit')
|
1215
1151
|
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.
|
1152
|
+
BaseSheet.addCommand(None, 'open-source', 'vd.replace(source)', 'jump to the source of this sheet')
|
1217
1153
|
|
1218
1154
|
BaseSheet.bindkey('KEY_RESIZE', 'redraw')
|
1219
1155
|
|
1220
1156
|
BaseSheet.addCommand('A', 'open-new', 'vd.push(vd.newSheet("unnamed", 1))', 'Open new empty sheet')
|
1221
1157
|
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
Sheet.addCommand('
|
1226
|
-
BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=sheet.name)', 'Rename current sheet')
|
1158
|
+
BaseSheet.addCommand('`', 'open-source', 'vd.push(source)', 'open source sheet')
|
1159
|
+
BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=cleanName(sheet.name))', 'Rename current sheet')
|
1160
|
+
|
1161
|
+
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
1162
|
|
1228
1163
|
|
1229
1164
|
@Column.api
|
@@ -1232,3 +1167,47 @@ def formatter_enum(col, fmtdict):
|
|
1232
1167
|
|
1233
1168
|
Sheet.addCommand('', 'setcol-formatter', 'cursorCol.formatter=input("set formatter to: ", value=cursorCol.formatter or "generic")', 'set formatter for current column (generic, json, python)')
|
1234
1169
|
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)')
|
1170
|
+
|
1171
|
+
|
1172
|
+
vd.addGlobals(
|
1173
|
+
RowColorizer=RowColorizer,
|
1174
|
+
CellColorizer=CellColorizer,
|
1175
|
+
ColumnColorizer=ColumnColorizer,
|
1176
|
+
RecursiveExprException=RecursiveExprException,
|
1177
|
+
LazyComputeRow=LazyComputeRow,
|
1178
|
+
Sheet=Sheet,
|
1179
|
+
TableSheet=TableSheet,
|
1180
|
+
SequenceSheet=SequenceSheet)
|
1181
|
+
|
1182
|
+
vd.addMenuItems('''
|
1183
|
+
File > New > open-new
|
1184
|
+
File > Rename > rename-sheet
|
1185
|
+
File > Guard > on > guard-sheet
|
1186
|
+
File > Guard > off > guard-sheet-off
|
1187
|
+
File > Duplicate > selected rows by ref > dup-selected
|
1188
|
+
File > Duplicate > all rows by ref > dup-rows
|
1189
|
+
File > Duplicate > selected rows deep > dup-selected-deep
|
1190
|
+
File > Duplicate > all rows deep > dup-rows-deep
|
1191
|
+
File > Reload > rows and columns > reload-sheet
|
1192
|
+
File > Quit > top sheet > quit-sheet
|
1193
|
+
File > Quit > all sheets > quit-all
|
1194
|
+
Edit > Modify > current cell > input > edit-cell
|
1195
|
+
Edit > Modify > selected cells > from input > setcol-input
|
1196
|
+
View > Sheets > stack > sheets-stack
|
1197
|
+
View > Sheets > all > sheets-all
|
1198
|
+
View > Other sheet > source sheet > open-source
|
1199
|
+
View > Split pane > in half > splitwin-half
|
1200
|
+
View > Split pane > in percent > splitwin-input
|
1201
|
+
View > Split pane > unsplit > splitwin-close
|
1202
|
+
View > Split pane > swap panes > splitwin-swap-pane
|
1203
|
+
View > Split pane > goto other pane > splitwin-swap
|
1204
|
+
View > Refresh screen > redraw
|
1205
|
+
Column > Type as > anytype > type-any
|
1206
|
+
Column > Type as > string > type-string
|
1207
|
+
Column > Type as > integer > type-int
|
1208
|
+
Column > Type as > float > type-float
|
1209
|
+
Column > Type as > locale float > type-floatlocale
|
1210
|
+
Column > Type as > length > type-len
|
1211
|
+
Column > Key > toggle current column > key-col
|
1212
|
+
Column > Key > unkey current column > key-col-off
|
1213
|
+
''')
|