streamdown 0.16.0__tar.gz → 0.18.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.
Files changed (73) hide show
  1. {streamdown-0.16.0 → streamdown-0.18.0}/PKG-INFO +19 -16
  2. {streamdown-0.16.0 → streamdown-0.18.0}/README.md +18 -15
  3. {streamdown-0.16.0 → streamdown-0.18.0}/pyproject.toml +1 -1
  4. streamdown-0.18.0/requirements.txt +5 -0
  5. streamdown-0.18.0/ss-new.py +45 -0
  6. streamdown-0.18.0/ss.py +69 -0
  7. {streamdown-0.16.0 → streamdown-0.18.0}/streamdown/sd.py +145 -74
  8. streamdown-0.18.0/streamdown/ss +1 -0
  9. streamdown-0.18.0/streamdown/ss1 +42 -0
  10. streamdown-0.18.0/tests/backtick-with-post-spaces.md +0 -0
  11. streamdown-0.18.0/tests/block.md +21 -0
  12. streamdown-0.18.0/tests/bold_reset_with_link.md +1 -0
  13. streamdown-0.18.0/tests/broken-example.md +14 -0
  14. streamdown-0.18.0/tests/chinese.md +4 -0
  15. {streamdown-0.16.0 → streamdown-0.18.0}/tests/chunk-buffer.sh +1 -1
  16. streamdown-0.18.0/tests/cjk-wrap.md +150 -0
  17. streamdown-0.18.0/tests/escape.md +7 -0
  18. streamdown-0.18.0/tests/jimmy_webb.md +1458 -0
  19. streamdown-0.18.0/tests/managerie.md +328 -0
  20. {streamdown-0.16.0 → streamdown-0.18.0}/tests/markdown.md +2 -2
  21. {streamdown-0.16.0 → streamdown-0.18.0}/tests/outline.md +2 -0
  22. streamdown-0.18.0/tests/strip-chunks.sh +2 -0
  23. {streamdown-0.16.0 → streamdown-0.18.0}/tests/table_test.md +0 -3
  24. streamdown-0.16.0/streamdown/scrape/file_0.py +0 -22
  25. streamdown-0.16.0/streamdown/scrape/file_1.js +0 -27
  26. streamdown-0.16.0/streamdown/scrape/file_2.cpp +0 -23
  27. streamdown-0.16.0/streamdown/tt.mds +0 -11
  28. streamdown-0.16.0/tests/block.md +0 -10
  29. streamdown-0.16.0/tests/line.md +0 -13
  30. streamdown-0.16.0/tests/longer-example.md +0 -18
  31. streamdown-0.16.0/tests/new.md +0 -3
  32. streamdown-0.16.0/tests/sd.log +0 -0
  33. streamdown-0.16.0/tests/table.md +0 -4
  34. streamdown-0.16.0/tests/white-space-code.md +0 -19
  35. {streamdown-0.16.0 → streamdown-0.18.0}/.gitignore +0 -0
  36. {streamdown-0.16.0 → streamdown-0.18.0}/.vimrc +0 -0
  37. {streamdown-0.16.0 → streamdown-0.18.0}/24-bit-color.sh +0 -0
  38. {streamdown-0.16.0 → streamdown-0.18.0}/LICENSE.MIT +0 -0
  39. {streamdown-0.16.0 → streamdown-0.18.0}/assets/logo.png +0 -0
  40. {streamdown-0.16.0 → streamdown-0.18.0}/assets/logo.svg +0 -0
  41. {streamdown-0.16.0 → streamdown-0.18.0}/configurable.png +0 -0
  42. {streamdown-0.16.0 → streamdown-0.18.0}/copyable.png +0 -0
  43. {streamdown-0.16.0 → streamdown-0.18.0}/dunder.png +0 -0
  44. {streamdown-0.16.0 → streamdown-0.18.0}/error.txt +0 -0
  45. {streamdown-0.16.0 → streamdown-0.18.0}/newdir/file_0.py +0 -0
  46. {streamdown-0.16.0 → streamdown-0.18.0}/newdir/file_1.rb +0 -0
  47. {streamdown-0.16.0 → streamdown-0.18.0}/newdir/file_2.jl +0 -0
  48. {streamdown-0.16.0 → streamdown-0.18.0}/passthrough.py +0 -0
  49. {streamdown-0.16.0 → streamdown-0.18.0}/python-go.png +0 -0
  50. {streamdown-0.16.0 → streamdown-0.18.0}/somelog.txt +0 -0
  51. {streamdown-0.16.0 → streamdown-0.18.0}/streamdown/__init__.py +0 -0
  52. {streamdown-0.16.0 → streamdown-0.18.0}/streamdown/plugins/README.md +0 -0
  53. {streamdown-0.16.0 → streamdown-0.18.0}/streamdown/plugins/latex.py +0 -0
  54. {streamdown-0.16.0 → streamdown-0.18.0}/table.png +0 -0
  55. {streamdown-0.16.0 → streamdown-0.18.0}/temp.py +0 -0
  56. {streamdown-0.16.0 → streamdown-0.18.0}/test.py +0 -0
  57. {streamdown-0.16.0 → streamdown-0.18.0}/test_input.md +0 -0
  58. {streamdown-0.16.0 → streamdown-0.18.0}/tests/README.md +0 -0
  59. {streamdown-0.16.0 → streamdown-0.18.0}/tests/code.md +0 -0
  60. {streamdown-0.16.0 → streamdown-0.18.0}/tests/example.md +0 -0
  61. {streamdown-0.16.0 → streamdown-0.18.0}/tests/fizzbuzz.md +0 -0
  62. {streamdown-0.16.0 → streamdown-0.18.0}/tests/inline.md +0 -0
  63. {streamdown-0.16.0 → streamdown-0.18.0}/tests/line-buffer.sh +0 -0
  64. {streamdown-0.16.0 → streamdown-0.18.0}/tests/line-wrap.md +0 -0
  65. {streamdown-0.16.0 → streamdown-0.18.0}/tests/links.md +0 -0
  66. {streamdown-0.16.0 → streamdown-0.18.0}/tests/mandlebrot.md +0 -0
  67. {streamdown-0.16.0 → streamdown-0.18.0}/tests/nested-example.md +0 -0
  68. {streamdown-0.16.0 → streamdown-0.18.0}/tests/pvgo_512.jpg +0 -0
  69. {streamdown-0.16.0 → streamdown-0.18.0}/tests/pythonvgo.md +0 -0
  70. {streamdown-0.16.0 → streamdown-0.18.0}/tests/table-break.md +0 -0
  71. {streamdown-0.16.0 → streamdown-0.18.0}/tests/test.md +0 -0
  72. {streamdown-0.16.0 → streamdown-0.18.0}/tests/test_input.md +0 -0
  73. {streamdown-0.16.0 → streamdown-0.18.0}/tests/wm.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamdown
