visidata 2.11.1__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 (255) 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 +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. {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 +59 -50
  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 +5 -1
  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 +30 -5
  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 +163 -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} +75 -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 +180 -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 +17 -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 +48 -10
  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 +301 -148
  187. visidata/man/vd.txt +290 -153
  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 +50 -201
  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 +229 -257
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +113 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/conftest.py +3 -3
  213. visidata/tests/test_cliptext.py +39 -0
  214. visidata/tests/test_commands.py +62 -7
  215. visidata/tests/test_edittext.py +2 -2
  216. visidata/tests/test_features.py +17 -0
  217. visidata/tests/test_menu.py +14 -0
  218. visidata/tests/test_path.py +13 -4
  219. visidata/text_source.py +53 -0
  220. visidata/textsheet.py +10 -3
  221. visidata/theme.py +44 -0
  222. visidata/themes/__init__.py +0 -0
  223. visidata/themes/ascii8.py +84 -0
  224. visidata/themes/asciimono.py +84 -0
  225. visidata/themes/light.py +17 -0
  226. visidata/threads.py +87 -39
  227. visidata/tuiwin.py +22 -0
  228. visidata/type_currency.py +22 -3
  229. visidata/type_date.py +31 -9
  230. visidata/type_floatsi.py +5 -1
  231. visidata/undo.py +17 -5
  232. visidata/utils.py +106 -23
  233. visidata/vdobj.py +28 -17
  234. visidata/windows.py +10 -0
  235. visidata/wrappers.py +9 -3
  236. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  237. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
  238. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
  239. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  240. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
  241. visidata-3.0.dist-info/RECORD +257 -0
  242. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  243. vgit/__init__.py +0 -1
  244. vgit/gitsheet.py +0 -164
  245. visidata/layout.py +0 -44
  246. visidata/misc.py +0 -5
  247. visidata-2.11.1.data/scripts/vgit +0 -9
  248. visidata-2.11.1.dist-info/RECORD +0 -155
  249. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  250. {vgit → visidata/apps/vgit}/abort.py +0 -0
  251. /visidata/{repeat.py → features/repeat.py} +0 -0
  252. {visidata-2.11.1.data → visidata-3.0.data}/scripts/vd +0 -0
  253. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
  255. {visidata-2.11.1.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,83 +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
- r = vd.parseMouse(top=vd.winTop, bot=vd.winBottom, menu=vd.scrMenu)
267
- for keystroke, y, x, winname, winscr in reversed(r):
268
- if (bottomPaneActive and winname == 'top') or (topPaneActive and winname == 'bot'):
269
- self.activePane = 1 if self.activePane == 2 else 2
270
- sheet = self.activeSheet
271
-
272
- f = self.getMouse(winscr, x, y, keystroke)
273
- sheet.mouseX, sheet.mouseY = x, y
274
- if f:
275
- if isinstance(f, str):
276
- for cmd in f.split():
277
- sheet.execCommand(cmd)
278
- else:
279
- f(y, x, keystroke)
280
-
281
- self.keystrokes = self.prettykeys(keystroke)
282
- keystroke = '' # already handled
283
- break # first successful command stops checking
284
- except curses.error:
285
- pass
199
+ keystroke = vd.handleMouse(sheet) # if it was handled, don't handle again as a regular keystroke
286
200
  except Exception as e:
287
- self.exceptionCaught(e)
201
+ vd.exceptionCaught(e)
288
202
 
289
- if keystroke and keystroke in self.keystrokes[:-1]:
203
+ if keystroke and keystroke in vd.allPrefixes and keystroke in vd.keystrokes[:-1]:
290
204
  vd.warning('duplicate prefix: ' + keystroke)
291
- self.keystrokes = ''
205
+ vd.keystrokes = ''
292
206
  else:
293
- self.keystrokes += self.prettykeys(keystroke)
207
+ keystroke = vd.prettykeys(keystroke)
208
+ vd.keystrokes += keystroke
294
209
 
295
- 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
296
211
 
297
212
  if not keystroke: # timeout instead of keypress
298
213
  pass
299
- elif keystroke == '^Q':
300
- return self.lastErrors and '\n'.join(self.lastErrors[-1])
301
- elif vd.bindkeys._get(self.keystrokes):
302
- 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)
303
218
  prefixWaiting = False
304
- elif keystroke in self.allPrefixes:
219
+ elif keystroke in vd.allPrefixes:
305
220
  prefixWaiting = True
306
221
  else:
307
- vd.status('no command for "%s"' % (self.keystrokes))
222
+ vd.status('no command for "%s"' % (vd.keystrokes))
308
223
  prefixWaiting = False
309
224
 
310
- self.checkForFinishedThreads()
311
- 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)
312
245
 
313
246
  # no idle redraw unless background threads are running
314
247
  time.sleep(0) # yield to other threads which may not have started yet
315
- if vd.unfinishedThreads:
248
+ if vd._nextCommands:
249
+ vd.curses_timeout = int(vd.options.replay_wait*1000)
250
+ elif vd.unfinishedThreads:
316
251
  vd.curses_timeout = nonidle_timeout
317
252
  else:
318
253
  numTimeouts += 1
@@ -324,7 +259,8 @@ def mainloop(self, scr):
324
259
  scr.timeout(vd.curses_timeout)
325
260
 
326
261
 
327
- def initCurses():
262
+ @VisiData.api
263
+ def initCurses(vd):
328
264
  # reduce ESC timeout to 25ms. http://en.chys.info/2009/09/esdelay-ncurses/
329
265
  os.putenv('ESCDELAY', '25')
330
266
  curses.use_env(True)
@@ -339,25 +275,23 @@ def initCurses():
339
275
 
340
276
  curses.raw() # get control keys instead of signals
341
277
  curses.meta(1) # allow "8-bit chars"
342
- curses.MOUSE_ALL = 0xffffffff
343
- curses.mousemask(curses.MOUSE_ALL if options.mouse_interval else 0)
344
- curses.mouseinterval(options.mouse_interval)
345
- curses.mouseEvents = {}
346
278
 
347
279
  scr.keypad(1)
348
280
 
349
281
  curses.def_prog_mode()
350
282
 
351
- for k in dir(curses):
352
- if k.startswith('BUTTON') or k in ('REPORT_MOUSE_POSITION', '2097152'):
353
- 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()
354
288
 
355
289
  return scr
356
290
 
357
291
 
358
292
  def wrapper(f, *args, **kwargs):
359
293
  try:
360
- scr = initCurses()
294
+ scr = vd.initCurses()
361
295
  return f(scr, *args, **kwargs)
362
296
  finally:
363
297
  curses.endwin()
@@ -373,9 +307,11 @@ def run(vd, *sheetlist):
373
307
  for vs in sheetlist:
374
308
  vd.push(vs, load=False)
375
309
 
376
- scr = initCurses()
310
+ scr = vd.initCurses()
377
311
  ret = vd.mainloop(scr)
378
312
  except curses.error as e:
313
+ if vd.options.debug:
314
+ raise
379
315
  vd.fail(str(e))
380
316
  finally:
381
317
  if scr:
@@ -384,4 +320,14 @@ def run(vd, *sheetlist):
384
320
  vd.cancelThread(*[t for t in vd.unfinishedThreads if not t.name.startswith('save_')])
385
321
 
386
322
  if ret:
387
- 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: