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
@@ -0,0 +1,84 @@
|
|
1
|
+
'ASCII monochrome theme; default colors only'
|
2
|
+
|
3
|
+
from visidata import vd
|
4
|
+
|
5
|
+
|
6
|
+
vd.themes['asciimono'] = dict(
|
7
|
+
disp_note_none='',
|
8
|
+
disp_truncator='>',
|
9
|
+
disp_oddspace='.',
|
10
|
+
disp_more_left='<',
|
11
|
+
disp_more_right='>',
|
12
|
+
disp_error_val='',
|
13
|
+
disp_ambig_width=1,
|
14
|
+
|
15
|
+
disp_pending='',
|
16
|
+
note_pending=':',
|
17
|
+
note_format_exc='?',
|
18
|
+
note_getter_exc='!',
|
19
|
+
note_type_exc='!',
|
20
|
+
|
21
|
+
color_note_pending='bold',
|
22
|
+
color_note_type='',
|
23
|
+
color_note_row='',
|
24
|
+
|
25
|
+
disp_column_sep='|',
|
26
|
+
disp_keycol_sep='|',
|
27
|
+
disp_rowtop_sep='|',
|
28
|
+
disp_rowmid_sep='|',
|
29
|
+
disp_rowbot_sep='|',
|
30
|
+
disp_rowend_sep='|',
|
31
|
+
disp_keytop_sep='|',
|
32
|
+
disp_keymid_sep='|',
|
33
|
+
disp_keybot_sep='|',
|
34
|
+
disp_endtop_sep='|',
|
35
|
+
disp_endmid_sep='|',
|
36
|
+
disp_endbot_sep='|',
|
37
|
+
disp_selected_note='+',
|
38
|
+
disp_sort_asc='^^^^^^',
|
39
|
+
disp_sort_desc='vvvvvv',
|
40
|
+
color_default='',
|
41
|
+
color_default_hdr='bold',
|
42
|
+
color_bottom_hdr='underline',
|
43
|
+
color_current_row='reverse',
|
44
|
+
color_current_col='bold',
|
45
|
+
color_current_hdr='bold reverse',
|
46
|
+
color_column_sep='',
|
47
|
+
color_key_col='bold',
|
48
|
+
color_hidden_col='8',
|
49
|
+
color_selected_row='',
|
50
|
+
color_edit_cell='',
|
51
|
+
color_graph_hidden='',
|
52
|
+
color_graph_selected='bold',
|
53
|
+
color_status_replay='',
|
54
|
+
|
55
|
+
color_graph_axis='bold',
|
56
|
+
color_sidebar='reverse',
|
57
|
+
color_change_pending='reverse',
|
58
|
+
color_delete_pending='underline',
|
59
|
+
|
60
|
+
disp_rstatus_fmt=' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows}',
|
61
|
+
disp_status_fmt='{sheet.shortcut}> {sheet.name}| ',
|
62
|
+
disp_lstatus_max=0,
|
63
|
+
disp_status_sep=' | ',
|
64
|
+
color_keystrokes='bold reverse',
|
65
|
+
color_status='bold reverse',
|
66
|
+
color_error='bold',
|
67
|
+
color_warning='bold',
|
68
|
+
color_top_status='underline',
|
69
|
+
color_active_status='reverse',
|
70
|
+
color_inactive_status='8',
|
71
|
+
color_working='',
|
72
|
+
|
73
|
+
color_menu='reverse',
|
74
|
+
color_menu_active='',
|
75
|
+
color_menu_spec='reverse',
|
76
|
+
color_menu_help='reverse',
|
77
|
+
color_add_pending='',
|
78
|
+
disp_menu_boxchars='||-- ||',
|
79
|
+
disp_menu_more='>',
|
80
|
+
disp_menu_push='+',
|
81
|
+
disp_menu_input='_',
|
82
|
+
disp_menu_fmt='_.;"`\\ ASCII mono /\':._',
|
83
|
+
plot_colors = 'white',
|
84
|
+
)
|
visidata/themes/light.py
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
'Light-mode theme using 256-colors.'
|
2
|
+
|
3
|
+
from visidata import vd
|
4
|
+
|
5
|
+
vd.themes['light'] = dict(
|
6
|
+
color_default = 'black on white', # the default fg and bg colors
|
7
|
+
color_key_col = '20 blue', # color of key columns
|
8
|
+
color_edit_cell = '234 black', # cell color to use when editing cell
|
9
|
+
color_selected_row = '164 magenta', # color of selected rows
|
10
|
+
color_note_row = '164 magenta', # color of row note on left edge
|
11
|
+
color_note_type = '88 red', # color of cell note for non-str types in anytype columns
|
12
|
+
color_warning = '202 11 yellow',
|
13
|
+
color_add_pending = '34 green',
|
14
|
+
color_change_pending = '166 yellow',
|
15
|
+
plot_colors = '20 red magenta black 28 88 94 99 106'
|
16
|
+
)
|
17
|
+
|
visidata/threads.py
CHANGED
@@ -5,16 +5,17 @@ import functools
|
|
5
5
|
import cProfile
|
6
6
|
import threading
|
7
7
|
import collections
|
8
|
+
import subprocess
|
9
|
+
import curses
|
8
10
|
|
9
11
|
from visidata import VisiData, vd, options, globalCommand, Sheet, EscapeException
|
10
|
-
from visidata import ColumnAttr, Column
|
11
|
-
from visidata import *
|
12
|
+
from visidata import ColumnAttr, Column, BaseSheet, ItemColumn
|
12
13
|
|
13
14
|
|
14
|
-
vd.option('profile', False, 'enable profiling on threads')
|
15
|
-
vd.option('min_memory_mb', 0, 'minimum memory to continue loading and async processing')
|
15
|
+
vd.option('profile', False, 'enable profiling on threads', max_help=-1)
|
16
|
+
vd.option('min_memory_mb', 0, 'minimum memory to continue loading and async processing', max_help=-1)
|
16
17
|
|
17
|
-
vd.
|
18
|
+
vd.theme_option('color_working', '118 5', 'color of system running smoothly')
|
18
19
|
|
19
20
|
BaseSheet.init('currentThreads', list)
|
20
21
|
|
@@ -85,7 +86,8 @@ def Progress(vd, iterable=None, gerund="", total=None, sheet=None):
|
|
85
86
|
def cancelThread(vd, *threads, exception=EscapeException):
|
86
87
|
'Raise *exception* in one or more *threads*.'
|
87
88
|
for t in threads:
|
88
|
-
|
89
|
+
if t.ident is not None:
|
90
|
+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(t.ident), ctypes.py_object(exception))
|
89
91
|
|
90
92
|
|
91
93
|
# each row is an augmented threading.Thread object
|
@@ -115,29 +117,32 @@ def elapsed_s(t):
|
|
115
117
|
|
116
118
|
@VisiData.api
|
117
119
|
def checkMemoryUsage(vd):
|
118
|
-
min_mem = options.min_memory_mb
|
119
120
|
threads = vd.unfinishedThreads
|
120
121
|
if not threads:
|
121
|
-
return
|
122
|
-
|
123
|
-
|
124
|
-
if min_mem:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
vd.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
122
|
+
return ''
|
123
|
+
|
124
|
+
min_mem = vd.options.min_memory_mb
|
125
|
+
if not min_mem:
|
126
|
+
return ''
|
127
|
+
|
128
|
+
try:
|
129
|
+
freestats = subprocess.run('free --total --mega'.split(), check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip().splitlines()
|
130
|
+
except FileNotFoundError as e:
|
131
|
+
if vd.options.debug:
|
132
|
+
vd.exceptionCaught(e)
|
133
|
+
vd.options.min_memory_mb = 0
|
134
|
+
vd.warning('disabling min_memory_mb: "free" not installed')
|
135
|
+
return ''
|
136
|
+
tot_m, used_m, free_m = map(int, freestats[-1].split()[1:])
|
137
|
+
ret = f' [{free_m}MB] '
|
138
|
+
if free_m < min_mem:
|
139
|
+
attr = '[:warning]'
|
140
|
+
vd.warning(f'{free_m}MB free < {min_mem}MB minimum, stopping threads')
|
141
|
+
vd.cancelThread(*vd.unfinishedThreads)
|
142
|
+
curses.flash()
|
143
|
+
else:
|
144
|
+
attr = '[:working]'
|
145
|
+
return attr + ret + '[/]'
|
141
146
|
|
142
147
|
|
143
148
|
# for progress bar
|
@@ -170,17 +175,34 @@ def _annotate_thread(t, endTime=None):
|
|
170
175
|
return t
|
171
176
|
|
172
177
|
# all long-running threads, including main and finished
|
173
|
-
|
178
|
+
vd.threads = [_annotate_thread(threading.current_thread(), 0)]
|
174
179
|
|
175
180
|
@VisiData.api
|
176
|
-
def
|
177
|
-
'Execute ``func(*args, **kwargs)`` in
|
181
|
+
def execSync(vd, func, *args, sheet=None, **kwargs):
|
182
|
+
'Execute ``func(*args, **kwargs)`` in this thread (synchronously). A drop-in substitute for vd.execAsync.'
|
183
|
+
vd.callNoExceptions(func, *args, **kwargs)
|
184
|
+
t = threading.current_thread()
|
185
|
+
t.sheet = sheet or vd.activeSheet
|
186
|
+
return t
|
178
187
|
|
179
|
-
|
180
|
-
|
188
|
+
@VisiData.api
|
189
|
+
def execAsync(vd, func, *args, **kwargs):
|
190
|
+
'''Execute ``func(*args, **kwargs)`` in a separate thread. `sheet` is a
|
191
|
+
special kwarg to indicate which sheet the thread should be associated with;
|
192
|
+
by default, uses vd.activeSheet. If `sheet` explicitly given as None, the thread
|
193
|
+
will be ignored by vd.sync and thread status indicators.
|
194
|
+
'''
|
195
|
+
|
196
|
+
if 'sheet' not in kwargs:
|
197
|
+
sheet = vd.activeSheet
|
198
|
+
else:
|
199
|
+
sheet = kwargs.pop('sheet')
|
200
|
+
|
201
|
+
if sheet is not None and (sheet.lastCommandThreads and threading.current_thread() not in sheet.lastCommandThreads):
|
202
|
+
vd.fail(f'still running **{sheet.lastCommandThreads[-1].name}** from previous command')
|
181
203
|
|
182
|
-
|
183
|
-
|
204
|
+
thread = threading.Thread(target=_toplevelTryFunc, daemon=True, args=(func,)+args, kwargs=kwargs)
|
205
|
+
vd.threads.append(_annotate_thread(thread))
|
184
206
|
|
185
207
|
if sheet is not None:
|
186
208
|
sheet.currentThreads.append(thread)
|
@@ -190,7 +212,7 @@ def execAsync(self, func, *args, sheet=None, **kwargs):
|
|
190
212
|
|
191
213
|
return thread
|
192
214
|
|
193
|
-
def _toplevelTryFunc(func, *args,
|
215
|
+
def _toplevelTryFunc(func, *args, **kwargs):
|
194
216
|
with ThreadProfiler(threading.current_thread()) as prof:
|
195
217
|
t = threading.current_thread()
|
196
218
|
t.name = func.__name__
|
@@ -198,8 +220,7 @@ def _toplevelTryFunc(func, *args, status=vd.status, **kwargs):
|
|
198
220
|
t.status = func(*args, **kwargs)
|
199
221
|
except EscapeException as e: # user aborted
|
200
222
|
t.status = 'aborted by user'
|
201
|
-
|
202
|
-
status('%s aborted' % t.name, priority=2)
|
223
|
+
vd.warning(f'{t.name} aborted')
|
203
224
|
except Exception as e:
|
204
225
|
t.exception = e
|
205
226
|
t.status = 'exception'
|
@@ -208,6 +229,18 @@ def _toplevelTryFunc(func, *args, status=vd.status, **kwargs):
|
|
208
229
|
if t.sheet:
|
209
230
|
t.sheet.currentThreads.remove(t)
|
210
231
|
|
232
|
+
def asyncignore(func):
|
233
|
+
'Decorator like `@asyncthread` but without attaching to a sheet, so no sheet.threadStatus will show it.'
|
234
|
+
@functools.wraps(func)
|
235
|
+
def _execAsync(*args, **kwargs):
|
236
|
+
@functools.wraps(func)
|
237
|
+
def _func(*args, **kwargs):
|
238
|
+
func(*args, **kwargs)
|
239
|
+
|
240
|
+
return vd.execAsync(_func, *args, **kwargs, sheet=None)
|
241
|
+
|
242
|
+
return _execAsync
|
243
|
+
|
211
244
|
def asyncsingle(func):
|
212
245
|
'''Function decorator like `@asyncthread` but as a singleton. When called, `func(...)` spawns a new thread, and cancels any previous thread still running *func*.
|
213
246
|
``vd.sync()`` does not wait for unfinished asyncsingle threads.
|
@@ -233,7 +266,7 @@ def asyncsingle(func):
|
|
233
266
|
@VisiData.property
|
234
267
|
def unfinishedThreads(self):
|
235
268
|
'A list of unfinished threads (those without a recorded `endTime`).'
|
236
|
-
return [t for t in self.threads if getattr(t, 'endTime', None) is None]
|
269
|
+
return [t for t in self.threads if getattr(t, 'endTime', None) is None and getattr(t, 'sheet', None) is not None]
|
237
270
|
|
238
271
|
@VisiData.api
|
239
272
|
def checkForFinishedThreads(self):
|
@@ -251,8 +284,9 @@ def sync(self, *joiningThreads):
|
|
251
284
|
while True:
|
252
285
|
deads = set() # dead threads
|
253
286
|
threads = joiningThreads or set(self.unfinishedThreads)
|
254
|
-
threads -= set([threading.current_thread(), getattr(vd, 'drawThread', None)])
|
287
|
+
threads -= set([threading.current_thread(), getattr(vd, 'drawThread', None), getattr(vd, 'outputProgressThread', None)])
|
255
288
|
threads -= deads
|
289
|
+
threads -= set([None])
|
256
290
|
for t in threads:
|
257
291
|
try:
|
258
292
|
if not t.is_alive() or t not in threading.enumerate() or getattr(t, 'noblock', False) is True:
|
@@ -272,7 +306,7 @@ min_thread_time_s = 0.10 # only keep threads that take longer than this number o
|
|
272
306
|
@VisiData.api
|
273
307
|
def open_pyprof(vd, p):
|
274
308
|
import pstats
|
275
|
-
return ProfileStatsSheet(p.
|
309
|
+
return ProfileStatsSheet(p.base_stem, source=pstats.Stats(p.given).stats)
|
276
310
|
|
277
311
|
|
278
312
|
@VisiData.api
|
@@ -308,10 +342,19 @@ class ThreadProfiler:
|
|
308
342
|
# remove very-short-lived async actions
|
309
343
|
if elapsed_s(self.thread) < min_thread_time_s:
|
310
344
|
vd.threads.remove(self.thread)
|
345
|
+
else:
|
346
|
+
if vd.options.profile:
|
347
|
+
self.thread.profile.dump_stats(f'{self.thread.name}.pyprof')
|
311
348
|
|
312
349
|
|
313
350
|
class ProfileSheet(Sheet):
|
314
351
|
rowtype = 'callsites' # rowdef: profiler_entry
|
352
|
+
guide = '''
|
353
|
+
# Profile Sheet
|
354
|
+
- `z Ctrl+S` to save as pyprof file
|
355
|
+
- `Ctrl+O` to open current function in $EDITOR
|
356
|
+
- `Enter` to open list of calls from current function
|
357
|
+
'''
|
315
358
|
columns = [
|
316
359
|
Column('funcname', getter=lambda col,row: codestr(row.code)),
|
317
360
|
Column('filename', getter=lambda col,row: os.path.split(row.code.co_filename)[-1] if not isinstance(row.code, str) else ''),
|
@@ -404,4 +447,10 @@ vd.addGlobals({
|
|
404
447
|
'Progress': Progress,
|
405
448
|
'asynccache': asynccache,
|
406
449
|
'asyncsingle': asyncsingle,
|
450
|
+
'asyncignore': asyncignore,
|
407
451
|
})
|
452
|
+
|
453
|
+
vd.addMenuItems('''
|
454
|
+
System > Threads sheet > threads-all
|
455
|
+
System > Toggle profiling > toggle-profile
|
456
|
+
''')
|
visidata/tuiwin.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
from visidata import VisiData, vd
|
2
|
+
|
3
|
+
vd._parentscrs = {} # scr -> parentscr
|
4
|
+
|
5
|
+
|
6
|
+
@VisiData.api
|
7
|
+
def subwindow(vd, scr, x, y, w, h):
|
8
|
+
'Return subwindow with its (0,0) at (x,y) relative to parent scr. Replacement for scr.derwin() to track parent scr.'
|
9
|
+
newscr = scr.derwin(h, w, y, x)
|
10
|
+
vd._parentscrs[newscr] = scr
|
11
|
+
return newscr
|
12
|
+
|
13
|
+
|
14
|
+
@VisiData.api
|
15
|
+
def getrootxy(vd, scr): # like scr.getparyx() but for all ancestor scrs
|
16
|
+
px, py = 0, 0
|
17
|
+
while scr in vd._parentscrs:
|
18
|
+
dy, dx = scr.getparyx()
|
19
|
+
if dy > 0: py += dy
|
20
|
+
if dx > 0: px += dx
|
21
|
+
scr = vd._parentscrs[scr]
|
22
|
+
return px, py
|
visidata/type_currency.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
from visidata import vd, Sheet
|
1
|
+
from visidata import vd, Sheet, Column
|
2
2
|
|
3
|
-
vd.option('disp_currency_fmt', '%.02f', 'default fmtstr to format for currency values', replay=True)
|
3
|
+
vd.option('disp_currency_fmt', '%.02f', 'default fmtstr to format for currency values', replay=True, help=vd.help_float_fmt)
|
4
|
+
vd.theme_option('color_currency_neg', 'red', 'color for negative values in currency displayer', replay=True)
|
4
5
|
|
5
6
|
|
6
7
|
floatchars='+-0123456789.'
|
@@ -13,4 +14,22 @@ def currency(*args):
|
|
13
14
|
return float(*args)
|
14
15
|
|
15
16
|
|
16
|
-
|
17
|
+
@Column.api
|
18
|
+
def displayer_currency(col, dw, width=None):
|
19
|
+
text = dw.text
|
20
|
+
|
21
|
+
if isinstance(dw.typedval, (int, float)):
|
22
|
+
if dw.typedval < 0:
|
23
|
+
text = f'({dw.text[1:]})'.rjust(width-1)
|
24
|
+
yield ('currency_neg', '')
|
25
|
+
else:
|
26
|
+
text = text.rjust(width-2)
|
27
|
+
|
28
|
+
yield ('', text)
|
29
|
+
|
30
|
+
|
31
|
+
Sheet.addCommand('$', 'type-currency', 'cursorCol.type=currency', 'set type of current column to currency')
|
32
|
+
|
33
|
+
vd.addMenuItems('''
|
34
|
+
Column > Type as > dirty float > type-currency
|
35
|
+
''')
|
visidata/type_date.py
CHANGED
@@ -1,16 +1,33 @@
|
|
1
1
|
import datetime
|
2
2
|
|
3
|
-
from visidata import vd, Sheet
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
from visidata import VisiData, vd, Sheet
|
4
|
+
|
5
|
+
@VisiData.lazy_property
|
6
|
+
def date_parse(vd):
|
7
|
+
try:
|
8
|
+
from dateutil.parser import parse
|
9
|
+
return parse
|
10
|
+
except ImportError:
|
9
11
|
vd.warning('install python-dateutil for date type')
|
10
|
-
return
|
12
|
+
return str
|
13
|
+
|
14
|
+
vd.help_date = '''
|
15
|
+
- RFC3339: `%Y-%m-%d %H:%M:%S.%f %z`
|
16
|
+
- `%A` Weekday as locale’s full name.
|
17
|
+
- `%w` Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
|
18
|
+
- `%d` Day of the month as a zero-padded decimal number.
|
19
|
+
- `%b` Month as locale’s abbreviated name.
|
20
|
+
- `%B` Month as locale’s full name.
|
21
|
+
- `%p` Locale’s equivalent of either AM or PM.
|
22
|
+
- `%c` Locale’s appropriate date and time representation.
|
23
|
+
- `%x` Locale’s appropriate date representation.
|
24
|
+
- `%X` Locale’s appropriate time representation.
|
25
|
+
- `%Z` Time zone name (empty string if the object is naive).
|
11
26
|
|
27
|
+
See [:onclick https://strftime.org]Python strftime()[/] for a full list of format codes.
|
28
|
+
'''
|
12
29
|
|
13
|
-
vd.option('disp_date_fmt','%Y-%m-%d', 'default fmtstr to strftime for date values', replay=True)
|
30
|
+
vd.option('disp_date_fmt','%Y-%m-%d', 'default fmtstr passed to strftime for date values', replay=True, help=vd.help_date)
|
14
31
|
|
15
32
|
|
16
33
|
@vd.numericType('@', '', formatter=lambda fmtstr,val: val.strftime(fmtstr or vd.options.disp_date_fmt))
|
@@ -28,7 +45,7 @@ class date(datetime.datetime):
|
|
28
45
|
if isinstance(s, int) or isinstance(s, float):
|
29
46
|
r = datetime.datetime.fromtimestamp(s)
|
30
47
|
elif isinstance(s, str):
|
31
|
-
r = date_parse(s)
|
48
|
+
r = vd.date_parse(s)
|
32
49
|
elif isinstance(s, (datetime.datetime, datetime.date)):
|
33
50
|
r = s
|
34
51
|
else:
|
@@ -109,3 +126,8 @@ vd.addGlobals(
|
|
109
126
|
|
110
127
|
|
111
128
|
Sheet.addCommand('@', 'type-date', 'cursorCol.type = date', 'set type of current column to date')
|
129
|
+
Sheet.addCommand('', 'type-datetime', 'cursorCol.type=date; cursorCol.fmtstr="%Y-%m-%d %H:%M:%S"', 'set type of current column to datetime')
|
130
|
+
|
131
|
+
vd.addMenuItems('''
|
132
|
+
Column > Type as > date > type-date
|
133
|
+
''')
|
visidata/type_floatsi.py
CHANGED
@@ -22,7 +22,7 @@ def floatsi(*args):
|
|
22
22
|
if not args:
|
23
23
|
return 0.0
|
24
24
|
if not isinstance(args[0], str):
|
25
|
-
return args[0]
|
25
|
+
return float(args[0])
|
26
26
|
|
27
27
|
s=args[0].strip()
|
28
28
|
for i, p in enumerate(vd.si_prefixes):
|
@@ -33,3 +33,7 @@ def floatsi(*args):
|
|
33
33
|
|
34
34
|
|
35
35
|
Sheet.addCommand('z%', 'type-floatsi', 'cursorCol.type = floatsi', 'set type of current column to SI float')
|
36
|
+
|
37
|
+
vd.addMenuItems('''
|
38
|
+
Column > Type as > SI float > type-floatsi
|
39
|
+
''')
|
visidata/undo.py
CHANGED
@@ -18,13 +18,14 @@ def isUndoableCommand(longname):
|
|
18
18
|
@VisiData.api
|
19
19
|
def addUndo(vd, undofunc, *args, **kwargs):
|
20
20
|
'On undo of latest command, call ``undofunc(*args, **kwargs)``.'
|
21
|
-
if options.undo:
|
21
|
+
if vd.options.undo:
|
22
22
|
# occurs when VisiData is just starting up
|
23
23
|
if getattr(vd, 'activeCommand', UNLOADED) is UNLOADED:
|
24
24
|
return
|
25
25
|
r = vd.modifyCommand
|
26
26
|
# some special commands, like open-file, do not have an undofuncs set
|
27
|
-
|
27
|
+
# do not set undofuncs for non-logged commands
|
28
|
+
if not r or not isUndoableCommand(r.longname) or not vd.activeCommand or not vd.isLoggableCommand(vd.activeCommand.longname):
|
28
29
|
return
|
29
30
|
if not r.undofuncs:
|
30
31
|
r.undofuncs = []
|
@@ -33,16 +34,17 @@ def addUndo(vd, undofunc, *args, **kwargs):
|
|
33
34
|
|
34
35
|
@VisiData.api
|
35
36
|
def undo(vd, sheet):
|
36
|
-
if not options.undo:
|
37
|
+
if not vd.options.undo:
|
37
38
|
vd.fail("options.undo not enabled")
|
38
39
|
|
39
40
|
# don't allow undo of first command on a sheet, which is always the command that created the sheet.
|
40
|
-
for cmdlogrow in sheet.cmdlog_sheet.rows[:0:-1]:
|
41
|
+
for i, cmdlogrow in enumerate(sheet.cmdlog_sheet.rows[:0:-1]):
|
41
42
|
if cmdlogrow.undofuncs:
|
42
43
|
for undofunc, args, kwargs, in cmdlogrow.undofuncs[::-1]:
|
43
44
|
undofunc(*args, **kwargs)
|
44
45
|
sheet.undone.append(cmdlogrow)
|
45
|
-
sheet.cmdlog_sheet.rows
|
46
|
+
row_idx = len(sheet.cmdlog_sheet.rows)-1 - i
|
47
|
+
del sheet.cmdlog_sheet.rows[row_idx]
|
46
48
|
|
47
49
|
vd.clearCaches() # undofunc can invalidate the drawcache
|
48
50
|
|
@@ -113,3 +115,13 @@ def addUndoColNames(vd, cols):
|
|
113
115
|
|
114
116
|
BaseSheet.addCommand('U', 'undo-last', 'vd.undo(sheet)', 'Undo the most recent change (options.undo must be enabled)')
|
115
117
|
BaseSheet.addCommand('R', 'redo-last', 'vd.redo(sheet)', 'Redo the most recent undo (options.undo must be enabled)')
|
118
|
+
|
119
|
+
vd.addGlobals(
|
120
|
+
undoAttrFunc=undoAttrFunc,
|
121
|
+
Fanout=Fanout,
|
122
|
+
undoAttrCopyFunc=undoAttrCopyFunc)
|
123
|
+
|
124
|
+
vd.addMenuItems('''
|
125
|
+
Edit > Undo > undo-last
|
126
|
+
Edit > Redo > redo-last
|
127
|
+
''')
|