streamdown 0.12.0__tar.gz → 0.13.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 (50) hide show
  1. {streamdown-0.12.0 → streamdown-0.13.0}/PKG-INFO +26 -3
  2. {streamdown-0.12.0 → streamdown-0.13.0}/README.md +25 -2
  3. {streamdown-0.12.0 → streamdown-0.13.0}/pyproject.toml +1 -1
  4. {streamdown-0.12.0 → streamdown-0.13.0}/streamdown/sd.py +112 -39
  5. streamdown-0.13.0/tests/block.md +10 -0
  6. {streamdown-0.12.0 → streamdown-0.13.0}/.gitignore +0 -0
  7. {streamdown-0.12.0 → streamdown-0.13.0}/.vimrc +0 -0
  8. {streamdown-0.12.0 → streamdown-0.13.0}/24-bit-color.sh +0 -0
  9. {streamdown-0.12.0 → streamdown-0.13.0}/LICENSE.MIT +0 -0
  10. {streamdown-0.12.0 → streamdown-0.13.0}/configurable.png +0 -0
  11. {streamdown-0.12.0 → streamdown-0.13.0}/copyable.png +0 -0
  12. {streamdown-0.12.0 → streamdown-0.13.0}/dunder.png +0 -0
  13. {streamdown-0.12.0 → streamdown-0.13.0}/error.txt +0 -0
  14. {streamdown-0.12.0 → streamdown-0.13.0}/newdir/file_0.py +0 -0
  15. {streamdown-0.12.0 → streamdown-0.13.0}/newdir/file_1.rb +0 -0
  16. {streamdown-0.12.0 → streamdown-0.13.0}/newdir/file_2.jl +0 -0
  17. {streamdown-0.12.0 → streamdown-0.13.0}/passthrough.py +0 -0
  18. {streamdown-0.12.0 → streamdown-0.13.0}/somelog.txt +0 -0
  19. {streamdown-0.12.0 → streamdown-0.13.0}/streamdown/__init__.py +0 -0
  20. {streamdown-0.12.0 → streamdown-0.13.0}/streamdown/plugins/README.md +0 -0
  21. {streamdown-0.12.0 → streamdown-0.13.0}/streamdown/plugins/latex.py +0 -0
  22. {streamdown-0.12.0 → streamdown-0.13.0}/streamdown/tt.mds +0 -0
  23. {streamdown-0.12.0 → streamdown-0.13.0}/table.png +0 -0
  24. {streamdown-0.12.0 → streamdown-0.13.0}/temp.py +0 -0
  25. {streamdown-0.12.0 → streamdown-0.13.0}/test.py +0 -0
  26. {streamdown-0.12.0 → streamdown-0.13.0}/test_input.md +0 -0
  27. {streamdown-0.12.0 → streamdown-0.13.0}/tests/README.md +0 -0
  28. {streamdown-0.12.0 → streamdown-0.13.0}/tests/chunk-buffer.sh +0 -0
  29. {streamdown-0.12.0 → streamdown-0.13.0}/tests/code.md +0 -0
  30. {streamdown-0.12.0 → streamdown-0.13.0}/tests/example.md +0 -0
  31. {streamdown-0.12.0 → streamdown-0.13.0}/tests/fizzbuzz.md +0 -0
  32. {streamdown-0.12.0 → streamdown-0.13.0}/tests/inline.md +0 -0
  33. {streamdown-0.12.0 → streamdown-0.13.0}/tests/line-buffer.sh +0 -0
  34. {streamdown-0.12.0 → streamdown-0.13.0}/tests/line-wrap.md +0 -0
  35. {streamdown-0.12.0 → streamdown-0.13.0}/tests/line.md +0 -0
  36. {streamdown-0.12.0 → streamdown-0.13.0}/tests/links.md +0 -0
  37. {streamdown-0.12.0 → streamdown-0.13.0}/tests/longer-example.md +0 -0
  38. {streamdown-0.12.0 → streamdown-0.13.0}/tests/mandlebrot.md +0 -0
  39. {streamdown-0.12.0 → streamdown-0.13.0}/tests/markdown.md +0 -0
  40. {streamdown-0.12.0 → streamdown-0.13.0}/tests/nested-example.md +0 -0
  41. {streamdown-0.12.0 → streamdown-0.13.0}/tests/new.md +0 -0
  42. {streamdown-0.12.0 → streamdown-0.13.0}/tests/outline.md +0 -0
  43. {streamdown-0.12.0 → streamdown-0.13.0}/tests/sd.log +0 -0
  44. {streamdown-0.12.0 → streamdown-0.13.0}/tests/table-break.md +0 -0
  45. {streamdown-0.12.0 → streamdown-0.13.0}/tests/table.md +0 -0
  46. {streamdown-0.12.0 → streamdown-0.13.0}/tests/table_test.md +0 -0
  47. {streamdown-0.12.0 → streamdown-0.13.0}/tests/test.md +0 -0
  48. {streamdown-0.12.0 → streamdown-0.13.0}/tests/test_input.md +0 -0
  49. {streamdown-0.12.0 → streamdown-0.13.0}/tests/white-space-code.md +0 -0
  50. {streamdown-0.12.0 → streamdown-0.13.0}/tests/wm.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamdown
