visidata 2.11.dev0__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.
- visidata/__init__.py +72 -91
- visidata/_input.py +263 -44
- visidata/_open.py +84 -29
- visidata/_types.py +22 -4
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +65 -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
- visidata/apps/vgit/__main__.py +3 -0
- visidata/apps/vgit/abort.py +23 -0
- visidata/apps/vgit/blame.py +76 -0
- visidata/apps/vgit/branch.py +153 -0
- visidata/apps/vgit/config.py +95 -0
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- visidata/apps/vgit/grep.py +37 -0
- visidata/apps/vgit/log.py +81 -0
- visidata/apps/vgit/main.py +55 -0
- visidata/apps/vgit/remote.py +57 -0
- visidata/apps/vgit/repos.py +71 -0
- visidata/apps/vgit/setup.py +37 -0
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- visidata/apps/vgit/statusbar.py +34 -0
- visidata/basesheet.py +59 -50
- visidata/canvas.py +251 -99
- visidata/choose.py +15 -11
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +84 -18
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +89 -114
- visidata/color.py +142 -56
- visidata/column.py +134 -131
- 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 +5 -1
- 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 +32 -6
- 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 +163 -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} +4 -2
- 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} +33 -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} +75 -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 +180 -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 +17 -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 +3 -5
- 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 +48 -10
- 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/macos.py +1 -1
- visidata/macros.py +130 -41
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -154
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -147
- visidata/man/vd.txt +291 -151
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +79 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +131 -43
- visidata/pivot.py +74 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +50 -201
- visidata/rename_col.py +20 -0
- visidata/save.py +42 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -24
- visidata/sheets.py +229 -257
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +113 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +62 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +17 -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 +87 -39
- 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 +18 -6
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.dev0.dist-info/RECORD +0 -142
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.dev0.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
|
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')
|
@@ -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.
|
200
|
-
self.
|
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.
|
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
|
-
@
|
225
|
-
def
|
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(
|
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
|
-
'
|
202
|
+
'Return ColorAttr for the given colorizers/col/row/value'
|
243
203
|
|
244
204
|
colorstack = []
|
245
|
-
for colorizer in self.
|
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
|
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
|
-
|
328
|
-
ret.
|
329
|
-
|
330
|
-
|
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
|
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
|
-
|
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
|
-
|
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.
|
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 -
|
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 =
|
624
|
-
sepColWidth =
|
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,
|
642
|
-
|
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 = '
|
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
|
685
|
-
vd.onMouse(scr, y+i,
|
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
|
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
|
-
|
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.
|
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
|
-
|
779
|
-
|
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
|
789
|
-
lines =
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
893
|
-
|
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
|
-
|
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
|
-
|
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=
|
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()
|
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', '
|
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.
|
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
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
Sheet.addCommand('
|
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
|
+
''')
|