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.
Files changed (256) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +78 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +63 -51
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +6 -2
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +22 -4
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +197 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +77 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +200 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +20 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +54 -12
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +302 -149
  187. visidata/man/vd.txt +291 -154
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +55 -205
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +239 -260
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +114 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/benchmark.csv +52 -0
  213. visidata/tests/conftest.py +3 -3
  214. visidata/tests/test_cliptext.py +39 -0
  215. visidata/tests/test_commands.py +65 -7
  216. visidata/tests/test_edittext.py +2 -2
  217. visidata/tests/test_features.py +28 -0
  218. visidata/tests/test_menu.py +14 -0
  219. visidata/tests/test_path.py +13 -4
  220. visidata/text_source.py +53 -0
  221. visidata/textsheet.py +10 -3
  222. visidata/theme.py +44 -0
  223. visidata/themes/__init__.py +0 -0
  224. visidata/themes/ascii8.py +84 -0
  225. visidata/themes/asciimono.py +84 -0
  226. visidata/themes/light.py +17 -0
  227. visidata/threads.py +89 -40
  228. visidata/tuiwin.py +22 -0
  229. visidata/type_currency.py +22 -3
  230. visidata/type_date.py +31 -9
  231. visidata/type_floatsi.py +5 -1
  232. visidata/undo.py +17 -5
  233. visidata/utils.py +106 -23
  234. visidata/vdobj.py +28 -17
  235. visidata/windows.py +10 -0
  236. visidata/wrappers.py +9 -3
  237. visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
  238. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
  239. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
  240. visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
  241. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
  242. visidata-3.0.1.dist-info/RECORD +258 -0
  243. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
  244. vgit/__init__.py +0 -1
  245. vgit/gitsheet.py +0 -164
  246. visidata/layout.py +0 -44
  247. visidata/misc.py +0 -5
  248. visidata-2.11.1.data/scripts/vgit +0 -9
  249. visidata-2.11.1.dist-info/RECORD +0 -155
  250. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  251. {vgit → visidata/apps/vgit}/abort.py +0 -0
  252. /visidata/{repeat.py → features/repeat.py} +0 -0
  253. {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
  256. {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
+ )
@@ -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.option('color_working', 'green', 'color of system running smoothly')
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
- ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(t.ident), ctypes.py_object(exception))
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 None
122
- ret = ''
123
- attr = 'color_working'
124
- if min_mem:
125
- try:
126
- freestats = subprocess.run('free --total --mega'.split(), check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip().splitlines()
127
- except FileNotFoundError as e:
128
- if options.debug:
129
- vd.exceptionCaught(e)
130
- options.min_memory_mb = 0
131
- vd.warning('disabling min_memory_mb: "free" not installed')
132
- return '', attr
133
- tot_m, used_m, free_m = map(int, freestats[-1].split()[1:])
134
- ret = '[%dMB] ' % free_m + ret
135
- if free_m < min_mem:
136
- attr = 'color_warning'
137
- vd.warning('%dMB free < %dMB minimum, stopping threads' % (free_m, min_mem))
138
- vd.cancelThread(*vd.unfinishedThreads)
139
- curses.flash()
140
- return ret, attr
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
- VisiData.init('threads', lambda: [_annotate_thread(threading.current_thread(), 0)])
178
+ vd.threads = [_annotate_thread(threading.current_thread(), 0)]
174
179
 
175
180
  @VisiData.api
176
- def execAsync(self, func, *args, sheet=None, **kwargs):
177
- 'Execute ``func(*args, **kwargs)`` in a separate thread.'
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
- thread = threading.Thread(target=_toplevelTryFunc, daemon=True, args=(func,)+args, kwargs=kwargs)
180
- self.threads.append(_annotate_thread(thread))
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
- if sheet is None:
183
- sheet = self.activeSheet
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, status=vd.status, **kwargs):
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
- if status:
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.name, source=pstats.Stats(p.given).stats)
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
- Sheet.addCommand('$', 'type-currency', 'cursorCol.type = currency', 'set type of current column to currency')
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
- try:
6
- from dateutil.parser import parse as date_parse
7
- except ImportError:
8
- def date_parse(r=''):
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 r
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
- if not r or not isUndoableCommand(r.longname):
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.remove(cmdlogrow)
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
+ ''')