3
- Version: 0.12.0
3
+ Version: 0.13.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
@@ -109,6 +109,31 @@ Width = 120
109
109
  Timeout = 1.0
110
110
  ```
111
111
 
112
+ ## Invocation
113
+ The most exciting feature here is `--exec` with it you can do full readline support like this:
114
+
115
+ $ sd --exec "llm chat"
116
+
117
+ And now you have all your readline stuff. It's pretty great.
118
+
119
+ ```shell
120
+ Streamdown - A markdown renderer for modern terminals
121
+
122
+ positional arguments:
123
+ filenameList Input file to process (also takes stdin)
124
+
125
+ options:
126
+ -h, --help show this help message and exit
127
+ -l LOGLEVEL, --loglevel LOGLEVEL
128
+ Set the logging level
129
+ -c COLOR, --color COLOR
130
+ Set the hsv base: h,s,v
131
+ -w WIDTH, --width WIDTH
132
+ Set the width
133
+ -e EXEC, --exec EXEC Wrap a program for more 'proper' i/o handling
134
+
135
+ ```
136
+
112
137
  ## Demo
113
138
  Do this
114
139
 
@@ -131,5 +156,3 @@ I'm really considering using `tinycss2` and making an actual stylesheet engine.
131
156
  #### scrape
132
157
  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
158
 
134
- #### exec
135
- I'm trying to get a readline capable wrapper so that interaction is as transparent as possible. After many days of research I've given up on trying to hack it through tty/pty hijacking and pipes and decided it has to be a standard wrapper. This should be low effort.
@@ -81,6 +81,31 @@ Width = 120
81
81
  Timeout = 1.0
82
82
  ```
83
83
 
84
+ ## Invocation
85
+ The most exciting feature here is `--exec` with it you can do full readline support like this:
86
+
87
+ $ sd --exec "llm chat"
88
+
89
+ And now you have all your readline stuff. It's pretty great.
90
+
91
+ ```shell
92
+ Streamdown - A markdown renderer for modern terminals
93
+
94
+ positional arguments:
95
+ filenameList Input file to process (also takes stdin)
96
+
97
+ options:
98
+ -h, --help show this help message and exit
99
+ -l LOGLEVEL, --loglevel LOGLEVEL
100
+ Set the logging level
101
+ -c COLOR, --color COLOR
102
+ Set the hsv base: h,s,v
103
+ -w WIDTH, --width WIDTH
104
+ Set the width
105
+ -e EXEC, --exec EXEC Wrap a program for more 'proper' i/o handling
106
+
107
+ ```
108
+
84
109
  ## Demo
85
110
  Do this
86
111
 
@@ -103,5 +128,3 @@ I'm really considering using `tinycss2` and making an actual stylesheet engine.
103
128
  #### scrape
104
129
  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.
105
130
 
