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.
Files changed (253) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +263 -44
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +22 -4
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +65 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. visidata/apps/vgit/__main__.py +3 -0
  18. visidata/apps/vgit/abort.py +23 -0
  19. visidata/apps/vgit/blame.py +76 -0
  20. visidata/apps/vgit/branch.py +153 -0
  21. visidata/apps/vgit/config.py +95 -0
  22. visidata/apps/vgit/diff.py +169 -0
  23. visidata/apps/vgit/gitsheet.py +161 -0
  24. visidata/apps/vgit/grep.py +37 -0
  25. visidata/apps/vgit/log.py +81 -0
  26. visidata/apps/vgit/main.py +55 -0
  27. visidata/apps/vgit/remote.py +57 -0
  28. visidata/apps/vgit/repos.py +71 -0
  29. visidata/apps/vgit/setup.py +37 -0
  30. visidata/apps/vgit/stash.py +69 -0
  31. visidata/apps/vgit/status.py +204 -0
  32. visidata/apps/vgit/statusbar.py +34 -0
  33. visidata/basesheet.py +59 -50
  34. visidata/canvas.py +251 -99
  35. visidata/choose.py +15 -11
  36. visidata/clean_names.py +29 -0
  37. visidata/clipboard.py +84 -18
  38. visidata/cliptext.py +220 -46
  39. visidata/cmdlog.py +89 -114
  40. visidata/color.py +142 -56
  41. visidata/column.py +134 -131
  42. visidata/ddw/input.ddw +74 -79
  43. visidata/ddw/regex.ddw +57 -0
  44. visidata/ddwplay.py +33 -14
  45. visidata/deprecated.py +77 -3
  46. visidata/desktop/visidata.desktop +7 -0
  47. visidata/editor.py +12 -6
  48. visidata/errors.py +5 -1
  49. visidata/experimental/__init__.py +0 -0
  50. visidata/experimental/diff_sheet.py +29 -0
  51. visidata/experimental/digit_autoedit.py +6 -0
  52. visidata/experimental/gdrive.py +89 -0
  53. visidata/experimental/google.py +37 -0
  54. visidata/experimental/gsheets.py +79 -0
  55. visidata/experimental/live_search.py +37 -0
  56. visidata/experimental/liveupdate.py +45 -0
  57. visidata/experimental/mark.py +133 -0
  58. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  59. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  60. visidata/experimental/rownum.py +73 -0
  61. visidata/experimental/slide_cells.py +26 -0
  62. visidata/expr.py +8 -4
  63. visidata/extensible.py +32 -6
  64. visidata/features/__init__.py +0 -0
  65. visidata/features/addcol_audiometadata.py +42 -0
  66. visidata/features/addcol_histogram.py +34 -0
  67. visidata/features/canvas_save_svg.py +69 -0
  68. visidata/features/change_precision.py +46 -0
  69. visidata/features/cmdpalette.py +163 -0
  70. visidata/features/colorbrewer.py +363 -0
  71. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  72. visidata/features/command_server.py +105 -0
  73. visidata/features/currency_to_usd.py +70 -0
  74. visidata/{customdate.py → features/customdate.py} +2 -0
  75. visidata/features/dedupe.py +132 -0
  76. visidata/{describe.py → features/describe.py} +17 -15
  77. visidata/features/errors_guide.py +26 -0
  78. visidata/features/expand_cols.py +202 -0
  79. visidata/{fill.py → features/fill.py} +4 -2
  80. visidata/{freeze.py → features/freeze.py} +11 -6
  81. visidata/features/graph_seaborn.py +79 -0
  82. visidata/features/helloworld.py +10 -0
  83. visidata/features/hint_types.py +17 -0
  84. visidata/{incr.py → features/incr.py} +5 -0
  85. visidata/{join.py → features/join.py} +107 -53
  86. visidata/features/known_cols.py +21 -0
  87. visidata/features/layout.py +62 -0
  88. visidata/{melt.py → features/melt.py} +33 -21
  89. visidata/features/normcol.py +118 -0
  90. visidata/features/open_config.py +7 -0
  91. visidata/features/open_syspaste.py +18 -0
  92. visidata/features/ping.py +157 -0
  93. visidata/features/procmgr.py +208 -0
  94. visidata/features/random_sample.py +6 -0
  95. visidata/{regex.py → features/regex.py} +47 -31
  96. visidata/features/reload_every.py +55 -0
  97. visidata/features/rename_col_cascade.py +30 -0
  98. visidata/features/scroll_context.py +60 -0
  99. visidata/features/select_equal_selected.py +11 -0
  100. visidata/features/setcol_fake.py +65 -0
  101. visidata/{slide.py → features/slide.py} +75 -21
  102. visidata/features/sparkline.py +48 -0
  103. visidata/features/status_source.py +20 -0
  104. visidata/{sysedit.py → features/sysedit.py} +2 -1
  105. visidata/features/sysopen_mailcap.py +46 -0
  106. visidata/features/term_extras.py +13 -0
  107. visidata/{transpose.py → features/transpose.py} +5 -4
  108. visidata/features/type_ipaddr.py +73 -0
  109. visidata/features/type_url.py +11 -0
  110. visidata/{unfurl.py → features/unfurl.py} +9 -9
  111. visidata/{window.py → features/window.py} +2 -2
  112. visidata/form.py +50 -21
  113. visidata/freqtbl.py +81 -33
  114. visidata/fuzzymatch.py +414 -0
  115. visidata/graph.py +105 -33
  116. visidata/guide.py +180 -0
  117. visidata/help.py +75 -44
  118. visidata/hint.py +39 -0
  119. visidata/indexsheet.py +109 -0
  120. visidata/input_history.py +55 -0
  121. visidata/interface.py +58 -0
  122. visidata/keys.py +17 -16
  123. visidata/loaders/__init__.py +9 -0
  124. visidata/loaders/_pandas.py +61 -21
  125. visidata/loaders/api_airtable.py +70 -0
  126. visidata/loaders/api_bitio.py +102 -0
  127. visidata/loaders/api_matrix.py +148 -0
  128. visidata/loaders/api_reddit.py +306 -0
  129. visidata/loaders/api_zulip.py +249 -0
  130. visidata/loaders/archive.py +41 -7
  131. visidata/loaders/arrow.py +7 -7
  132. visidata/loaders/conll.py +49 -0
  133. visidata/loaders/csv.py +25 -7
  134. visidata/loaders/eml.py +3 -4
  135. visidata/loaders/f5log.py +1204 -0
  136. visidata/loaders/fec.py +325 -0
  137. visidata/loaders/fixed_width.py +3 -5
  138. visidata/loaders/frictionless.py +3 -3
  139. visidata/loaders/geojson.py +8 -5
  140. visidata/loaders/google.py +48 -0
  141. visidata/loaders/graphviz.py +4 -4
  142. visidata/loaders/hdf5.py +4 -4
  143. visidata/loaders/html.py +48 -10
  144. visidata/loaders/http.py +84 -30
  145. visidata/loaders/imap.py +20 -10
  146. visidata/loaders/jrnl.py +52 -0
  147. visidata/loaders/json.py +83 -29
  148. visidata/loaders/jsonla.py +74 -0
  149. visidata/loaders/lsv.py +15 -11
  150. visidata/loaders/mailbox.py +40 -0
  151. visidata/loaders/markdown.py +1 -3
  152. visidata/loaders/mbtiles.py +4 -5
  153. visidata/loaders/mysql.py +11 -13
  154. visidata/loaders/npy.py +7 -7
  155. visidata/loaders/odf.py +4 -1
  156. visidata/loaders/orgmode.py +428 -0
  157. visidata/loaders/pandas_freqtbl.py +14 -20
  158. visidata/loaders/parquet.py +62 -6
  159. visidata/loaders/pcap.py +3 -3
  160. visidata/loaders/pdf.py +4 -3
  161. visidata/loaders/png.py +19 -13
  162. visidata/loaders/postgres.py +9 -8
  163. visidata/loaders/rec.py +7 -3
  164. visidata/loaders/s3.py +342 -0
  165. visidata/loaders/sas.py +5 -5
  166. visidata/loaders/scrape.py +186 -0
  167. visidata/loaders/shp.py +6 -5
  168. visidata/loaders/spss.py +5 -6
  169. visidata/loaders/sqlite.py +68 -28
  170. visidata/loaders/texttables.py +1 -1
  171. visidata/loaders/toml.py +60 -0
  172. visidata/loaders/tsv.py +61 -19
  173. visidata/loaders/ttf.py +19 -7
  174. visidata/loaders/unzip_http.py +6 -5
  175. visidata/loaders/usv.py +1 -1
  176. visidata/loaders/vcf.py +16 -16
  177. visidata/loaders/vds.py +10 -7
  178. visidata/loaders/vdx.py +30 -5
  179. visidata/loaders/xlsb.py +8 -1
  180. visidata/loaders/xlsx.py +145 -25
  181. visidata/loaders/xml.py +6 -3
  182. visidata/loaders/xword.py +4 -4
  183. visidata/loaders/yaml.py +15 -5
  184. visidata/macos.py +1 -1
  185. visidata/macros.py +130 -41
  186. visidata/main.py +119 -94
  187. visidata/mainloop.py +101 -154
  188. visidata/man/parse_options.py +2 -2
  189. visidata/man/vd.1 +302 -147
  190. visidata/man/vd.txt +291 -151
  191. visidata/memory.py +3 -3
  192. visidata/menu.py +104 -423
  193. visidata/metasheets.py +59 -141
  194. visidata/modify.py +79 -23
  195. visidata/motd.py +3 -3
  196. visidata/mouse.py +137 -0
  197. visidata/movement.py +43 -35
  198. visidata/optionssheet.py +99 -0
  199. visidata/path.py +131 -43
  200. visidata/pivot.py +74 -47
  201. visidata/plugins.py +65 -192
  202. visidata/pyobj.py +50 -201
  203. visidata/rename_col.py +20 -0
  204. visidata/save.py +42 -20
  205. visidata/search.py +54 -10
  206. visidata/selection.py +84 -5
  207. visidata/settings.py +162 -24
  208. visidata/sheets.py +229 -257
  209. visidata/shell.py +51 -21
  210. visidata/sidebar.py +162 -0
  211. visidata/sort.py +11 -4
  212. visidata/statusbar.py +113 -104
  213. visidata/stored_list.py +43 -0
  214. visidata/stored_prop.py +38 -0
  215. visidata/tests/conftest.py +3 -3
  216. visidata/tests/test_cliptext.py +39 -0
  217. visidata/tests/test_commands.py +62 -7
  218. visidata/tests/test_edittext.py +2 -2
  219. visidata/tests/test_features.py +17 -0
  220. visidata/tests/test_menu.py +14 -0
  221. visidata/tests/test_path.py +13 -4
  222. visidata/text_source.py +53 -0
  223. visidata/textsheet.py +10 -3
  224. visidata/theme.py +44 -0
  225. visidata/themes/__init__.py +0 -0
  226. visidata/themes/ascii8.py +84 -0
  227. visidata/themes/asciimono.py +84 -0
  228. visidata/themes/light.py +17 -0
  229. visidata/threads.py +87 -39
  230. visidata/tuiwin.py +22 -0
  231. visidata/type_currency.py +22 -3
  232. visidata/type_date.py +31 -9
  233. visidata/type_floatsi.py +5 -1
  234. visidata/undo.py +18 -6
  235. visidata/utils.py +106 -23
  236. visidata/vdobj.py +28 -17
  237. visidata/windows.py +10 -0
  238. visidata/wrappers.py +9 -3
  239. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  240. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
  241. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
  242. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  243. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
  244. visidata-3.0.dist-info/RECORD +257 -0
  245. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  246. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
  247. visidata/layout.py +0 -44
  248. visidata/misc.py +0 -5
  249. visidata-2.11.dev0.dist-info/RECORD +0 -142
  250. /visidata/{repeat.py → features/repeat.py} +0 -0
  251. {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
  252. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  253. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/mainloop.py CHANGED
@@ -1,12 +1,12 @@
1
+ import builtins
1
2
  import contextlib
2
3
  import os
3
4
  import curses
4
5
  import signal
5
6
  import threading
6
7
  import time
7
- from unittest import mock
8
8
 
9
- from visidata import vd, VisiData, colors, ESC, options, clipbox
9
+ from visidata import vd, VisiData, colors, ESC, options, BaseSheet, AttrDict
10
10
 
11
11
  __all__ = ['ReturnValue', 'run']
12
12
 
@@ -15,73 +15,43 @@ vd.timeouts_before_idle = 10
15
15
  vd.min_draw_ms = 100 # draw_all at least this often, even if keystrokes are pending
16
16
  vd._lastDrawTime = 0 # last time drawn (from time.time())
17
17
 
18
- vd.option('disp_splitwin_pct', 0, 'height of second sheet on screen')
19
- vd.option('mouse_interval', 1, 'max time between press/release for click (ms)', sheettype=None)
20
- vd.option('color_sidebar', 'black on 114 blue', 'color of sidebar')
21
-
22
18
 
23
19
  class ReturnValue(BaseException):
24
20
  'raise ReturnValue(ret) to exit from an inner runresult() with its result.'
25
21
  pass
26
22
 
27
23
 
28
-
29
24
  @VisiData.api
30
- def draw_sheet(self, scr, sheet):
31
- 'Erase *scr* and draw *sheet* on it, including status bars and sidebar.'
32
-
33
- sheet.ensureLoaded()
34
-
35
- scr.erase() # clear screen before every re-draw
36
- scr.bkgd(' ', colors.color_default)
37
-
38
- sheet._scr = scr
39
-
40
- self.drawLeftStatus(scr, sheet)
41
- self.drawRightStatus(scr, sheet) # visible during this getkeystroke
42
-
43
- try:
44
- sheet.draw(scr)
45
- except Exception as e:
46
- self.exceptionCaught(e)
47
-
25
+ def callNoExceptions(vd, func, *args, **kwargs):
26
+ 'Catch and log any raised exceptions. Reraise when options.debug.'
48
27
  try:
49
- sidebar = sheet.sidebar
50
- sidebar_title = sheet.sidebar_title
28
+ return func(*args, **kwargs)
51
29
  except Exception as e:
52
30
  vd.exceptionCaught(e)
53
- sidebar = str(e)
54
- sidebar_title = 'error'
55
-
56
- vd.drawSidebar(scr, sidebar, title=sidebar_title)
57
-
58
-
59
- def iterwraplines(lines, width=80):
60
- import textwrap
61
- for line in lines:
62
- yield from textwrap.wrap(line, width=width, subsequent_indent=' ')
63
31
 
64
32
 
65
33
  @VisiData.api
66
- def drawSidebar(vd, scr, text, title='sidebar'):
67
- if not text:
68
- return
34
+ def drawSheet(vd, scr, sheet):
35
+ 'Erase *scr* and draw *sheet* on it, including status bars and sidebar.'
69
36
 
70
- h, w = scr.getmaxyx()
71
- maxh, maxw = 0, 0
37
+ sheet.ensureLoaded()
72
38
 
73
- lines = list(iterwraplines(text.splitlines(), width=w//2-2))
39
+ scr.erase() # clear screen before every re-draw
40
+ scr.bkgd(' ', colors.color_default.attr)
74
41
 
75
- maxh = min(h-2, len(lines)+2)
76
- maxw = min(w//2, max(map(len, lines))+4)
42
+ sheet._scr = scr
77
43
 
78
- sidebar_scr = scr.derwin(maxh, maxw, h-maxh-1, w-maxw-1)
79
- clipbox(sidebar_scr, lines, colors.color_sidebar, title=title)
44
+ vd.callNoExceptions(sheet.draw, scr)
45
+ vd.callNoExceptions(vd.drawLeftStatus, scr, sheet)
46
+ vd.callNoExceptions(vd.drawRightStatus, scr, sheet) # visible during this getkeystroke
80
47
 
81
48
 
82
49
  vd.windowConfig = dict(pct=0, n=0, h=0, w=0) # n=top line of bottom window; h=height of bottom window; w=width of screen
83
- vd.winTop = mock.MagicMock(__bool__=mock.Mock(return_value=False))
84
- vd.scrMenu = mock.MagicMock(__bool__=mock.Mock(return_value=False))
50
+
51
+ vd.winTop = None
52
+ vd.scrMenu = None
53
+ vd.scrFull = None
54
+
85
55
 
86
56
  @VisiData.api
87
57
  def setWindows(vd, scr, pct=None):
@@ -105,17 +75,17 @@ def setWindows(vd, scr, pct=None):
105
75
  if not topmenulines:
106
76
  vd.scrMenu = None
107
77
  elif not vd.scrMenu:
108
- vd.scrMenu = scr.derwin(h, w, 0, 0)
78
+ vd.scrMenu = vd.subwindow(scr, 0, 0, w, h)
109
79
  vd.scrMenu.keypad(1)
110
80
 
111
- vd.winTop = scr.derwin(n, w, topmenulines, 0)
81
+ vd.winTop = vd.subwindow(scr, 0, topmenulines, w, n)
112
82
  vd.winTop.keypad(1)
113
- vd.winBottom = scr.derwin(h-n-topmenulines, w, n+topmenulines, 0)
83
+ vd.winBottom = vd.subwindow(scr, 0, n+topmenulines, w, h-n-topmenulines)
114
84
  vd.winBottom.keypad(1)
115
85
  if pct == 0 or pct >= 100: # no second pane
116
86
  vd.win1 = vd.winBottom
117
87
  # drawing to 0-line window causes problems
118
- vd.win2 = mock.MagicMock(__bool__=mock.Mock(return_value=False))
88
+ vd.win2 = None
119
89
  elif pct > 0: # pane 2 from line n to bottom
120
90
  vd.win1 = vd.winTop
121
91
  vd.win2 = vd.winBottom
@@ -141,24 +111,26 @@ def draw_all(vd):
141
111
  if ss1 and not ss2:
142
112
  vd.activePane = 1
143
113
  vd.setWindows(vd.scrFull)
144
- vd.draw_sheet(vd.win1, ss1[0])
114
+ vd.drawSheet(vd.win1, ss1[0])
145
115
  if vd.win2:
146
116
  vd.win2.erase()
147
117
  elif not ss1 and ss2:
148
118
  vd.activePane = 2
149
119
  vd.setWindows(vd.scrFull)
150
- vd.draw_sheet(vd.win2, ss2[0])
120
+ vd.drawSheet(vd.win2, ss2[0])
151
121
  if vd.win1:
152
122
  vd.win1.erase()
153
123
  elif ss1 and ss2 and vd.win2:
154
- vd.draw_sheet(vd.win1, ss1[0])
155
- vd.draw_sheet(vd.win2, ss2[0])
124
+ vd.drawSheet(vd.win1, ss1[0])
125
+ vd.drawSheet(vd.win2, ss2[0])
156
126
  elif ss1 and ss2 and not vd.win2:
157
- vd.draw_sheet(vd.win1, vd.sheetstack(vd.activePane)[0])
127
+ vd.drawSheet(vd.win1, vd.sheetstack(vd.activePane)[0])
158
128
  vd.setWindows(vd.scrFull)
159
129
 
160
130
  if vd.scrMenu:
161
- vd.drawMenu(vd.scrMenu, vd.activeSheet)
131
+ vd.callNoExceptions(vd.drawMenu, vd.scrMenu, vd.activeSheet)
132
+
133
+ vd.callNoExceptions(vd.drawSidebar, vd.scrFull, vd.activeSheet)
162
134
 
163
135
  if vd.win1:
164
136
  vd.win1.refresh()
@@ -179,37 +151,7 @@ def runresult(vd):
179
151
 
180
152
 
181
153
  @VisiData.api
182
- def parseMouse(vd, **kwargs):
183
- 'Return list of mouse interactions (clicktype, y, x, name, scr) for curses screens given in kwargs as name:scr.'
184
-
185
- devid, x, y, z, bstate = curses.getmouse()
186
-
187
- clicktype = ''
188
- if bstate & curses.BUTTON_CTRL:
189
- clicktype += "CTRL-"
190
- bstate &= ~curses.BUTTON_CTRL
191
- if bstate & curses.BUTTON_ALT:
192
- clicktype += "ALT-"
193
- bstate &= ~curses.BUTTON_ALT
194
- if bstate & curses.BUTTON_SHIFT:
195
- clicktype += "SHIFT-"
196
- bstate &= ~curses.BUTTON_SHIFT
197
-
198
- keystroke = clicktype + curses.mouseEvents.get(bstate, str(bstate))
199
-
200
- found = []
201
- for winname, winscr in kwargs.items():
202
- py, px = winscr.getparyx()
203
- mh, mw = winscr.getmaxyx()
204
- if py <= y < py+mh and px <= x < px+mw:
205
- found.append((keystroke, y-py, x-px, winname, winscr))
206
- # vd.debug(f'{keystroke} at ({x-px}, {y-py}) in window {winname} {winscr}')
207
-
208
- return found
209
-
210
-
211
- @VisiData.api
212
- def mainloop(self, scr):
154
+ def mainloop(vd, scr):
213
155
  'Manage execution of keystrokes and subsequent redrawing of screen.'
214
156
  nonidle_timeout = vd.curses_timeout
215
157
 
@@ -221,12 +163,12 @@ def mainloop(self, scr):
221
163
  prefixWaiting = False
222
164
  vd.scrFull = scr
223
165
 
224
- self.keystrokes = ''
166
+ vd.keystrokes = ''
225
167
  while True:
226
- if not self.stackedSheets and self.currentReplay is None:
168
+ if not vd.stackedSheets and vd.currentReplay is None:
227
169
  return
228
170
 
229
- sheet = self.activeSheet
171
+ sheet = vd.activeSheet
230
172
 
231
173
  if not sheet:
232
174
  continue # waiting for replay to push sheet
@@ -236,82 +178,76 @@ def mainloop(self, scr):
236
178
 
237
179
  vd.setWindows(vd.scrFull)
238
180
 
239
- if not self.drainPendingKeys(scr) or time.time() - self._lastDrawTime > self.min_draw_ms/1000: #1459
240
- self.draw_all()
241
- self._lastDrawTime = time.time()
181
+ if not vd.drainPendingKeys(scr) or time.time() - vd._lastDrawTime > vd.min_draw_ms/1000: #1459
182
+ vd.draw_all()
183
+ vd._lastDrawTime = time.time()
242
184
 
243
- if vd._nextCommands:
244
- sheet.execCommand(vd._nextCommands.pop(0), keystrokes=self.keystrokes)
245
- continue
246
-
247
- keystroke = self.getkeystroke(scr, sheet)
185
+ keystroke = vd.getkeystroke(scr, sheet)
248
186
 
249
- if not keystroke and prefixWaiting and "Alt+" in self.keystrokes: # timeout ESC
250
- self.keystrokes = ''
187
+ if not keystroke and prefixWaiting and "Alt+" in vd.keystrokes: # timeout ESC
188
+ vd.keystrokes = ''
251
189
 
252
190
  if keystroke: # wait until next keystroke to clear statuses and previous keystrokes
253
191
  numTimeouts = 0
254
192
  if not prefixWaiting:
255
- self.keystrokes = ''
193
+ vd.keystrokes = ''
256
194
 
257
- self.statuses.clear()
195
+ vd.statuses.clear()
258
196
 
259
197
  if keystroke == 'KEY_MOUSE':
260
198
  try:
261
- self.keystrokes = ''
262
- pct = vd.windowConfig['pct']
263
- topPaneActive = ((vd.activePane == 2 and pct < 0) or (vd.activePane == 1 and pct > 0))
264
- bottomPaneActive = ((vd.activePane == 1 and pct < 0) or (vd.activePane == 2 and pct > 0))
265
-
266
- if (bottomPaneActive and winname == 'top') or (topPaneActive and winname == 'bot'):
267
- self.activePane = 1 if self.activePane == 2 else 2
268
- sheet = self.activeSheet
269
-
270
- for keystroke, y, x, winname, winscr in vd.parseMouse(top=vd.winTop, bot=vd.winBottom, menu=vd.scrMenu):
271
- f = self.getMouse(winscr, x, y, keystroke)
272
- sheet.mouseX, sheet.mouseY = x, y
273
- if f:
274
- if isinstance(f, str):
275
- for cmd in f.split():
276
- sheet.execCommand(cmd)
277
- else:
278
- f(y, x, keystroke)
279
-
280
- self.keystrokes = self.prettykeys(keystroke)
281
- keystroke = '' # already handled
282
- break # first successful command stops checking
283
- except curses.error:
284
- pass
199
+ keystroke = vd.handleMouse(sheet) # if it was handled, don't handle again as a regular keystroke
285
200
  except Exception as e:
286
- self.exceptionCaught(e)
201
+ vd.exceptionCaught(e)
287
202
 
288
- if keystroke and keystroke in self.keystrokes[:-1]:
203
+ if keystroke and keystroke in vd.allPrefixes and keystroke in vd.keystrokes[:-1]:
289
204
  vd.warning('duplicate prefix: ' + keystroke)
290
- self.keystrokes = ''
205
+ vd.keystrokes = ''
291
206
  else:
292
- self.keystrokes += self.prettykeys(keystroke)
207
+ keystroke = vd.prettykeys(keystroke)
208
+ vd.keystrokes += keystroke
293
209
 
294
- self.drawRightStatus(sheet._scr, sheet) # visible for commands that wait for input
210
+ vd.drawRightStatus(sheet._scr, sheet) # visible for commands that wait for input
295
211
 
296
212
  if not keystroke: # timeout instead of keypress
297
213
  pass
298
- elif keystroke == '^Q':
299
- return self.lastErrors and '\n'.join(self.lastErrors[-1])
300
- elif vd.bindkeys._get(self.keystrokes):
301
- sheet.execCommand(self.keystrokes, keystrokes=self.keystrokes)
214
+ elif keystroke == 'Ctrl+Q':
215
+ return vd.lastErrors and '\n'.join(vd.lastErrors[-1])
216
+ elif vd.bindkeys._get(vd.keystrokes):
217
+ sheet.execCommand(vd.keystrokes, keystrokes=vd.keystrokes)
302
218
  prefixWaiting = False
303
- elif keystroke in self.allPrefixes:
219
+ elif keystroke in vd.allPrefixes:
304
220
  prefixWaiting = True
305
221
  else:
306
- vd.status('no command for "%s"' % (self.keystrokes))
222
+ vd.status('no command for "%s"' % (vd.keystrokes))
307
223
  prefixWaiting = False
308
224
 
309
- self.checkForFinishedThreads()
310
- sheet.checkCursorNoExceptions()
225
+ # play next queued command
226
+ if vd._nextCommands and not vd.unfinishedThreads:
227
+ cmd = vd._nextCommands.pop(0)
228
+ if isinstance(cmd, (dict, list)): # .vd cmdlog rows are NamedListTemplate
229
+ try:
230
+ if vd.replayOne(cmd):
231
+ vd.replay_cancel()
232
+ except Exception as e:
233
+ vd.exceptionCaught(e)
234
+ vd.replay_cancel()
235
+ else:
236
+ sheet.execCommand(cmd, keystrokes=vd.keystrokes)
237
+
238
+ if not vd._nextCommands:
239
+ if vd.currentReplay:
240
+ vd.currentReplayRow = None
241
+ vd.currentReplay = None
242
+
243
+ vd.checkForFinishedThreads()
244
+ vd.callNoExceptions(sheet.checkCursor)
311
245
 
312
246
  # no idle redraw unless background threads are running
313
247
  time.sleep(0) # yield to other threads which may not have started yet
314
- if vd.unfinishedThreads:
248
+ if vd._nextCommands:
249
+ vd.curses_timeout = int(vd.options.replay_wait*1000)
250
+ elif vd.unfinishedThreads:
315
251
  vd.curses_timeout = nonidle_timeout
316
252
  else:
317
253
  numTimeouts += 1
@@ -323,7 +259,8 @@ def mainloop(self, scr):
323
259
  scr.timeout(vd.curses_timeout)
324
260
 
325
261
 
326
- def initCurses():
262
+ @VisiData.api
263
+ def initCurses(vd):
327
264
  # reduce ESC timeout to 25ms. http://en.chys.info/2009/09/esdelay-ncurses/
328
265
  os.putenv('ESCDELAY', '25')
329
266
  curses.use_env(True)
@@ -338,25 +275,23 @@ def initCurses():
338
275
 
339
276
  curses.raw() # get control keys instead of signals
340
277
  curses.meta(1) # allow "8-bit chars"
341
- curses.MOUSE_ALL = 0xffffffff
342
- curses.mousemask(curses.MOUSE_ALL if options.mouse_interval else 0)
343
- curses.mouseinterval(options.mouse_interval)
344
- curses.mouseEvents = {}
345
278
 
346
279
  scr.keypad(1)
347
280
 
348
281
  curses.def_prog_mode()
349
282
 
350
- for k in dir(curses):
351
- if k.startswith('BUTTON') or k in ('REPORT_MOUSE_POSITION', '2097152'):
352
- curses.mouseEvents[getattr(curses, k)] = k
283
+ vd.drainPendingKeys(scr)
284
+ if '\x1b' in vd.pendingKeys: #1993
285
+ # if start of an ANSI escape sequence, might be mangled, discard remaining keystrokes
286
+ vd.pendingKeys.clear()
287
+ curses.flushinp()
353
288
 
354
289
  return scr
355
290
 
356
291
 
357
292
  def wrapper(f, *args, **kwargs):
358
293
  try:
359
- scr = initCurses()
294
+ scr = vd.initCurses()
360
295
  return f(scr, *args, **kwargs)
361
296
  finally:
362
297
  curses.endwin()
@@ -372,9 +307,11 @@ def run(vd, *sheetlist):
372
307
  for vs in sheetlist:
373
308
  vd.push(vs, load=False)
374
309
 
375
- scr = initCurses()
310
+ scr = vd.initCurses()
376
311
  ret = vd.mainloop(scr)
377
312
  except curses.error as e:
313
+ if vd.options.debug:
314
+ raise
378
315
  vd.fail(str(e))
379
316
  finally:
380
317
  if scr:
@@ -383,4 +320,14 @@ def run(vd, *sheetlist):
383
320
  vd.cancelThread(*[t for t in vd.unfinishedThreads if not t.name.startswith('save_')])
384
321
 
385
322
  if ret:
386
- vd.printout(ret)
323
+ builtins.print(ret)
324
+
325
+ return ret
326
+
327
+ @VisiData.api
328
+ def addCommand(vd, *args, **kwargs):
329
+ return BaseSheet.addCommand(*args, **kwargs)
330
+
331
+
332
+ import sys
333
+ vd.addGlobals({k:getattr(sys.modules[__name__], k) for k in __all__})
@@ -23,8 +23,8 @@ options_menu_skel = '''.It Sy "{optname:<19}" No "{default}"
23
23
  {description}
24
24
  '''
25
25
 
26
- visidata.options.setdefault('plot_colors', '', visidata.options._opts._get('plot_colors', 'default').helpstr)
27
- visidata.options.setdefault('motd_url', '', visidata.options._opts._get('motd_url', 'default').helpstr)
26
+ visidata.options.setdefault('plot_colors', '', visidata.options._opts._get('plot_colors', 'default').helpstr, visidata.options._opts._get('plot_colors', 'default').module)
27
+ visidata.options.setdefault('motd_url', '', visidata.options._opts._get('motd_url', 'default').helpstr, visidata.options._opts._get('motd_url', 'default').module)
28
28
 
29
29
  with open(fncli, 'w') as cliOut:
30
30
  with open(fnopts, 'w') as menuOut: