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
visidata/clipboard.py
CHANGED
@@ -5,19 +5,24 @@ import io
|
|
5
5
|
import sys
|
6
6
|
import tempfile
|
7
7
|
import functools
|
8
|
+
import os
|
8
9
|
|
9
|
-
from visidata import VisiData, vd, asyncthread
|
10
|
-
from visidata import Sheet, Path
|
10
|
+
from visidata import VisiData, vd, asyncthread, SettableColumn
|
11
|
+
from visidata import Sheet, Path, Column
|
11
12
|
|
12
13
|
if sys.platform == 'win32':
|
13
|
-
syscopy_cmd_default = 'clip'
|
14
|
-
syspaste_cmd_default = '
|
14
|
+
syscopy_cmd_default = 'clip.exe'
|
15
|
+
syspaste_cmd_default = 'powershell -command Get-Clipboard'
|
15
16
|
elif sys.platform == 'darwin':
|
16
17
|
syscopy_cmd_default = 'pbcopy w'
|
17
18
|
syspaste_cmd_default = 'pbpaste'
|
18
19
|
else:
|
19
|
-
|
20
|
-
|
20
|
+
if 'WAYLAND_DISPLAY' in os.environ:
|
21
|
+
syscopy_cmd_default = 'wl-copy'
|
22
|
+
syspaste_cmd_default = 'wl-paste'
|
23
|
+
else:
|
24
|
+
syscopy_cmd_default = 'xclip -selection clipboard -filter' # xsel --clipboard --input
|
25
|
+
syspaste_cmd_default = 'xclip -selection clipboard -o' # xsel --clipboard
|
21
26
|
|
22
27
|
vd.option('clipboard_copy_cmd', syscopy_cmd_default, 'command to copy stdin to system clipboard', sheettype=None)
|
23
28
|
vd.option('clipboard_paste_cmd', syspaste_cmd_default, 'command to send contents of system clipboard to stdout', sheettype=None)
|
@@ -48,7 +53,7 @@ def syscopyValue(sheet, val):
|
|
48
53
|
p = subprocess.run(
|
49
54
|
sheet.options.clipboard_copy_cmd.split(),
|
50
55
|
input=val,
|
51
|
-
encoding=
|
56
|
+
encoding='utf-8',
|
52
57
|
stdout=subprocess.DEVNULL)
|
53
58
|
|
54
59
|
vd.status('copied value to system clipboard')
|
@@ -70,16 +75,19 @@ def syscopyCells_async(sheet, cols, rows, filetype):
|
|
70
75
|
vd.status(f'copying {vs.nRows} {vs.rowtype} to system clipboard as {filetype}')
|
71
76
|
|
72
77
|
with io.StringIO() as buf:
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
with tempfile.NamedTemporaryFile() as temp:
|
79
|
+
temp.close() #2118
|
80
|
+
|
81
|
+
vd.sync(vd.saveSheets(Path(f'{temp.name}.{filetype}', fptext=buf), vs, confirm_overwrite=False))
|
82
|
+
subprocess.run(
|
83
|
+
sheet.options.clipboard_copy_cmd.split(),
|
84
|
+
input=buf.getvalue(),
|
85
|
+
encoding='utf-8',
|
86
|
+
stdout=subprocess.DEVNULL)
|
79
87
|
|
80
88
|
|
81
89
|
@VisiData.api
|
82
|
-
def
|
90
|
+
def sysclipValue(vd):
|
83
91
|
cmd = vd.options.clipboard_paste_cmd
|
84
92
|
return subprocess.check_output(vd.options.clipboard_paste_cmd.split()).decode('utf-8')
|
85
93
|
|
@@ -87,7 +95,7 @@ def sysclip_value(vd):
|
|
87
95
|
@VisiData.api
|
88
96
|
@asyncthread
|
89
97
|
def pasteFromClipboard(vd, cols, rows):
|
90
|
-
text = vd.getLastArgs() or vd.
|
98
|
+
text = vd.getLastArgs() or vd.sysclipValue().strip() or vd.fail('system clipboard is empty')
|
91
99
|
|
92
100
|
vd.addUndoSetValues(cols, rows)
|
93
101
|
lines = text.split('\n')
|
@@ -108,6 +116,8 @@ def pasteFromClipboard(vd, cols, rows):
|
|
108
116
|
|
109
117
|
@Sheet.api
|
110
118
|
def delete_row(sheet, rowidx):
|
119
|
+
if not sheet.rows:
|
120
|
+
vd.fail("no row to delete")
|
111
121
|
if not sheet.defer:
|
112
122
|
oldrow = sheet.rows.pop(rowidx)
|
113
123
|
vd.addUndo(sheet.rows.insert, rowidx, oldrow)
|
@@ -122,11 +132,33 @@ def delete_row(sheet, rowidx):
|
|
122
132
|
sheet.setModified()
|
123
133
|
return oldrow
|
124
134
|
|
135
|
+
|
125
136
|
@Sheet.api
|
137
|
+
@asyncthread
|
126
138
|
def paste_after(sheet, rowidx):
|
127
|
-
|
128
|
-
|
139
|
+
'Paste rows from *vd.cliprows* at *rowidx*.'
|
140
|
+
if not vd.memory.cliprows: #1793
|
141
|
+
vd.warning('nothing to paste')
|
142
|
+
return
|
129
143
|
|
144
|
+
for col in vd.memory.clipcols[sheet.nVisibleCols:]:
|
145
|
+
newcol = SettableColumn()
|
146
|
+
newcol.__setstate__(col.__getstate__())
|
147
|
+
sheet.addColumn(newcol)
|
148
|
+
|
149
|
+
addedRows = []
|
150
|
+
|
151
|
+
for extrow in vd.memory.cliprows:
|
152
|
+
if isinstance(extrow, Column):
|
153
|
+
newrow = copy(extrow)
|
154
|
+
else:
|
155
|
+
newrow = sheet.newRow()
|
156
|
+
for col, extcol in zip(sheet.visibleCols, vd.memory.clipcols):
|
157
|
+
col.setValue(newrow, extcol.getTypedValue(extrow))
|
158
|
+
|
159
|
+
addedRows.append(newrow)
|
160
|
+
|
161
|
+
sheet.addRows(addedRows, index=rowidx)
|
130
162
|
|
131
163
|
|
132
164
|
Sheet.addCommand('y', 'copy-row', 'copyRows([cursorRow])', 'yank (copy) current row to clipboard')
|
@@ -167,3 +199,27 @@ Sheet.addCommand('gzx', 'cut-cells', 'copyCells(cursorCol, onlySelectedRows); cu
|
|
167
199
|
|
168
200
|
Sheet.bindkey('KEY_DC', 'delete-cell'),
|
169
201
|
Sheet.bindkey('gKEY_DC', 'delete-cells'),
|
202
|
+
|
203
|
+
vd.addMenuItems('''
|
204
|
+
Edit > Delete > current row > delete-row
|
205
|
+
Edit > Delete > current cell > delete-cell
|
206
|
+
Edit > Delete > selected rows > delete-selected
|
207
|
+
Edit > Delete > selected cells > delete-cells
|
208
|
+
Edit > Copy > current cell > copy-cell
|
209
|
+
Edit > Copy > current row > copy-row
|
210
|
+
Edit > Copy > selected cells > copy-cells
|
211
|
+
Edit > Copy > selected rows > copy-selected
|
212
|
+
Edit > Copy > to system clipboard > current cell > syscopy-cell
|
213
|
+
Edit > Copy > to system clipboard > current row > syscopy-row
|
214
|
+
Edit > Copy > to system clipboard > selected cells > syscopy-cells
|
215
|
+
Edit > Copy > to system clipboard > selected rows > syscopy-selected
|
216
|
+
Edit > Cut > current row > cut-row
|
217
|
+
Edit > Cut > selected cells > cut-selected
|
218
|
+
Edit > Cut > current cell > cut-cell
|
219
|
+
Edit > Paste > row after > paste-after
|
220
|
+
Edit > Paste > row before > paste-before
|
221
|
+
Edit > Paste > into selected cells > setcol-clipboard
|
222
|
+
Edit > Paste > into current cell > paste-cell
|
223
|
+
Edit > Paste > from system clipboard > cells at cursor > syspaste-cells
|
224
|
+
Edit > Paste > from system clipboard > selected cells > syspaste-cells-selected
|
225
|
+
''')
|
visidata/cliptext.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
import unicodedata
|
2
2
|
import sys
|
3
|
+
import re
|
3
4
|
import functools
|
5
|
+
import textwrap
|
4
6
|
|
5
|
-
from visidata import options, drawcache
|
6
|
-
|
7
|
-
__all__ = ['clipstr', 'clipdraw', 'clipbox', 'dispwidth', 'iterchars']
|
7
|
+
from visidata import options, drawcache, vd, update_attr, colors, ColorAttr
|
8
8
|
|
9
9
|
disp_column_fill = ' '
|
10
|
+
internal_markup_re = r'(\[[:/][^\]]*?\])' # [:whatever until the closing bracket] or [/whatever] or [:]
|
10
11
|
|
11
12
|
### Curses helpers
|
12
13
|
|
@@ -52,16 +53,56 @@ def wcwidth(cc, ambig=1):
|
|
52
53
|
return 0
|
53
54
|
|
54
55
|
|
56
|
+
def is_vdcode(s:str) -> bool:
|
57
|
+
return (s.startswith('[:') and s.endswith(']')) or \
|
58
|
+
(s.startswith('[/') and s.endswith(']'))
|
59
|
+
|
60
|
+
|
61
|
+
def iterchunks(s, literal=False):
|
62
|
+
attrstack = [dict(link='', cattr=ColorAttr())]
|
63
|
+
legitopens = 0
|
64
|
+
chunks = re.split(internal_markup_re, s)
|
65
|
+
for chunk in chunks:
|
66
|
+
if not chunk:
|
67
|
+
continue
|
68
|
+
|
69
|
+
if not literal and is_vdcode(chunk):
|
70
|
+
cattr = attrstack[-1]['cattr']
|
71
|
+
link = attrstack[-1]['link']
|
72
|
+
|
73
|
+
if chunk.startswith('[:onclick '):
|
74
|
+
attrstack.append(dict(link=chunk[10:-1], cattr=cattr.update(colors.clickable)))
|
75
|
+
continue
|
76
|
+
elif chunk == '[:]': # clear stack, keep origattr
|
77
|
+
if len(attrstack) > 1:
|
78
|
+
del attrstack[1:]
|
79
|
+
continue
|
80
|
+
elif chunk.startswith('[/'): # pop last attr off stack
|
81
|
+
if len(attrstack) > 1:
|
82
|
+
attrstack.pop()
|
83
|
+
continue # don't display trailing [/foo] ever
|
84
|
+
else: # push updated color on stack
|
85
|
+
newcolor = colors.get_color(chunk[2:-1])
|
86
|
+
if newcolor:
|
87
|
+
cattr = update_attr(cattr, newcolor, len(attrstack))
|
88
|
+
attrstack.append(dict(link=link, cattr=cattr))
|
89
|
+
continue
|
90
|
+
|
91
|
+
yield attrstack[-1], chunk
|
92
|
+
|
93
|
+
|
55
94
|
@functools.lru_cache(maxsize=100000)
|
56
|
-
def dispwidth(ss, maxwidth=None):
|
95
|
+
def dispwidth(ss, maxwidth=None, literal=False):
|
57
96
|
'Return display width of string, according to unicodedata width and options.disp_ambig_width.'
|
58
97
|
disp_ambig_width = options.disp_ambig_width
|
59
98
|
w = 0
|
60
99
|
|
61
|
-
for
|
62
|
-
|
63
|
-
|
64
|
-
|
100
|
+
for _, s in iterchunks(ss, literal=literal):
|
101
|
+
for cc in s:
|
102
|
+
if cc:
|
103
|
+
w += wcwidth(cc, disp_ambig_width)
|
104
|
+
if maxwidth and w > maxwidth:
|
105
|
+
return maxwidth
|
65
106
|
return w
|
66
107
|
|
67
108
|
|
@@ -76,7 +117,7 @@ def _dispch(c, oddspacech=None, combch=None, modch=None):
|
|
76
117
|
elif c in ZERO_WIDTH_CF:
|
77
118
|
return combch, 1
|
78
119
|
|
79
|
-
return c, dispwidth(c)
|
120
|
+
return c, dispwidth(c, literal=True)
|
80
121
|
|
81
122
|
|
82
123
|
def iterchars(x):
|
@@ -103,24 +144,35 @@ def iterchars(x):
|
|
103
144
|
def _clipstr(s, dispw, trunch='', oddspacech='', combch='', modch=''):
|
104
145
|
'''Return clipped string and width in terminal display characters.
|
105
146
|
Note: width may differ from len(s) if East Asian chars are 'fullwidth'.'''
|
147
|
+
if not s:
|
148
|
+
return '', 0
|
149
|
+
|
150
|
+
if dispw == 1:
|
151
|
+
return s[0], 1
|
152
|
+
|
106
153
|
w = 0
|
107
154
|
ret = ''
|
108
155
|
|
109
156
|
trunchlen = dispwidth(trunch)
|
110
157
|
for c in s:
|
111
158
|
newc, chlen = _dispch(c, oddspacech=oddspacech, combch=combch, modch=modch)
|
112
|
-
if newc:
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
159
|
+
if not newc:
|
160
|
+
newc = c
|
161
|
+
chlen = dispwidth(c)
|
162
|
+
|
163
|
+
if dispw and w+chlen > dispw:
|
164
|
+
if trunchlen and dispw > trunchlen:
|
165
|
+
lastchlen = _dispch(ret[-1])[1]
|
166
|
+
if w+trunchlen > dispw:
|
167
|
+
ret = ret[:-1]
|
168
|
+
w -= lastchlen
|
169
|
+
ret += trunch # replace final char with ellipsis
|
170
|
+
w += trunchlen
|
122
171
|
break
|
123
172
|
|
173
|
+
w += chlen
|
174
|
+
ret += newc
|
175
|
+
|
124
176
|
return ret, w
|
125
177
|
|
126
178
|
|
@@ -139,38 +191,149 @@ def clipstr(s, dispw, truncator=None, oddspace=None):
|
|
139
191
|
modch='',
|
140
192
|
combch='')
|
141
193
|
|
142
|
-
|
143
|
-
|
194
|
+
|
195
|
+
def clipdraw(scr, y, x, s, attr, w=None, clear=True, literal=False, **kwargs):
|
196
|
+
'''Draw `s` at (y,x)-(y,x+w) with curses `attr`, clipping with ellipsis char.
|
197
|
+
If `clear`, clear whole editing area before displaying.
|
198
|
+
If `literal`, do not interpret internal color code markup.
|
199
|
+
Return width drawn (max of w).
|
200
|
+
'''
|
201
|
+
if not literal:
|
202
|
+
chunks = iterchunks(s, literal=literal)
|
203
|
+
else:
|
204
|
+
chunks = [(dict(link='', cattr=ColorAttr()), s)]
|
205
|
+
|
206
|
+
x = max(0, x)
|
207
|
+
y = max(0, y)
|
208
|
+
assert x >= 0, x
|
209
|
+
assert y >= 0, y
|
210
|
+
|
211
|
+
return clipdraw_chunks(scr, y, x, chunks, attr, w=w, clear=clear, **kwargs)
|
212
|
+
|
213
|
+
|
214
|
+
def clipdraw_chunks(scr, y, x, chunks, cattr:ColorAttr=ColorAttr(), w=None, clear=True, literal=False, **kwargs):
|
215
|
+
'''Draw `chunks` (sequence of (color:str, text:str) as from iterchunks) at (y,x)-(y,x+w) with curses `attr`, clipping with ellipsis char.
|
216
|
+
If `clear`, clear whole editing area before displaying.
|
217
|
+
Return width drawn (max of w).
|
218
|
+
'''
|
144
219
|
if scr:
|
145
|
-
|
220
|
+
windowHeight, windowWidth = scr.getmaxyx()
|
146
221
|
else:
|
147
|
-
windowWidth = 80
|
148
|
-
|
222
|
+
windowHeight, windowWidth = 25, 80
|
223
|
+
totaldispw = 0
|
224
|
+
|
225
|
+
assert isinstance(cattr, ColorAttr), cattr
|
226
|
+
origattr = cattr
|
227
|
+
origw = w
|
228
|
+
clipped = ''
|
229
|
+
link = ''
|
230
|
+
|
231
|
+
if w and clear:
|
232
|
+
actualw = min(w, windowWidth-x-1)
|
233
|
+
if scr:
|
234
|
+
scr.addstr(y, x, disp_column_fill*actualw, cattr.attr) # clear whole area before displaying
|
235
|
+
|
149
236
|
try:
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
237
|
+
for colorstate, chunk in chunks:
|
238
|
+
if isinstance(colorstate, str):
|
239
|
+
cattr = cattr.update(colors.get_color(colorstate))
|
240
|
+
else:
|
241
|
+
cattr = origattr.update(colorstate['cattr'])
|
242
|
+
link = colorstate['link']
|
243
|
+
|
244
|
+
if not chunk:
|
245
|
+
continue
|
246
|
+
|
247
|
+
if origw is None:
|
248
|
+
chunkw = dispwidth(chunk, maxwidth=windowWidth-totaldispw)
|
249
|
+
else:
|
250
|
+
chunkw = origw-totaldispw
|
251
|
+
|
252
|
+
chunkw = min(chunkw, windowWidth-x-1)
|
253
|
+
if chunkw <= 0: # no room anyway
|
254
|
+
return totaldispw
|
255
|
+
if not scr:
|
256
|
+
return totaldispw
|
257
|
+
|
258
|
+
# convert to string just before drawing
|
259
|
+
clipped, dispw = clipstr(chunk, chunkw, **kwargs)
|
260
|
+
|
261
|
+
if y >= 0 and y < windowHeight:
|
262
|
+
scr.addstr(y, x, clipped, cattr.attr)
|
263
|
+
else:
|
264
|
+
if vd.options.debug:
|
265
|
+
raise Exception(f'addstr(y={y} x={x}) out of bounds')
|
266
|
+
|
267
|
+
if link:
|
268
|
+
vd.onMouse(scr, x, y, dispw, 1, BUTTON1_RELEASED=link)
|
269
|
+
|
270
|
+
x += dispw
|
271
|
+
totaldispw += dispw
|
272
|
+
|
273
|
+
if chunkw < dispw:
|
274
|
+
break
|
168
275
|
except Exception as e:
|
169
|
-
|
170
|
-
|
276
|
+
if vd.options.debug:
|
277
|
+
raise
|
278
|
+
# raise type(e)('%s [clip_draw y=%s x=%s dispw=%s w=%s clippedlen=%s]' % (e, y, x, totaldispw, w, len(clipped))
|
171
279
|
# ).with_traceback(sys.exc_info()[2])
|
172
280
|
|
173
|
-
return
|
281
|
+
return totaldispw
|
282
|
+
|
283
|
+
|
284
|
+
def _markdown_to_internal(text):
|
285
|
+
'Return markdown-formatted `text` converted to internal formatting (like `[:color]text[/]`).'
|
286
|
+
text = re.sub(r'`(.*?)`', r'[:code]\1[/]', text)
|
287
|
+
text = re.sub(r'(^#.*?)$', r'[:heading]\1[/]', text)
|
288
|
+
text = re.sub(r'\*\*(.*?)\*\*', r'[:bold]\1[/]', text)
|
289
|
+
text = re.sub(r'\*(.*?)\*', r'[:italic]\1[/]', text)
|
290
|
+
text = re.sub(r'\b_(.*?)_\b', r'[:underline]\1[/]', text)
|
291
|
+
return text
|
292
|
+
|
293
|
+
|
294
|
+
def wraptext(text, width=80, indent=''):
|
295
|
+
'''
|
296
|
+
Word-wrap `text` and yield (formatted_line, textonly_line) for each line of at most `width` characters.
|
297
|
+
Formatting like `[:color]text[/]` is ignored for purposes of computing width, and not included in `textonly_line`.
|
298
|
+
'''
|
299
|
+
import re
|
300
|
+
|
301
|
+
if width <= 0:
|
302
|
+
return
|
303
|
+
|
304
|
+
for line in text.splitlines():
|
305
|
+
if not line:
|
306
|
+
yield '', ''
|
307
|
+
continue
|
308
|
+
|
309
|
+
line = _markdown_to_internal(line)
|
310
|
+
chunks = re.split(internal_markup_re, line)
|
311
|
+
textchunks = [x for x in chunks if not is_vdcode(x)]
|
312
|
+
for linenum, textline in enumerate(textwrap.wrap(''.join(textchunks), width=width, drop_whitespace=False)):
|
313
|
+
txt = textline
|
314
|
+
r = ''
|
315
|
+
while chunks:
|
316
|
+
c = chunks[0]
|
317
|
+
if len(c) > len(txt):
|
318
|
+
r += txt
|
319
|
+
chunks[0] = c[len(txt):]
|
320
|
+
break
|
321
|
+
|
322
|
+
if len(chunks) == 1:
|
323
|
+
r += chunks.pop(0)
|
324
|
+
else:
|
325
|
+
chunks.pop(0)
|
326
|
+
r += txt[:len(c)] + chunks.pop(0)
|
327
|
+
|
328
|
+
txt = txt[len(c):]
|
329
|
+
|
330
|
+
r = r.strip()
|
331
|
+
if linenum > 0:
|
332
|
+
r = indent + r
|
333
|
+
yield r, textline
|
334
|
+
|
335
|
+
for c in chunks:
|
336
|
+
yield c, ''
|
174
337
|
|
175
338
|
|
176
339
|
def clipbox(scr, lines, attr, title=''):
|
@@ -178,6 +341,17 @@ def clipbox(scr, lines, attr, title=''):
|
|
178
341
|
scr.bkgd(attr)
|
179
342
|
scr.box()
|
180
343
|
h, w = scr.getmaxyx()
|
181
|
-
clipdraw(scr, 0, w-len(title)-6, f"| {title} |", attr)
|
182
344
|
for i, line in enumerate(lines):
|
183
345
|
clipdraw(scr, i+1, 2, line, attr)
|
346
|
+
|
347
|
+
clipdraw(scr, 0, w-len(title)-6, f"| {title} |", attr)
|
348
|
+
|
349
|
+
|
350
|
+
vd.addGlobals(clipstr=clipstr,
|
351
|
+
clipdraw=clipdraw,
|
352
|
+
clipdraw_chunks=clipdraw_chunks,
|
353
|
+
clipbox=clipbox,
|
354
|
+
dispwidth=dispwidth,
|
355
|
+
iterchars=iterchars,
|
356
|
+
iterchunks=iterchunks,
|
357
|
+
wraptext=wraptext)
|