3
- Version: 0.16.0
3
+ Version: 0.18.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
@@ -31,18 +31,22 @@ Description-Content-Type: text/markdown
31
31
  <img src=https://github.com/user-attachments/assets/0468eac0-2a00-4e98-82ca-09e6ac679357/>
32
32
  <br/>
33
33
  <a href=https://pypi.org/project/streamdown><img src=https://badge.fury.io/py/streamdown.svg/></a>
34
+ <br/><strong>Terminal streaming markdown that rocks</strong>
35
+
34
36
  </p>
35
37
 
36
- The streaming markdown renderer for the terminal that rocks!
37
- Streamdown works with [simonw's llm](https://github.com/simonw/llm) along with any other streaming markdown. You even get full readline and keyboard navigation support.
38
+
39
+ Streamdown works with [simonw's llm](https://github.com/simonw/llm) along with any other streaming markdown, even something basic like curl.
40
+ It supports standard piping like any normal pager and a clean `execvp` option for robustly wrapping around interactive programs with readline or their own ANSI stuff to manage.
38
41
  ```bash
39
42
  $ pip install streamdown
40
43
  ```
41
44
  ![Streamdown is Amazing](https://github.com/user-attachments/assets/268cb340-78cc-4df0-a773-c5ac95eceeeb)
42
45
 
43
46
  ### Provides clean copyable code for long code lines
44
- You may have noticed *inferior* renderers inject line breaks when copying code that wraps around. We're better and now you are too!
47
+ Some *inferior* renderers inject line breaks when copying code that wraps around. We're better and now you are too!
45
48
  ![Handle That Mandle](https://github.com/user-attachments/assets/a27aa70c-f691-4796-84f0-c2eb18c7de23)
49
+ **Tip**: You can make things prettier if you don't mind if this guarantee is broken. See the `PrettyBroken` flag below!
46
50
 
47
51
  ### Supports images
48
52
  Here's kitty and alacritty. Try to do that in glow...
@@ -57,6 +61,9 @@ Here's kitty and alacritty. Try to do that in glow...
57
61
  As well as everything else...
58
62
  ![dunder](https://github.com/user-attachments/assets/d41d7fec-6dec-4387-b53d-f2098f269a5e)
59
63
 
64
+ Very ... Carefully ... Supported ...
65
+ ![cjk1](https://github.com/user-attachments/assets/75162ade-4734-440e-aaa3-5ffc17a0dd46)
66
+
60
67
  ### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
61
68
  ![configurable](https://github.com/user-attachments/assets/19ca2ec9-8ea1-4a79-87ca-8352789269fe)
62
69
 
@@ -67,7 +74,7 @@ For instance, here is the [latex plugin](https://github.com/kristopolous/Streamd
67
74
 
68
75
  ## TOML Configuration
69
76
 
70
- Streamdown uses a TOML configuration file 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.
77
+ 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.
71
78
 
72
79
  Here are the sections:
73
80
 
@@ -85,12 +92,15 @@ Defines the base Hue (H), Saturation (S), and Value (V) from which all other pal
85
92
  * `Margin` (integer, default: `2`): The left and right indent for the output.
86
93
  * `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
87
94
  * `PrettyPad` (boolean, default: `false`): 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
95
+ * `PrettyBroken` (boolean, default: `false`): 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.
88
96
  * `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
89
97
  * `Syntax` (string, default `monokai`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
90
98
 
91
99
  Example:
92
100
  ```toml
93
101
  [style]
102
+ PrettyPad = true
103
+ PrettyBroken = true
94
104
  HSV = [0.7, 0.5, 0.5]
95
105
  Dark = { H = 1.0, S = 1.2, V = 0.25 } # Make dark elements less saturated and darker
96
106
  Symbol = { H = 1.0, S = 1.8, V = 1.8 } # Make symbols more vibrant
@@ -103,16 +113,13 @@ Controls optional features:
103
113
  * `CodeSpaces` (boolean, default: `true`): Enables detection of code blocks indented with 4 spaces. Set to `false` to disable this detection method (triple-backtick blocks still work).
104
114
  * `Clipboard` (boolean, default: `true`): Enables copying the last code block encountered to the system clipboard using OSC 52 escape sequences upon exit. Set to `false` to disable.
105
115
  * `Logging` (boolean, default: `false`): Enables logging to tmpdir (/tmp/sd) of the raw markdown for debugging and bug reporting. The logging uses an emoji as a record separator so the actual streaming delays can be simulated and replayed. If you use the `filename` based invocation, that is to say, `sd <filename>`, this type of logging is always off.
106
- * `Timeout` (float, default: `0.5`): This is a workaround to the [buffer parsing bugs](https://github.com/kristopolous/Streamdown/issues/4). By increasing the select timeout, the parser loop only gets triggerd on newline which means that having to resume from things like a code block, inside a list, inside a table, between buffers, without breaking formatting doesn't need to be done. I assert (2025-04-09) this is no longer a bug. Feel free to turn on `Logging` and post an issue if you find a repeatable one.
116
+ * `Savebrace` (boolean, default: `true`): Saves the code blocks of a conversation to the append file `/tmp/sd/savebrace` so you can fzf or whatever you want through it. See how it's used in my [llmehelp](https://github.com/kristopolous/llmehelp) scripts, specifically `screen-query` and `sd-picker`.
107
117
 
108
118
  Example:
109
119
  ```toml
110
120
  [features]
111
121
  CodeSpaces = false
112
122
  Clipboard = false
113
- Margin = 4
114
- Width = 120
115
- Timeout = 1.0
116
123
  ```
117
124
 
118
125
  ## Command Line
@@ -146,7 +153,7 @@ Do this
146
153
  $ ./streamdown/sd.py tests/*md
147
154
 
148
155
  ## Install from source
149
- After the git clone least one of these should work, hopefully. it's using the modern uv pip tool.
156
+ After the git clone least one of these should work, hopefully. it's using the modern uv pip tool but is also backwards compatible to the `pip3 install -r requirements.txt` flow.
150
157
 
151
158
  $ pipx install -e .
152
159
  $ pip install -e .
@@ -154,9 +161,5 @@ After the git clone least one of these should work, hopefully. it's using the mo
154
161
 
155
162
  ### Future work
156
163
 
157
- #### CSS
158
- I'm really considering using `tinycss2` and making an actual stylesheet engine. This is related to another problem - getting a modern HTML renderer in the terminal that is actually navigable. I *think* it's probably a separate project.
159
-
160
- #### scrape
161
- This is already partially implemented. The idea is every code block can get extracted and put in a directory so you can have a conversation to generate every piece of a project, similar to Aider, Claude or Goose, but in the most hands-off yet still convenient way possible.
162
-
164
+ #### Glow styles
165
+ I'm going to try to be compatible with other popular markdown styles to help for a smoother transition. Glow compatible json sheets is on my radar. There's also mdless and frogmouth. Might be others
@@ -2,18 +2,22 @@
2
2
  <img src=https://github.com/user-attachments/assets/0468eac0-2a00-4e98-82ca-09e6ac679357/>
3
3
  <br/>
4
4
  <a href=https://pypi.org/project/streamdown><img src=https://badge.fury.io/py/streamdown.svg/></a>
5
+ <br/><strong>Terminal streaming markdown that rocks</strong>
6
+
5
7
  </p>
6
8
 
7
- The streaming markdown renderer for the terminal that rocks!
8
- Streamdown works with [simonw's llm](https://github.com/simonw/llm) along with any other streaming markdown. You even get full readline and keyboard navigation support.
9
+
10
+ Streamdown works with [simonw's llm](https://github.com/simonw/llm) along with any other streaming markdown, even something basic like curl.
11
+ It supports standard piping like any normal pager and a clean `execvp` option for robustly wrapping around interactive programs with readline or their own ANSI stuff to manage.
9
12
  ```bash
10
13
  $ pip install streamdown
11
14
  ```
12
15
  ![Streamdown is Amazing](https://github.com/user-attachments/assets/268cb340-78cc-4df0-a773-c5ac95eceeeb)
13
16
 
14
17
  ### Provides clean copyable code for long code lines
15
- You may have noticed *inferior* renderers inject line breaks when copying code that wraps around. We're better and now you are too!
18
+ Some *inferior* renderers inject line breaks when copying code that wraps around. We're better and now you are too!
16
19
  ![Handle That Mandle](https://github.com/user-attachments/assets/a27aa70c-f691-4796-84f0-c2eb18c7de23)
20
+ **Tip**: You can make things prettier if you don't mind if this guarantee is broken. See the `PrettyBroken` flag below!
17
21
 
18
22
  ### Supports images
19
23
  Here's kitty and alacritty. Try to do that in glow...
@@ -28,6 +32,9 @@ Here's kitty and alacritty. Try to do that in glow...
28
32
  As well as everything else...
29
33
  ![dunder](https://github.com/user-attachments/assets/d41d7fec-6dec-4387-b53d-f2098f269a5e)
30
34
 
35
+ Very ... Carefully ... Supported ...
36
+ ![cjk1](https://github.com/user-attachments/assets/75162ade-4734-440e-aaa3-5ffc17a0dd46)
37
+
31
38
  ### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
32
39
  ![configurable](https://github.com/user-attachments/assets/19ca2ec9-8ea1-4a79-87ca-8352789269fe)
33
40
 
@@ -38,7 +45,7 @@ For instance, here is the [latex plugin](https://github.com/kristopolous/Streamd
38
45
 
39
46
  ## TOML Configuration
40
47
 
41
- Streamdown uses a TOML configuration file 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.
48
+ 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.
42
49
 
43
50
  Here are the sections:
44
51
 
@@ -56,12 +63,15 @@ Defines the base Hue (H), Saturation (S), and Value (V) from which all other pal
56
63
  * `Margin` (integer, default: `2`): The left and right indent for the output.
57
64
  * `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
58
65
  * `PrettyPad` (boolean, default: `false`): 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
66
+ * `PrettyBroken` (boolean, default: `false`): 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.
59
67
  * `ListIndent` (integer, default: `2`): This is the recursive indent for the list styles.
60
68
  * `Syntax` (string, default `monokai`): This is the syntax [highlighting theme which come via pygments](https://pygments.org/styles/).
61
69
 
62
70
  Example:
63
71
  ```toml
64
72
  [style]
73
+ PrettyPad = true
74
+ PrettyBroken = true
65
75
  HSV = [0.7, 0.5, 0.5]
66
76
  Dark = { H = 1.0, S = 1.2, V = 0.25 } # Make dark elements less saturated and darker
67
77
  Symbol = { H = 1.0, S = 1.8, V = 1.8 } # Make symbols more vibrant
@@ -74,16 +84,13 @@ Controls optional features:
74
84
  * `CodeSpaces` (boolean, default: `true`): Enables detection of code blocks indented with 4 spaces. Set to `false` to disable this detection method (triple-backtick blocks still work).
75
85
  * `Clipboard` (boolean, default: `true`): Enables copying the last code block encountered to the system clipboard using OSC 52 escape sequences upon exit. Set to `false` to disable.
76
86
  * `Logging` (boolean, default: `false`): Enables logging to tmpdir (/tmp/sd) of the raw markdown for debugging and bug reporting. The logging uses an emoji as a record separator so the actual streaming delays can be simulated and replayed. If you use the `filename` based invocation, that is to say, `sd <filename>`, this type of logging is always off.
77
- * `Timeout` (float, default: `0.5`): This is a workaround to the [buffer parsing bugs](https://github.com/kristopolous/Streamdown/issues/4). By increasing the select timeout, the parser loop only gets triggerd on newline which means that having to resume from things like a code block, inside a list, inside a table, between buffers, without breaking formatting doesn't need to be done. I assert (2025-04-09) this is no longer a bug. Feel free to turn on `Logging` and post an issue if you find a repeatable one.
87
+ * `Savebrace` (boolean, default: `true`): Saves the code blocks of a conversation to the append file `/tmp/sd/savebrace` so you can fzf or whatever you want through it. See how it's used in my [llmehelp](https://github.com/kristopolous/llmehelp) scripts, specifically `screen-query` and `sd-picker`.
78
88
 
79
89
  Example:
80
90
  ```toml
81
91
  [features]
82
92
  CodeSpaces = false
83
93
  Clipboard = false
84
- Margin = 4
85
- Width = 120
86
- Timeout = 1.0
87
94
  ```
88
95
 
89
96
  ## Command Line
@@ -117,7 +124,7 @@ Do this
117
124
  $ ./streamdown/sd.py tests/*md
118
125
 
119
126
  ## Install from source
120
- After the git clone least one of these should work, hopefully. it's using the modern uv pip tool.
127
+ After the git clone least one of these should work, hopefully. it's using the modern uv pip tool but is also backwards compatible to the `pip3 install -r requirements.txt` flow.
121
128
 
122
129
  $ pipx install -e .
123
130
  $ pip install -e .
@@ -125,9 +132,5 @@ After the git clone least one of these should work, hopefully. it's using the mo
125
132
 
126
133
  ### Future work
127
134
 
128
- #### CSS
129
- I'm really considering using `tinycss2` and making an actual stylesheet engine. This is related to another problem - getting a modern HTML renderer in the terminal that is actually navigable. I *think* it's probably a separate project.
130
-
131
- #### scrape
132
- This is already partially implemented. The idea is every code block can get extracted and put in a directory so you can have a conversation to generate every piece of a project, similar to Aider, Claude or Goose, but in the most hands-off yet still convenient way possible.
133
-
135
+ #### Glow styles
136
+ I'm going to try to be compatible with other popular markdown styles to help for a smoother transition. Glow compatible json sheets is on my radar. There's also mdless and frogmouth. Might be others
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "streamdown"
7
- version = "0.16.0"
7
+ version = "0.18.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"
@@ -0,0 +1,5 @@
1
+ pygments
2
+ pylatexenc
3
+ appdirs
4
+ term-image
5
+ toml
@@ -0,0 +1,45 @@
1
+ import os
2
+ import pty
3
+ import sys
4
+ import tty
5
+ import termios
6
+ import select
7
+ import re
8
+
9
+ def main():
10
+ orig_attrs = termios.tcgetattr(sys.stdin)
11
+ try:
12
+ tty.setraw(sys.stdin)
13
+ pid, fd = pty.fork()
14
+
15
+ if pid == 0:
16
+ # Child process: run any command
17
+ os.execvp("llm", ["llm", "chat"])
18
+ else:
19
+ buffer = b""
20
+
21
+ while True:
22
+ r, _, _ = select.select([fd, sys.stdin], [], [])
23
+
24
+ if sys.stdin in r:
25
+ data = os.read(sys.stdin.fileno(), 1024)
26
+ if not data:
27
+ break
28
+ os.write(fd, data)
29
+
30
+ if fd in r:
31
+ data = os.read(fd, 1024)
32
+ if not data:
33
+ break
34
+
35
+ buffer += data
36
+ # Replace "fizz" only in printable content
37
+ output = re.sub(rb'fizz', b'fizzbuzz', buffer)
38
+ os.write(sys.stdout.fileno(), output)
39
+ buffer = b""
40
+ finally:
41
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, orig_attrs)
42
+
43
+ if __name__ == "__main__":
44
+ main()
45
+
@@ -0,0 +1,69 @@
1
+ import os
2
+ import pty
3
+ import select
4
+ import sys
5
+ import tty
6
+ import termios
7
+ import fcntl
8
+ import struct
9
+
10
+ def set_pty_size(fd, target_fd):
11
+ """Set window size of PTY to match the real terminal."""
12
+ s = fcntl.ioctl(target_fd, termios.TIOCGWINSZ, b"\x00" * 8)
13
+ fcntl.ioctl(fd, termios.TIOCSWINSZ, s)
14
+
15
+ def main():
16
+ # Save original terminal settings
17
+ orig_attrs = termios.tcgetattr(sys.stdin.fileno())
18
+ try:
19
+ tty.setraw(sys.stdin.fileno()) # raw mode to send Ctrl-C, etc.
20
+ pid, fd = pty.fork()
21
+
22
+ if pid == 0:
23
+ os.execvp("bash", ["bash"])
24
+ else:
25
+ set_pty_size(fd, sys.stdin.fileno())
26
+
27
+ while True:
28
+ r, _, _ = select.select([fd, sys.stdin], [], [])
29
+
30
+ if sys.stdin in r:
31
+ user_input = os.read(sys.stdin.fileno(), 1024)
32
+ if not user_input:
33
+ break
34
+ os.write(fd, user_input)
35
+
36
+ if fd in r:
37
+ output = os.read(fd, 1024)
38
+ if not output:
39
+ break
40
+
41
+ # Carefully handle ANSI sequences
42
+ # Split on ANSI escape sequences to avoid breaking them
43
+ chunks = []
44
+ i = 0
45
+ while i < len(output):
46
+ if output[i:i+1] == b'\x1b':
47
+ end = i + 1
48
+ while end < len(output) and not (64 <= output[end] <= 126):
49
+ end += 1
50
+ end += 1 # include final letter
51
+ chunks.append(output[i:end])
52
+ i = end
53
+ else:
54
+ j = i
55
+ while j < len(output) and output[j:j+1] != b'\x1b':
56
+ j += 1
57
+ # Perform replacement on plain text only
58
+ text = output[i:j].replace(b"fizz", b"fizzbuzz")
59
+ chunks.append(text)
60
+ i = j
61
+
62
+ os.write(sys.stdout.fileno(), b''.join(chunks))
63
+
64
+ finally:
65
+ termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, orig_attrs)
66
+
67
+ if __name__ == "__main__":
68
+ main()
69
+