106
- #### exec
107
- I'm trying to get a readline capable wrapper so that interaction is as transparent as possible. After many days of research I've given up on trying to hack it through tty/pty hijacking and pipes and decided it has to be a standard wrapper. This should be low effort.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "streamdown"
7
- version = "0.12.0"
7
+ version = "0.13.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"
@@ -12,6 +12,7 @@ import appdirs, toml
12
12
  import logging, tempfile
13
13
  import os, sys
14
14
  import pty, select
15
+ import termios, tty
15
16
 
16
17
  import math
17
18
  import re
@@ -20,6 +21,7 @@ import subprocess
20
21
  import traceback
21
22
  import colorsys
22
23
  import base64
24
+ import importlib
23
25
  from io import BytesIO
24
26
  import pygments.util
25
27
  from argparse import ArgumentParser
@@ -28,7 +30,10 @@ from pygments.lexers import get_lexer_by_name
28
30
  from pygments.formatters import Terminal256Formatter
29
31
  from pygments.styles import get_style_by_name
30
32
 
31
- from .plugins import latex
33
+ if __package__ is None:
34
+ from plugins import latex
35
+ else:
36
+ from .plugins import latex
32
37
 
33
38
  default_toml = """
34
39
  [features]
@@ -77,7 +82,8 @@ UNDERLINE = ["\033[4m", "\033[24m"]
77
82
  ITALIC = ["\033[3m", "\033[23m"]
78
83
 
79
84
  ESCAPE = r"\033\[[0-9;]*[mK]"
80
- ANSIESCAPE = r"\033(\[[0-9;]*[mK]|][0-9]*;;.*?\\|\\)"
85
+ ANSIESCAPE = r'\033(?:\[[0-9;?]*[a-zA-Z]|][0-9]*;;.*?\\|\\)'
86
+ #r"\033(\[[0-9;]*[mK]|][0-9]*;;.*?\\|\\)"
81
87
  KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
82
88
 
83
89
  visible = lambda x: re.sub(ANSIESCAPE, "", x)
@@ -114,15 +120,18 @@ class ParseState:
114
120
  self.first_line = True
115
121
  self.last_line_empty = False
116
122
  self.is_pty = False
123
+ self.is_exec = False
117
124
  self.maybe_prompt = False
118
125
  self.emit_flag = None
119
126
  self.scrape = None
120
127
  self.scrape_ix = 0
128
+ self.terminal = None
121
129
 
122
130
  self.CodeSpaces = _features.get("CodeSpaces")
123
131
  self.Clipboard = _features.get("Clipboard")
124
132
  self.Logging = _features.get("Logging")
125
133
  self.Timeout = _features.get("Timeout")
134
+ self.WidthArg = None
126
135
 
127
136
  # If the entire block is indented this will
128
137
  # tell us what that is
@@ -150,7 +159,13 @@ class ParseState:
150
159
  self.in_italic = False
151
160
  self.in_table = False # (Code.[Header|Body] | False)
152
161
  self.in_underline = False
153
- self.in_blockquote = False
162
+ self.block_depth = 0
163
+
164
+ self.exec_sub = None
165
+ self.exec_master = None
166
+ self.exec_slave = None
167
+ self.exec_kb = 0
168
+ self.exec_israw = False
154
169
 
155
170
  self.exit = 0
156
171
  self.where_from = None
@@ -161,7 +176,7 @@ class ParseState:
161
176
  return state
162
177
 
163
178
  def space_left(self):
164
- return (MARGIN_SPACES if len(self.current_line) == 0 else "") + (BQUOTE if self.in_blockquote else "")
179
+ return (Style.MarginSpaces if len(self.current_line) == 0 else "") + (Style.Blockquote * self.block_depth)
165
180
 
166
181
  state = ParseState()
167
182
 
@@ -210,7 +225,7 @@ def format_table(rowList):
210
225
  # Correct indentation: This should be outside the c_idx loop
211
226
  joined_line = f"{BG}{bg_color}{extra}{FG}{Style.Symbol}│{RESET}".join(line_segments)
212
227
  # Correct indentation and add missing characters
213
- yield f"{MARGIN_SPACES}{joined_line}{RESET}"
228
+ yield f"{Style.MarginSpaces}{joined_line}{RESET}"
214
229
 
215
230
  state.bg = BGRESET
216
231
 
@@ -218,21 +233,21 @@ def emit_h(level, text):
218
233
  text = line_format(text)
219
234
  spaces_to_center = ((state.Width - visible_length(text)) / 2)
220
235
  if level == 1: #
221
- return f"\n{MARGIN_SPACES}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}\n"
236
+ return f"\n{Style.MarginSpaces}{BOLD[0]}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{BOLD[1]}\n"
222
237
  elif level == 2: ##
223
- return f"\n{MARGIN_SPACES}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{RESET}\n\n"
238
+ return f"\n{Style.MarginSpaces}{BOLD[0]}{FG}{Style.Bright}{' ' * math.floor(spaces_to_center)}{text}{' ' * math.ceil(spaces_to_center)}{RESET}\n\n"
224
239
  elif level == 3: ###
225
- return f"{MARGIN_SPACES}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
240
+ return f"{Style.MarginSpaces}{FG}{Style.Head}{BOLD[0]}{text}{RESET}"
226
241
  elif level == 4: ####
227
- return f"{MARGIN_SPACES}{FG}{Style.Symbol}{text}{RESET}"
242
+ return f"{Style.MarginSpaces}{FG}{Style.Symbol}{text}{RESET}"
228
243
  else: # level 5 or 6
229
- return f"{MARGIN_SPACES}{text}{RESET}"
244
+ return f"{Style.MarginSpaces}{text}{RESET}"
230
245
 
231
246
  def code_wrap(text_in):
232
247
  # get the indentation of the first line
233
248
  indent = len(text_in) - len(text_in.lstrip())
234
249
  text = text_in.lstrip()
235
- mywidth = state.FullWidth - indent
250
+ mywidth = state.WidthFull - indent
236
251
 
237
252
  # We take special care to preserve empty lines
238
253
  if len(text) == 0:
@@ -378,11 +393,39 @@ def parse(stream):
378
393
  byte = None
379
394
  TimeoutIx = 0
380
395
  while True:
381
- if state.is_pty:
396
+ if state.is_pty or state.is_exec:
382
397
  byte = None
383
- ready, _, _ = select.select([stream.fileno()], [], [], state.Timeout)
398
+ ready_in, _, _ = select.select(
399
+ [stream.fileno(), state.exec_master], [], [], state.Timeout)
400
+
401
+ if state.is_exec:
402
+ # This is keyboard input
403
+ if stream.fileno() in ready_in:
404
+ byte = os.read(stream.fileno(), 1)
405
+
406
+ state.exec_kb += 1
407
+ os.write(state.exec_master, byte)
408
+
409
+ if byte == b'\n':
410
+ state.buffer = b''
411
+ print("")
412
+ state.exec_kb = 0
413
+ else:
414
+ continue
384
415
 
385
- if stream.fileno() in ready:
416
+ if state.exec_master in ready_in:
417
+ TimeoutIx = 0
418
+ byte = os.read(state.exec_master, 1)
419
+
420
+ if state.exec_kb:
421
+ os.write(sys.stdout.fileno(), byte)
422
+
423
+ if len(ready_in) == 0:
424
+ TimeoutIx += 1
425
+
426
+
427
+
428
+ elif stream.fileno() in ready_in:
386
429
  byte = os.read(stream.fileno(), 1)
387
430
  TimeoutIx = 0
388
431
  elif TimeoutIx == 0:
@@ -402,14 +445,15 @@ def parse(stream):
402
445
 
403
446
  line = state.buffer.decode('utf-8')
404
447
  state.has_newline = line.endswith('\n')
405
- state.maybe_prompt = not state.has_newline and state.current()['none'] and re.match(r'^.*>\s+$', line)
448
+ # I hate this. There should be better ways.
449
+ state.maybe_prompt = not state.has_newline and state.current()['none'] and re.match(r'^.*>\s+$', visible(line))
406
450
 
407
451
  # let's wait for a newline
408
452
  if state.maybe_prompt:
409
453
  state.emit_flag = Code.Flush
410
454
  yield line
455
+ state.current_line = ''
411
456
  state.buffer = b''
412
- continue
413
457
 
414
458
  if not state.has_newline:
415
459
  continue
@@ -447,7 +491,7 @@ def parse(stream):
447
491
  else:
448
492
  state.in_list = False
449
493
 
450
- if state.first_indent == None:
494
+ if state.first_indent is None:
451
495
  state.first_indent = len(line) - len(line.lstrip())
452
496
  if len(line) - len(line.lstrip()) >= state.first_indent:
453
497
  line = line[state.first_indent:]
@@ -462,13 +506,21 @@ def parse(stream):
462
506
  if state.in_table and not state.in_code and not re.match(r"^\s*\|.+\|\s*$", line):
463
507
  state.in_table = False
464
508
 
465
- block_match = re.match(r"^<.?think>$", line)
509
+ block_match = re.match(r"^((> )*|<.?think>)", line)
466
510
  if block_match:
467
- state.in_blockquote = not state.in_blockquote
468
- # consume and don't emit
469
- if not state.in_blockquote:
470
- yield( RESET)
471
- continue
511
+ if block_match.group(1) == '</think>':
512
+ state.block_depth = 0
513
+ yield(RESET)
514
+ elif block_match.group(1) == '<think>':
515
+ state.block_depth = 1
516
+ else:
517
+ state.block_depth = int(len(block_match.group(0)) / 2)
518
+ # we also need to consume those tokens
519
+ line = line[state.block_depth * 2:]
520
+ else:
521
+ if state.block_depth > 0:
522
+ yield RESET
523
+ state.block_depth = 0
472
524
 
473
525
  #
474
526
  # <code><pre>
@@ -532,7 +584,7 @@ def parse(stream):
532
584
 
533
585
 
534
586
  if code_type == Code.Backtick:
535
- continue
587
+ continue
536
588
  else:
537
589
  # otherwise we don't want to consume
538
590
  # nor do we want to be here.
@@ -600,10 +652,10 @@ def parse(stream):
600
652
 
601
653
  code_line = ' ' * indent + this_batch.strip()
602
654
 
603
- margin = state.FullWidth - visible_length(code_line)
655
+ margin = state.WidthFull - visible_length(code_line)
604
656
  yield f"{Style.Codebg}{code_line}{' ' * max(0, margin)}{BGRESET}"
605
657
  continue
606
- except Goto as ex:
658
+ except Goto:
607
659
  pass
608
660
 
609
661
  except Exception as ex:
@@ -694,7 +746,7 @@ def parse(stream):
694
746
  if hr_match:
695
747
  if state.last_line_empty or last_line_empty_cache:
696
748
  # print a horizontal rule using a unicode midline
697
- yield f"{MARGIN_SPACES}{FG}{Style.Symbol}{'─' * state.Width}{RESET}"
749
+ yield f"{Style.MarginSpaces}{FG}{Style.Symbol}{'─' * state.Width}{RESET}"
698
750
  else:
699
751
  # We tell the next level up that the beginning of the buffer should be a flag.
700
752
  # Underneath this condition it will no longer yield
@@ -722,6 +774,7 @@ def emit(inp):
722
774
  buffer = []
723
775
  flush = False
724
776
  for chunk in parse(inp):
777
+ width_calc()
725
778
  if state.emit_flag:
726
779
  if state.emit_flag == Code.Flush:
727
780
  flush = True
@@ -770,8 +823,17 @@ def apply_multipliers(name, H, S, V):
770
823
  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"]))
771
824
  return ';'.join([str(int(x * 256)) for x in [r, g, b]]) + "m"
772
825
 
826
+ def width_calc():
827
+ state.WidthFull = state.WidthArg or int(get_terminal_width())
828
+ state.Width = state.WidthFull - 2 * Style.Margin
829
+ Style.Codepad = [
830
+ f"{RESET}{FG}{Style.Dark}{'▄' * state.WidthFull}{RESET}\n",
831
+ f"{RESET}{FG}{Style.Dark}{'▀' * state.WidthFull}{RESET}"
832
+ ]
833
+
773
834
  def main():
774
- global H, S, V, MARGIN_SPACES
835
+ global H, S, V
836
+
775
837
  parser = ArgumentParser(description="Streamdown - A markdown renderer for modern terminals")
776
838
  parser.add_argument("filenameList", nargs="*", help="Input file to process (also takes stdin)")
777
839
  parser.add_argument("-l", "--loglevel", default="INFO", help="Set the logging level")
@@ -796,24 +858,27 @@ def main():
796
858
  os.makedirs(args.scrape, exist_ok=True)
797
859
  state.scrape = args.scrape
798
860
 
799
- MARGIN_SPACES = " " * Style.Margin
800
- state.FullWidth = int(args.width) or _style.get("Width") or int(get_terminal_width())
801
- state.Width = state.FullWidth - 2 * Style.Margin
861
+ Style.MarginSpaces = " " * Style.Margin
862
+ state.WidthArg = int(args.width) or _style.get("Width") or 0
863
+ width_calc()
864
+
802
865
  Style.Codebg = f"{BG}{Style.Dark}"
803
866
  Style.Link = f"{FG}{Style.Symbol}{UNDERLINE[0]}"
804
867
  Style.Blockquote = f"{FG}{Style.Grey} \u258E "
805
868
 
806
- Style.Codepad = [
807
- f"{RESET}{FG}{Style.Dark}{'▄' * state.FullWidth}{RESET}\n",
808
- f"{RESET}{FG}{Style.Dark}{'▀' * state.FullWidth}{RESET}"
809
- ]
810
869
 
811
870
  logging.basicConfig(stream=sys.stdout, level=args.loglevel.upper(), format=f'%(message)s')
871
+ state.exec_master, state.exec_slave = pty.openpty()
812
872
  try:
813
873
  inp = sys.stdin
814
874
  if args.exec:
815
- state.sub = subprocess.Popen(args.exec.split(' '), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
816
- inp = state.sub.stdout
875
+ state.terminal = termios.tcgetattr(sys.stdin)
876
+ state.is_exec = True
877
+ state.exec_sub = subprocess.Popen(args.exec.split(' '), stdin=state.exec_slave, stdout=state.exec_slave, stderr=state.exec_slave, close_fds=True)
878
+ os.close(state.exec_slave) # We don't need slave in parent
879
+ # Set stdin to raw mode so we don't need to press enter
880
+ tty.setcbreak(sys.stdin.fileno())
881
+ emit(sys.stdin)
817
882
 
818
883
  elif args.filenameList:
819
884
  # Let's say we only care about logging in streams
@@ -832,11 +897,13 @@ def main():
832
897
  os.set_blocking(inp.fileno(), False)
833
898
  emit(inp)
834
899
 
835
- except KeyboardInterrupt:
900
+ except (OSError, KeyboardInterrupt):
836
901
  state.exit = 130
837
902
 
838
903
  except Exception as ex:
839
- logging.warning(f"Exception thrown: {ex}")
904
+ if state.terminal:
905
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, state.terminal)
906
+ logging.warning(f"Exception thrown: {type(ex)} {ex}")
840
907
  traceback.print_exc()
841
908
 
842
909
  if state.Clipboard and state.code_buffer:
@@ -847,6 +914,12 @@ def main():
847
914
  base64_string = base64_bytes.decode('utf-8')
848
915
  print(f"\033]52;c;{base64_string}\a", end="", flush=True)
849
916
 
917
+
918
+ if state.terminal:
919
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, state.terminal)
920
+ os.close(state.exec_master)
921
+ if state.exec_sub:
922
+ state.exec_sub.wait()
850
923
  sys.exit(state.exit)
851
924
 
852
925
  if __name__ == "__main__":
@@ -0,0 +1,10 @@
1
+ So here is some text
2
+
3
+ > and technically we are in blockquote
4
+ > territory with these. Blockquote is one
5
+ > > of the few things that can be embedded
6
+ > > in markdown. Stylistically they are
7
+ > > different than lists, but
8
+ > not by much.
9
+
10
+ Now we are out of the blockquote
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes