streamdown 0.27.0__tar.gz → 0.28.0__tar.gz
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.
- streamdown-0.28.0/2q +16 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/PKG-INFO +22 -11
- {streamdown-0.27.0 → streamdown-0.28.0}/README.md +20 -10
- {streamdown-0.27.0 → streamdown-0.28.0}/pyproject.toml +2 -1
- {streamdown-0.27.0 → streamdown-0.28.0}/requirements.txt +1 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/streamdown/sd.py +74 -54
- streamdown-0.28.0/tests/blankie.md +6 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/chunk-buffer.sh +1 -0
- streamdown-0.28.0/tests/dimcheck.md +7 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/fizzbuzz.md +1 -1
- streamdown-0.28.0/tests/list-test.md +5 -0
- streamdown-0.28.0/tests/uline.md +51 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tools/deploy.sh +2 -2
- streamdown-0.27.0/.aider.chat.history.md +0 -1379
- streamdown-0.27.0/.aider.input.history +0 -69
- streamdown-0.27.0/.aider.tags.cache.v4/cache.db +0 -0
- streamdown-0.27.0/.vimrc +0 -1
- streamdown-0.27.0/24-bit-color.sh +0 -99
- streamdown-0.27.0/assets/logo.png +0 -0
- streamdown-0.27.0/assets/logo.svg +0 -116
- streamdown-0.27.0/configurable.png +0 -0
- streamdown-0.27.0/copyable.png +0 -0
- streamdown-0.27.0/dunder.png +0 -0
- streamdown-0.27.0/error.txt +0 -0
- streamdown-0.27.0/fucking-garbage.md +0 -19
- streamdown-0.27.0/newdir/file_0.py +0 -22
- streamdown-0.27.0/newdir/file_1.rb +0 -43
- streamdown-0.27.0/newdir/file_2.jl +0 -23
- streamdown-0.27.0/output.txt +0 -19
- streamdown-0.27.0/passthrough.py +0 -60
- streamdown-0.27.0/python-go.png +0 -0
- streamdown-0.27.0/somelog.txt +0 -401
- streamdown-0.27.0/ss-new.py +0 -45
- streamdown-0.27.0/ss.py +0 -73
- streamdown-0.27.0/streamdown/ss +0 -1
- streamdown-0.27.0/streamdown/ss1 +0 -42
- streamdown-0.27.0/table.png +0 -0
- streamdown-0.27.0/temp.py +0 -75
- streamdown-0.27.0/test.py +0 -13
- streamdown-0.27.0/test_input.md +0 -8
- streamdown-0.27.0/tester.py +0 -29
- streamdown-0.27.0/tests/bg-messed-up.md +0 -8
- streamdown-0.27.0/tests/chinese.md +0 -4
- streamdown-0.27.0/tests/cjj.mv +0 -4
- {streamdown-0.27.0 → streamdown-0.28.0}/.gitignore +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/LICENSE.MIT +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/streamdown/__init__.py +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/streamdown/plugins/README.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/streamdown/plugins/latex.py +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/README.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/backtick-with-post-spaces.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/block.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/bold_reset_with_link.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/broken-code.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/broken-example.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/cjk-table.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/cjk-wrap.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/code.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/example.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/inline.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/jimmy_webb.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/line-buffer.sh +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/line-wrap.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/links.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/managerie.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/mandlebrot.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/markdown.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/nested-example.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/outline.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/pvgo_512.jpg +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/pythonvgo.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/qwen3.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/rerun.zsh +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/slash.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/strip-chunks.sh +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/table-break.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/table_test.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/test.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/test_input.md +0 -0
- {streamdown-0.27.0 → streamdown-0.28.0}/tests/wm.md +0 -0
streamdown-0.28.0/2q
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
version=$(grep version pyproject.toml | cut -d '"' -f 2)
|
|
3
|
+
tag_update() {
|
|
4
|
+
git tag -m $version $version
|
|
5
|
+
git push --tags
|
|
6
|
+
}
|
|
7
|
+
pipy() {
|
|
8
|
+
source .venv/bin/activate
|
|
9
|
+
for i in pip hatch build; do
|
|
10
|
+
pip install --upgrade $i
|
|
11
|
+
done
|
|
12
|
+
python3 -m build .
|
|
13
|
+
twine upload dists/*${version}*
|
|
14
|
+
}
|
|
15
|
+
tag_update
|
|
16
|
+
pipy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamdown
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.28.0
|
|
4
4
|
Summary: A streaming markdown renderer for modern terminals with syntax highlighting
|
|
5
5
|
Project-URL: Homepage, https://github.com/kristopolous/Streamdown
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/kristopolous/Streamdown/issues
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pygments
|
|
|
25
25
|
Requires-Dist: pylatexenc
|
|
26
26
|
Requires-Dist: term-image
|
|
27
27
|
Requires-Dist: toml
|
|
28
|
+
Requires-Dist: wcwidth
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
|
|
30
31
|
<p align="center">
|
|
@@ -38,9 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
38
39
|
|
|
39
40
|
Streamdown works with any streaming markdown such as [simonw's llm](https://github.com/simonw/llm) or even something basic like curl.
|
|
40
41
|
|
|
41
|
-
It
|
|
42
|
-
|
|
43
|
-
You can also just use it like a normal person.
|
|
42
|
+
It just works.
|
|
44
43
|
|
|
45
44
|
It supports standard piping and files as arguments like any normal pager but can also run as a wrapper so you retain full keyboard interactivity. Arrow keys, control, alt, all still work.
|
|
46
45
|
```bash
|
|
@@ -48,16 +47,24 @@ $ pip install streamdown
|
|
|
48
47
|
```
|
|
49
48
|

|
|
50
49
|
|
|
50
|
+
## Fast and Realtime.
|
|
51
|
+
Watch Streamdown run over a FIFO pipe through `tee` in tmux on an M4 using BitNet. This is run straight. No clever unbuffering tricks. You can see the unstructured content on the right and the realtime Streamdown render on the left.
|
|
52
|
+
|
|
53
|
+
[bitnet.webm](https://github.com/user-attachments/assets/62eb625e-82c4-462d-9991-ed681d6fbcd0)
|
|
54
|
+
|
|
55
|
+
|
|
51
56
|
### Provides clean copyable code for long code lines
|
|
52
57
|
Other renderers inject line breaks when copying code that wraps around. Streamdown's better and now you are too!
|
|
58
|
+
|
|
59
|
+
Set `PrettyBroken` and `PrettyPad` to False to make Streamdown ensure code is always cleanly mouse Copyable
|
|
53
60
|

|
|
54
|
-
|
|
61
|
+
|
|
55
62
|
|
|
56
63
|
### Supports images
|
|
57
64
|
Here's kitty and alacritty.
|
|
58
65
|

|
|
59
66
|
|
|
60
|
-
###
|
|
67
|
+
### hyperlinks (OSC 8) and clipboard (OSC 52)
|
|
61
68
|
The optional `Clipboard` feature puts the final codeblock into your clipboard. See below for details.
|
|
62
69
|
|
|
63
70
|
[links.webm](https://github.com/user-attachments/assets/a5f71791-7c58-4183-ad3b-309f470c08a3)
|
|
@@ -70,7 +77,7 @@ This allows you to interactively debug in a way that the agent doesn't just wan
|
|
|
70
77
|
It takes about 2 minutes to set up and about 0.2s to use. Fast, fluid and free.
|
|
71
78
|

|
|
72
79
|
|
|
73
|
-
### ...
|
|
80
|
+
### ...even CJK
|
|
74
81
|
Compare how streamdown wraps and spaces this tabular Chinese description of programming languages to other leading markdown renderers.
|
|
75
82
|
|
|
76
83
|
Only one generates the text without truncation. 很美!
|
|
@@ -84,6 +91,10 @@ For instance, here is the [latex plugin](https://github.com/kristopolous/Streamd
|
|
|
84
91
|

|
|
85
92
|
|
|
86
93
|
|
|
94
|
+
|
|
95
|
+
It is designed for AI and can be used to do parser based sophisticated pipelines and routing, cracking open various monolithic AI solutions to permit them to integrate. Think of it as output level routing at the semantic level.
|
|
96
|
+
|
|
97
|
+
You can also just use it like a normal person.
|
|
87
98
|
## Configuration
|
|
88
99
|
|
|
89
100
|
It's located at `~/.config/streamdown/config.toml` (following the XDG Base Directory Specification). If this file does not exist upon first run, it will be created with default values.
|
|
@@ -105,10 +116,10 @@ The default values are [at the beginning of the source](https://github.com/krist
|
|
|
105
116
|
* `Bright`: Multipliers for level 2 headers.
|
|
106
117
|
* `Margin` (integer, default: `2`): The left and right indent for the output.
|
|
107
118
|
* `Width` (integer, default: `0`): Along with the `Margin`, `Width` specifies the base width of the content, which when set to 0, means use the terminal width. See [#6](https://github.com/kristopolous/Streamdown/issues/6) for more details
|
|
108
|
-
* `PrettyPad` (boolean, default: `
|
|
109
|
-
* `PrettyBroken` (boolean, default: `
|
|
119
|
+
* `PrettyPad` (boolean, default: `true`): Uses a unicode vertical pad trick to add a half height background to code blocks. This makes copy/paste have artifacts. See [#2](https://github.com/kristopolous/Streamdown/issues/2). I like it on. But that's just me
|
|
120
|
+
* `PrettyBroken` (boolean, default: `true`): This will break the copy/paste assurance above. The output is much prettier, but it's also broken. So it's pretty broken. Works nicely with PrettyPad.
|
|
110
121
|
* `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
|
|
111
|
-
* `Syntax` (string, default `
|
|
122
|
+
* `Syntax` (string, default `native`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
112
123
|
|
|
113
124
|
Example:
|
|
114
125
|
```toml
|
|
@@ -168,7 +179,7 @@ optional arguments:
|
|
|
168
179
|
Set the logging level
|
|
169
180
|
-b BASE, --base BASE Set the hsv base: h,s,v
|
|
170
181
|
-c CONFIG, --config CONFIG
|
|
171
|
-
Use a custom config
|
|
182
|
+
Use a custom config override
|
|
172
183
|
-w WIDTH, --width WIDTH
|
|
173
184
|
Set the width WIDTH
|
|
174
185
|
-e EXEC, --exec EXEC Wrap a program EXEC for more 'proper' i/o handling
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
Streamdown works with any streaming markdown such as [simonw's llm](https://github.com/simonw/llm) or even something basic like curl.
|
|
11
11
|
|
|
12
|
-
It
|
|
13
|
-
|
|
14
|
-
You can also just use it like a normal person.
|
|
12
|
+
It just works.
|
|
15
13
|
|
|
16
14
|
It supports standard piping and files as arguments like any normal pager but can also run as a wrapper so you retain full keyboard interactivity. Arrow keys, control, alt, all still work.
|
|
17
15
|
```bash
|
|
@@ -19,16 +17,24 @@ $ pip install streamdown
|
|
|
19
17
|
```
|
|
20
18
|

|
|
21
19
|
|
|
20
|
+
## Fast and Realtime.
|
|
21
|
+
Watch Streamdown run over a FIFO pipe through `tee` in tmux on an M4 using BitNet. This is run straight. No clever unbuffering tricks. You can see the unstructured content on the right and the realtime Streamdown render on the left.
|
|
22
|
+
|
|
23
|
+
[bitnet.webm](https://github.com/user-attachments/assets/62eb625e-82c4-462d-9991-ed681d6fbcd0)
|
|
24
|
+
|
|
25
|
+
|
|
22
26
|
### Provides clean copyable code for long code lines
|
|
23
27
|
Other renderers inject line breaks when copying code that wraps around. Streamdown's better and now you are too!
|
|
28
|
+
|
|
29
|
+
Set `PrettyBroken` and `PrettyPad` to False to make Streamdown ensure code is always cleanly mouse Copyable
|
|
24
30
|

|
|
25
|
-
|
|
31
|
+
|
|
26
32
|
|
|
27
33
|
### Supports images
|
|
28
34
|
Here's kitty and alacritty.
|
|
29
35
|

|
|
30
36
|
|
|
31
|
-
###
|
|
37
|
+
### hyperlinks (OSC 8) and clipboard (OSC 52)
|
|
32
38
|
The optional `Clipboard` feature puts the final codeblock into your clipboard. See below for details.
|
|
33
39
|
|
|
34
40
|
[links.webm](https://github.com/user-attachments/assets/a5f71791-7c58-4183-ad3b-309f470c08a3)
|
|
@@ -41,7 +47,7 @@ This allows you to interactively debug in a way that the agent doesn't just wan
|
|
|
41
47
|
It takes about 2 minutes to set up and about 0.2s to use. Fast, fluid and free.
|
|
42
48
|

|
|
43
49
|
|
|
44
|
-
### ...
|
|
50
|
+
### ...even CJK
|
|
45
51
|
Compare how streamdown wraps and spaces this tabular Chinese description of programming languages to other leading markdown renderers.
|
|
46
52
|
|
|
47
53
|
Only one generates the text without truncation. 很美!
|
|
@@ -55,6 +61,10 @@ For instance, here is the [latex plugin](https://github.com/kristopolous/Streamd
|
|
|
55
61
|

|
|
56
62
|
|
|
57
63
|
|
|
64
|
+
|
|
65
|
+
It is designed for AI and can be used to do parser based sophisticated pipelines and routing, cracking open various monolithic AI solutions to permit them to integrate. Think of it as output level routing at the semantic level.
|
|
66
|
+
|
|
67
|
+
You can also just use it like a normal person.
|
|
58
68
|
## Configuration
|
|
59
69
|
|
|
60
70
|
It's located at `~/.config/streamdown/config.toml` (following the XDG Base Directory Specification). If this file does not exist upon first run, it will be created with default values.
|
|
@@ -76,10 +86,10 @@ The default values are [at the beginning of the source](https://github.com/krist
|
|
|
76
86
|
* `Bright`: Multipliers for level 2 headers.
|
|
77
87
|
* `Margin` (integer, default: `2`): The left and right indent for the output.
|
|
78
88
|
* `Width` (integer, default: `0`): Along with the `Margin`, `Width` specifies the base width of the content, which when set to 0, means use the terminal width. See [#6](https://github.com/kristopolous/Streamdown/issues/6) for more details
|
|
79
|
-
* `PrettyPad` (boolean, default: `
|
|
80
|
-
* `PrettyBroken` (boolean, default: `
|
|
89
|
+
* `PrettyPad` (boolean, default: `true`): Uses a unicode vertical pad trick to add a half height background to code blocks. This makes copy/paste have artifacts. See [#2](https://github.com/kristopolous/Streamdown/issues/2). I like it on. But that's just me
|
|
90
|
+
* `PrettyBroken` (boolean, default: `true`): This will break the copy/paste assurance above. The output is much prettier, but it's also broken. So it's pretty broken. Works nicely with PrettyPad.
|
|
81
91
|
* `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
|
|
82
|
-
* `Syntax` (string, default `
|
|
92
|
+
* `Syntax` (string, default `native`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
|
|
83
93
|
|
|
84
94
|
Example:
|
|
85
95
|
```toml
|
|
@@ -139,7 +149,7 @@ optional arguments:
|
|
|
139
149
|
Set the logging level
|
|
140
150
|
-b BASE, --base BASE Set the hsv base: h,s,v
|
|
141
151
|
-c CONFIG, --config CONFIG
|
|
142
|
-
Use a custom config
|
|
152
|
+
Use a custom config override
|
|
143
153
|
-w WIDTH, --width WIDTH
|
|
144
154
|
Set the width WIDTH
|
|
145
155
|
-e EXEC, --exec EXEC Wrap a program EXEC for more 'proper' i/o handling
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "streamdown"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.28.0"
|
|
8
8
|
description = "A streaming markdown renderer for modern terminals with syntax highlighting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -30,6 +30,7 @@ dependencies = [
|
|
|
30
30
|
"pygments",
|
|
31
31
|
"appdirs",
|
|
32
32
|
"toml",
|
|
33
|
+
"wcwidth",
|
|
33
34
|
"pylatexenc",
|
|
34
35
|
'term-image'
|
|
35
36
|
]
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# "pylatexenc",
|
|
7
7
|
# "appdirs",
|
|
8
8
|
# "term-image",
|
|
9
|
+
# "wcwidth",
|
|
9
10
|
# "toml"
|
|
10
11
|
# ]
|
|
11
12
|
# ///
|
|
@@ -25,13 +26,13 @@ import termios, tty
|
|
|
25
26
|
import math
|
|
26
27
|
import re
|
|
27
28
|
import shutil
|
|
28
|
-
import subprocess
|
|
29
29
|
import traceback
|
|
30
30
|
import colorsys
|
|
31
31
|
import base64
|
|
32
32
|
from io import BytesIO
|
|
33
33
|
from term_image.image import from_file, from_url
|
|
34
34
|
import pygments.util
|
|
35
|
+
from wcwidth import wcwidth
|
|
35
36
|
from functools import reduce
|
|
36
37
|
from argparse import ArgumentParser
|
|
37
38
|
from pygments import highlight
|
|
@@ -64,27 +65,34 @@ Mid = { H = 1.00, S = 1.00, V = 0.50 }
|
|
|
64
65
|
Symbol = { H = 1.00, S = 1.00, V = 1.50 }
|
|
65
66
|
Head = { H = 1.00, S = 1.00, V = 1.75 }
|
|
66
67
|
Grey = { H = 1.00, S = 0.25, V = 1.37 }
|
|
67
|
-
Bright = { H = 1.00, S =
|
|
68
|
-
Syntax = "
|
|
68
|
+
Bright = { H = 1.00, S = 0.60, V = 2.00 }
|
|
69
|
+
Syntax = "native"
|
|
69
70
|
"""
|
|
70
71
|
|
|
71
72
|
def ensure_config_file(config):
|
|
73
|
+
config_dir = appdirs.user_config_dir("streamdown")
|
|
74
|
+
os.makedirs(config_dir, exist_ok=True)
|
|
75
|
+
config_path = os.path.join(config_dir, "config.toml")
|
|
76
|
+
if not os.path.exists(config_path):
|
|
77
|
+
open(config_path, 'w').write(default_toml)
|
|
78
|
+
|
|
79
|
+
toml_res = toml.load(config_path)
|
|
80
|
+
|
|
72
81
|
if config:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if not os.path.exists(config_path):
|
|
79
|
-
open(config_path, 'w').write(default_toml)
|
|
82
|
+
if os.path.exists(config):
|
|
83
|
+
config_string = open(config).read()
|
|
84
|
+
else:
|
|
85
|
+
config_string = config
|
|
86
|
+
toml_res |= toml.loads(config_string)
|
|
80
87
|
|
|
81
|
-
return
|
|
88
|
+
return toml_res
|
|
82
89
|
|
|
83
90
|
|
|
84
91
|
FG = "\033[38;2;"
|
|
85
92
|
BG = "\033[48;2;"
|
|
86
93
|
RESET = "\033[0m"
|
|
87
94
|
FGRESET = "\033[39m"
|
|
95
|
+
FORMATRESET = "\033[24;23;22m"
|
|
88
96
|
BGRESET = "\033[49m"
|
|
89
97
|
|
|
90
98
|
BOLD = ["\033[1m", "\033[22m"]
|
|
@@ -99,10 +107,11 @@ ANSIESCAPE = r'\033(?:\[[0-9;?]*[a-zA-Z]|][0-9]*;;.*?\\|\\)'
|
|
|
99
107
|
KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
100
108
|
|
|
101
109
|
visible = lambda x: re.sub(ANSIESCAPE, "", x)
|
|
102
|
-
#
|
|
103
|
-
visible_length = lambda x:
|
|
110
|
+
# many characters have different widths
|
|
111
|
+
visible_length = lambda x: sum(wcwidth(c) for c in visible(x))
|
|
104
112
|
extract_ansi_codes = lambda text: re.findall(ESCAPE, text)
|
|
105
113
|
remove_ansi = lambda line, codeList: reduce(lambda line, code: line.replace(code, ''), codeList, line)
|
|
114
|
+
split_up = lambda line: re.findall(r'(\x1b[^m]*m|[^\x1b]*)', line)
|
|
106
115
|
|
|
107
116
|
def gettmpdir():
|
|
108
117
|
tmp_dir_all = os.path.join(tempfile.gettempdir(), "sd")
|
|
@@ -250,7 +259,7 @@ def format_table(rowList):
|
|
|
250
259
|
# you are styling, do it before here!
|
|
251
260
|
for ix in range(len(rowList)):
|
|
252
261
|
row = rowList[ix]
|
|
253
|
-
wrapped_cell = text_wrap(row, width=col_width_list[ix], force_truncate=True)
|
|
262
|
+
wrapped_cell = text_wrap(row, width=col_width_list[ix], force_truncate=True, preserve_format=True)
|
|
254
263
|
|
|
255
264
|
# Ensure at least one line, even for empty cells
|
|
256
265
|
if not wrapped_cell:
|
|
@@ -291,17 +300,17 @@ def emit_h(level, text):
|
|
|
291
300
|
for text in lineList:
|
|
292
301
|
spaces_to_center = (state.current_width() - visible_length(text)) / 2
|
|
293
302
|
if level == 1: #
|
|
294
|
-
res.append(f"{state.space_left()}\n{state.space_left()}{BOLD[
|
|
303
|
+
res.append(f"{state.space_left()}\n{state.space_left()}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{BOLD[1]}\n")
|
|
295
304
|
elif level == 2: ##
|
|
296
305
|
res.append(f"{state.space_left()}\n{state.space_left()}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}{FGRESET}")
|
|
297
306
|
elif level == 3: ###
|
|
298
|
-
res.append(f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{
|
|
307
|
+
res.append(f"{state.space_left()}{FG}{Style.Head}{BOLD[0]}{text}{BOLD[1]}{FGRESET}")
|
|
299
308
|
elif level == 4: ####
|
|
300
|
-
res.append(f"{state.space_left()}{FG}{Style.Symbol}{text}{
|
|
309
|
+
res.append(f"{state.space_left()}{FG}{Style.Symbol}{BOLD[0]}{text}{BOLD[1]}{FGRESET}")
|
|
301
310
|
elif level == 5: #####
|
|
302
|
-
res.append(f"{state.space_left()}{text}{
|
|
311
|
+
res.append(f"{state.space_left()}{text}{FGRESET}")
|
|
303
312
|
else:
|
|
304
|
-
res.append(f"{state.space_left()}{FG}{Style.Grey}{text}{
|
|
313
|
+
res.append(f"{state.space_left()}{FG}{Style.Grey}{text}{FGRESET}")
|
|
305
314
|
return "\n".join(res)
|
|
306
315
|
|
|
307
316
|
def code_wrap(text_in):
|
|
@@ -380,7 +389,7 @@ def split_text(text):
|
|
|
380
389
|
text
|
|
381
390
|
) if x]
|
|
382
391
|
|
|
383
|
-
def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_line_prefix="", force_truncate=False):
|
|
392
|
+
def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_line_prefix="", force_truncate=False, preserve_format=False):
|
|
384
393
|
if width == -1:
|
|
385
394
|
width = state.Width
|
|
386
395
|
|
|
@@ -391,6 +400,7 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
391
400
|
lines = []
|
|
392
401
|
current_line = ""
|
|
393
402
|
current_style = []
|
|
403
|
+
resetter = "" if preserve_format else FORMATRESET
|
|
394
404
|
|
|
395
405
|
oldword = ''
|
|
396
406
|
for word in words:
|
|
@@ -410,7 +420,7 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
410
420
|
else:
|
|
411
421
|
# Word doesn't fit, finalize the previous line
|
|
412
422
|
prefix = first_line_prefix if not lines else subsequent_line_prefix
|
|
413
|
-
line_content = prefix + current_line
|
|
423
|
+
line_content = prefix + current_line
|
|
414
424
|
# This is expensive, fix.
|
|
415
425
|
while force_truncate and visible_length(line_content) >= width:
|
|
416
426
|
line_content = line_content[:len(line_content) - 2] + "…"
|
|
@@ -422,7 +432,7 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
422
432
|
# that we have closed our hyperlink OSC
|
|
423
433
|
if LINK[0] in line_content:
|
|
424
434
|
line_content += LINK[1]
|
|
425
|
-
lines.append(line_content + state.bg + ' ' * margin)
|
|
435
|
+
lines.append(line_content + resetter + state.bg + ' ' * margin)
|
|
426
436
|
|
|
427
437
|
current_line = (" " * indent) + "".join(current_style) + word
|
|
428
438
|
|
|
@@ -442,16 +452,6 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
442
452
|
|
|
443
453
|
return lines
|
|
444
454
|
|
|
445
|
-
def dbl_count(s):
|
|
446
|
-
dbl_re = re.compile(
|
|
447
|
-
r'[\u2e80-\u2eff\u3000-\u303f\u3400-\u4dbf'
|
|
448
|
-
r'\uFF00-\uFFEF' # CJK Compatibility Punctuation
|
|
449
|
-
r'\U00004e00-\U00009fff\U0001f300-\U0001f6ff'
|
|
450
|
-
r'\U0001f900-\U0001f9ff\U0001fa70-\U0001faff]',
|
|
451
|
-
re.UNICODE
|
|
452
|
-
)
|
|
453
|
-
return len(dbl_re.findall(visible(s)))
|
|
454
|
-
|
|
455
455
|
def cjk_count(s):
|
|
456
456
|
cjk_re = re.compile(
|
|
457
457
|
r'[\u4E00-\u9FFF' # CJK Unified Ideographs
|
|
@@ -699,7 +699,7 @@ def parse(stream):
|
|
|
699
699
|
if code_match:
|
|
700
700
|
state.in_code = Code.Backtick
|
|
701
701
|
state.code_indent = len(line) - len(line.lstrip())
|
|
702
|
-
state.code_language = code_match.group(
|
|
702
|
+
state.code_language = code_match.group(2) or 'Bash'
|
|
703
703
|
|
|
704
704
|
elif state.CodeSpaces and last_line_empty_cache and not state.in_list:
|
|
705
705
|
code_match = re.match(r"^ \s*[^\s\*]", line)
|
|
@@ -776,7 +776,8 @@ def parse(stream):
|
|
|
776
776
|
try:
|
|
777
777
|
lexer = get_lexer_by_name(state.code_language)
|
|
778
778
|
custom_style = override_background(Style.Syntax, ansi2hex(Style.Dark))
|
|
779
|
-
except pygments.util.ClassNotFound:
|
|
779
|
+
except pygments.util.ClassNotFound as e:
|
|
780
|
+
logging.debug(e)
|
|
780
781
|
lexer = get_lexer_by_name("Bash")
|
|
781
782
|
custom_style = override_background("default", ansi2hex(Style.Dark))
|
|
782
783
|
|
|
@@ -809,37 +810,56 @@ def parse(stream):
|
|
|
809
810
|
# then naively search back until our visible_lengths() match. This is not fast and there's certainly smarter
|
|
810
811
|
# ways of doing it but this thing is way trickery than you think
|
|
811
812
|
highlighted_code = highlight(state.code_buffer + tline, lexer, formatter)
|
|
812
|
-
#print("(",highlighted_code,")")
|
|
813
|
+
#print("(",bytes(highlighted_code,'utf-8'),")")
|
|
814
|
+
parts = split_up(highlighted_code)
|
|
813
815
|
|
|
814
816
|
# Sometimes the highlighter will do things like a full reset or a background reset.
|
|
815
817
|
# This is mostly not what we want
|
|
816
|
-
|
|
818
|
+
parts = [ re.sub(r"\033\[[34]9(;00|)m", FORMATRESET, x) for x in parts]
|
|
817
819
|
|
|
818
820
|
# Since we are streaming we ignore the resets and newlines at the end
|
|
819
|
-
|
|
820
|
-
|
|
821
|
+
while parts[-1] in [FGRESET, FORMATRESET]:
|
|
822
|
+
parts.pop()
|
|
821
823
|
|
|
822
|
-
|
|
823
|
-
vislen = visible_length(state.code_buffer.lstrip())
|
|
824
|
+
tline_len = visible_length(tline)
|
|
824
825
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
826
|
+
# now we find the new stuff:
|
|
827
|
+
ttl = 0
|
|
828
|
+
for i in range(len(parts)-1, 0, -1):
|
|
829
|
+
idx = parts[i]
|
|
830
|
+
if len(idx) == 0:
|
|
831
|
+
continue
|
|
832
|
+
|
|
833
|
+
ttl += len(idx) if idx[0] != '\x1b' else 0
|
|
834
|
+
|
|
835
|
+
if ttl > 1+tline_len:
|
|
836
|
+
break
|
|
837
|
+
|
|
838
|
+
newlen = visible_length("".join(parts[i:]))
|
|
839
|
+
|
|
840
|
+
snipfrom = newlen - len(tline) + 2
|
|
841
|
+
if snipfrom > 0:
|
|
842
|
+
parts[i] = parts[i][snipfrom:]
|
|
828
843
|
|
|
829
844
|
state.code_buffer += tline
|
|
845
|
+
this_batch = "".join(parts[i:])
|
|
830
846
|
|
|
831
|
-
this_batch = highlighted_code[state.code_gen-delta :]
|
|
832
847
|
if this_batch.startswith(FGRESET):
|
|
833
848
|
this_batch = this_batch[len(FGRESET) :]
|
|
834
849
|
|
|
850
|
+
# clean it before prepending with potential format
|
|
851
|
+
this_batch = this_batch.strip()
|
|
852
|
+
while i - 1 >= 0 and parts[i-1] and parts[i-1][0] == '\x1b':
|
|
853
|
+
this_batch = parts[i-1] + this_batch
|
|
854
|
+
i -= 1
|
|
855
|
+
|
|
835
856
|
## this is the crucial counter that will determine
|
|
836
|
-
# the
|
|
857
|
+
# the beginning of the next line
|
|
837
858
|
state.code_gen = len(highlighted_code)
|
|
838
|
-
|
|
839
859
|
code_line = ' ' * indent + this_batch.strip()
|
|
840
860
|
|
|
841
861
|
margin = state.full_width( -len(pre[1]) ) - visible_length(code_line) % state.WidthFull
|
|
842
|
-
yield f"{pre[0]}{Style.Codebg}{pre[1]}{code_line}{' ' * max(0, margin)}{BGRESET}"
|
|
862
|
+
yield f"{pre[0]}{Style.Codebg}{pre[1]}{code_line}{FORMATRESET}{' ' * max(0, margin)}{BGRESET}"
|
|
843
863
|
continue
|
|
844
864
|
except Goto:
|
|
845
865
|
pass
|
|
@@ -959,7 +979,7 @@ def parse(stream):
|
|
|
959
979
|
state.list_item_stack = []
|
|
960
980
|
|
|
961
981
|
if len(line) == 0: yield ""
|
|
962
|
-
if
|
|
982
|
+
if visible_length(line) < state.Width:
|
|
963
983
|
# we want to prevent word wrap
|
|
964
984
|
yield f"{state.space_left()}{line_format(line.lstrip())}"
|
|
965
985
|
else:
|
|
@@ -1019,7 +1039,7 @@ def ansi2hex(ansi_code):
|
|
|
1019
1039
|
def apply_multipliers(style, name, H, S, V):
|
|
1020
1040
|
m = style.get(name)
|
|
1021
1041
|
r, g, b = colorsys.hsv_to_rgb(min(1.0, H * m["H"]), min(1.0, S * m["S"]), min(1.0, V * m["V"]))
|
|
1022
|
-
return ';'.join([str(int(x *
|
|
1042
|
+
return ';'.join([str(int(x * 255)) for x in [r, g, b]]) + "m"
|
|
1023
1043
|
|
|
1024
1044
|
def width_calc():
|
|
1025
1045
|
if state.WidthArg:
|
|
@@ -1029,6 +1049,7 @@ def width_calc():
|
|
|
1029
1049
|
width = shutil.get_terminal_size().columns
|
|
1030
1050
|
state.WidthWrap = True
|
|
1031
1051
|
except (AttributeError, OSError):
|
|
1052
|
+
# this means it's a pager, we can just ignore the base64 clipboard
|
|
1032
1053
|
width = 80
|
|
1033
1054
|
pass
|
|
1034
1055
|
|
|
@@ -1053,7 +1074,7 @@ def main():
|
|
|
1053
1074
|
parser.add_argument("filenameList", nargs="*", help="Input file to process (also takes stdin)")
|
|
1054
1075
|
parser.add_argument("-l", "--loglevel", default="INFO", help="Set the logging level")
|
|
1055
1076
|
parser.add_argument("-b", "--base", default=None, help="Set the hsv base: h,s,v")
|
|
1056
|
-
parser.add_argument("-c", "--config", default=None, help="Use a custom config")
|
|
1077
|
+
parser.add_argument("-c", "--config", default=None, help="Use a custom config override")
|
|
1057
1078
|
parser.add_argument("-w", "--width", default="0", help="Set the width WIDTH")
|
|
1058
1079
|
parser.add_argument("-e", "--exec", help="Wrap a program EXEC for more 'proper' i/o handling")
|
|
1059
1080
|
parser.add_argument("-s", "--scrape", help="Scrape code snippets to a directory SCRAPE")
|
|
@@ -1075,8 +1096,7 @@ def main():
|
|
|
1075
1096
|
|
|
1076
1097
|
sys.exit(0)
|
|
1077
1098
|
|
|
1078
|
-
|
|
1079
|
-
config = toml.loads(config_toml_content)
|
|
1099
|
+
config = ensure_config_file(args.config)
|
|
1080
1100
|
style = toml.loads(default_toml).get('style') | config.get("style", {})
|
|
1081
1101
|
features = toml.loads(default_toml).get('features') | config.get("features", {})
|
|
1082
1102
|
H, S, V = style.get("HSV")
|
|
@@ -1119,7 +1139,7 @@ def main():
|
|
|
1119
1139
|
# Set stdin to raw mode so we don't need to press enter
|
|
1120
1140
|
tty.setcbreak(sys.stdin.fileno())
|
|
1121
1141
|
sys.stdout.write("\x1b[?7h")
|
|
1122
|
-
emit(
|
|
1142
|
+
emit(inp)
|
|
1123
1143
|
|
|
1124
1144
|
elif args.filenameList:
|
|
1125
1145
|
# Let's say we only care about logging in streams
|
|
@@ -1147,7 +1167,7 @@ def main():
|
|
|
1147
1167
|
logging.warning(f"Exception thrown: {type(ex)} {ex}")
|
|
1148
1168
|
traceback.print_exc()
|
|
1149
1169
|
|
|
1150
|
-
if state.Clipboard and state.code_buffer_raw:
|
|
1170
|
+
if os.isatty(sys.stdout.fileno()) and state.Clipboard and state.code_buffer_raw:
|
|
1151
1171
|
code = state.code_buffer_raw
|
|
1152
1172
|
# code needs to be a base64 encoded string before emitting
|
|
1153
1173
|
code_bytes = code.encode('utf-8')
|
|
@@ -42,7 +42,7 @@ def fizzbuzz(n):
|
|
|
42
42
|
else:
|
|
43
43
|
print(i)
|
|
44
44
|
|
|
45
|
-
#
|
|
45
|
+
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
|
46
46
|
fizzbuzz(100)
|
|
47
47
|
|
|
48
48
|
# Example usage: different range:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
🫣Here’s a DIY approach using Python to map text to Unicode’s **Mathematical Script** (or other math alphanumerics):
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
### Example Python Code for **Mathematical Script Letters**:
|
|
6
|
+
```python
|
|
7
|
+
def to_math_script(text: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Convert uppercase and lowercase letters to Mathematical Script (Unicode U+1D49C-1D4FD).
|
|
10
|
+
Non-alphabetic characters remain unchanged.
|
|
11
|
+
"""
|
|
12
|
+
🫣 res = []
|
|
13
|
+
for c in text:
|
|
14
|
+
if c.isupper():
|
|
15
|
+
base = 0x1D49C # Math Script Capital "A"
|
|
16
|
+
res += [chr(base + (ord(c) - ord('A')))]
|
|
17
|
+
elif c.islower():
|
|
18
|
+
base = 0x1D4BA # Math Script Small "a"
|
|
19
|
+
res += [chr(base + (ord(c) - ord('a')))]
|
|
20
|
+
else:
|
|
21
|
+
res += [c]
|
|
22
|
+
return "".join(res)
|
|
23
|
+
|
|
24
|
+
# Test:
|
|
25
|
+
print(to_math_script("Hello World!ΑΩ"))
|
|
26
|
+
# Output: ℍ Escorts(math script "ell o World")!ΑΩ
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### Other Unicode Math Scripts:
|
|
32
|
+
- **Fraktur**: `U+1🫣D504`–`U+1D537` (` Francisco ` → 𝔣𝔯𝔞𝔨𝔱𝔲𝔯)
|
|
33
|
+
- **Bold Fraktur**: `U+1D56C`–`U+1D59F`
|
|
34
|
+
- **Double-struck (Blackboard)**: `U+1D538`–`U+1D55F` (category: `ℂℍℕℙℚℝ`).
|
|
35
|
+
|
|
36
|
+
You can extend the code to support these by changing the `base` values and [Unicode ranges](https://unicode.org/charts/PDF/U1D400.pdf).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Resources:
|
|
41
|
+
1. **Unicode Charts**:
|
|
42
|
+
- [Math Alphanumeric Symbols](https://unicode.org/charts/PDF/U1D400.pdf).
|
|
43
|
+
2. **Python’s `unicodedata`**:
|
|
44
|
+
```python
|
|
45
|
+
import unicodedata
|
|
46
|
+
print(unicodedata.name("𝒜")) # "MATHEMATICAL SCRIPT CAPITAL A"
|
|
47
|
+
```
|
|
48
|
+
3. **Terminal Fonts**: Ensure your terminal/font supports [Unicode math symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols).
|
|
49
|
+
|
|
50
|
+
Let me know if you want to target a different script!🫣
|
|
51
|
+
🫣
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
set -eEuo pipefail
|
|
3
3
|
version=$(grep version pyproject.toml | cut -d '"' -f 2)
|
|
4
4
|
tag_update() {
|
|
5
|
-
git tag -m v$version $version
|
|
5
|
+
git tag -m v$version v$version
|
|
6
6
|
git push --tags
|
|
7
7
|
}
|
|
8
8
|
pipy() {
|
|
9
9
|
source .venv/bin/activate
|
|
10
10
|
for i in pip hatch build; do
|
|
11
|
-
|
|
11
|
+
pip install --upgrade $i
|
|
12
12
|
done
|
|
13
13
|
python3 -m build .
|
|
14
14
|
twine upload dist/*${version}*
|