streamdown 0.17.0__py3-none-any.whl → 0.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
streamdown/sd.py CHANGED
@@ -36,7 +36,7 @@ from functools import reduce
36
36
  from argparse import ArgumentParser
37
37
  from pygments import highlight
38
38
  from pygments.lexers import get_lexer_by_name
39
- from pygments.formatters import Terminal256Formatter
39
+ from pygments.formatters import TerminalTrueColorFormatter
40
40
  from pygments.styles import get_style_by_name
41
41
 
42
42
  if __package__ is None:
@@ -46,7 +46,7 @@ else:
46
46
 
47
47
  default_toml = """
48
48
  [features]
49
- CodeSpaces = true
49
+ CodeSpaces = false
50
50
  Clipboard = true
51
51
  Logging = false
52
52
  Timeout = 0.1
@@ -62,7 +62,7 @@ HSV = [0.8, 0.5, 0.5]
62
62
  Dark = { H = 1.00, S = 1.50, V = 0.25 }
63
63
  Mid = { H = 1.00, S = 1.00, V = 0.50 }
64
64
  Symbol = { H = 1.00, S = 1.00, V = 1.50 }
65
- Head = { H = 1.00, S = 2.00, V = 1.50 }
65
+ Head = { H = 1.00, S = 1.00, V = 1.75 }
66
66
  Grey = { H = 1.00, S = 0.25, V = 1.37 }
67
67
  Bright = { H = 1.00, S = 2.00, V = 2.00 }
68
68
  Syntax = "monokai"
@@ -99,7 +99,8 @@ ANSIESCAPE = r'\033(?:\[[0-9;?]*[a-zA-Z]|][0-9]*;;.*?\\|\\)'
99
99
  KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
100
100
 
101
101
  visible = lambda x: re.sub(ANSIESCAPE, "", x)
102
- visible_length = lambda x: len(visible(x))
102
+ # cjk characters are double width
103
+ visible_length = lambda x: len(visible(x)) + cjk_count(x)
103
104
  extract_ansi_codes = lambda text: re.findall(ESCAPE, text)
104
105
  remove_ansi = lambda line, codeList: reduce(lambda line, code: line.replace(code, ''), codeList, line)
105
106
 
@@ -231,7 +232,7 @@ def format_table(rowList):
231
232
  # Note this is where every cell is formatted so if
232
233
  # you are styling, do it before here!
233
234
  for row in rowList:
234
- wrapped_cell = text_wrap(row, width=col_width)
235
+ wrapped_cell = text_wrap(row, width=col_width, force_truncate=True)
235
236
 
236
237
  # Ensure at least one line, even for empty cells
237
238
  if not wrapped_cell:
@@ -285,7 +286,7 @@ def code_wrap(text_in):
285
286
  # get the indentation of the first line
286
287
  indent = len(text_in) - len(text_in.lstrip())
287
288
  text = text_in.lstrip()
288
- mywidth = state.full_width() - indent
289
+ mywidth = state.full_width(-4 if Style.PrettyBroken else 0) - indent
289
290
 
290
291
  # We take special care to preserve empty lines
291
292
  if len(text) == 0:
@@ -304,7 +305,7 @@ def ansi_collapse(codelist, inp):
304
305
  # We break SGR strings into various classes concerning their applicate or removal
305
306
  nums = {
306
307
  'fg': r'3\d', 'bg': r'4\d',
307
- 'b': r'2?1', 'i': r'2?3', 'u': r'3?2',
308
+ 'b': r'2?[12]', 'i': r'2?3', 'u': r'3?2',
308
309
  'reset': '0'
309
310
  }
310
311
 
@@ -334,12 +335,19 @@ def ansi_collapse(codelist, inp):
334
335
 
335
336
  return codelist + inp
336
337
 
337
- def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_line_prefix=""):
338
+
339
+ def split_text(text):
340
+ return re.split(
341
+ r'(?<=[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF])|(?=[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF])|\s+',
342
+ text
343
+ )
344
+
345
+ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_line_prefix="", force_truncate=False):
338
346
  if width == -1:
339
347
  width = state.Width
340
348
 
341
349
  # The empty word clears the buffer at the end.
342
- words = line_format(text).split() + [""]
350
+ words = split_text(line_format(text)) + [""]
343
351
  lines = []
344
352
  current_line = ""
345
353
  current_style = []
@@ -352,13 +360,21 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
352
360
  current_style.append(codes.pop(0))
353
361
 
354
362
  if len(word) and visible_length(current_line) + visible_length(word) + 1 <= width: # +1 for space
355
- current_line += (" " if current_line else "") + word
363
+
364
+ current_line += (" " if len(visible(word)) > 0 and current_line and not cjk_count(word) else "") + word
356
365
  else:
357
366
  # Word doesn't fit, finalize the previous line
358
367
  prefix = first_line_prefix if not lines else subsequent_line_prefix
359
368
  line_content = prefix + current_line
369
+ # This is expensive, fix.
370
+ while force_truncate and visible_length(line_content) >= width:
371
+ line_content = line_content[:len(line_content) - 2] + "…"
372
+
360
373
  margin = max(0, width - visible_length(line_content))
361
- lines.append(line_content + state.bg + ' ' * margin)
374
+
375
+ if line_content.strip() != "":
376
+ lines.append(line_content + state.bg + ' ' * margin)
377
+
362
378
  current_line = (" " * indent) + "".join(current_style) + word
363
379
 
364
380
  if len(codes):
@@ -372,8 +388,20 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
372
388
 
373
389
  return lines
374
390
 
391
+ def cjk_count(s):
392
+ cjk_re = re.compile(
393
+ r'[\u4E00-\u9FFF' # CJK Unified Ideographs
394
+ r'|\u3400-\u4DBF' # CJK Unified Ideographs Extension A
395
+ r'|\uF900-\uFAFF' # CJK Compatibility Ideographs
396
+ r'|\uFF00-\uFFEF' # CJK Compatibility Punctuation
397
+ r'|\u3000-\u303F' # CJK Symbols and Punctuation
398
+ r'|\U0002F800-\U0002FA1F]' # CJK Compatibility Ideographs Supplement
399
+ )
400
+
401
+ return len(cjk_re.findall(visible(s)))
402
+
375
403
  def line_format(line):
376
- not_text = lambda token: not (token.isalnum() or token == '\\')
404
+ not_text = lambda token: not (token.isalnum() or token == '\\') or cjk_count(token)
377
405
  footnotes = lambda match: ''.join([chr(SUPER[int(i)]) for i in match.group(1)])
378
406
 
379
407
  def process_images(match):
@@ -402,7 +430,7 @@ def line_format(line):
402
430
  result = ""
403
431
 
404
432
  for match in tokenList:
405
- token = match.group(1)
433
+ token = re.sub(r'\s+',' ', match.group(1))
406
434
  next_token = line[match.end()] if match.end() < len(line) else ""
407
435
  prev_token = line[match.start()-1] if match.start() > 0 else ""
408
436
 
@@ -458,6 +486,7 @@ def parse(stream):
458
486
  last_line_empty_cache = None
459
487
  byte = None
460
488
  TimeoutIx = 0
489
+ lexer = None
461
490
  while True:
462
491
  if state.is_pty or state.is_exec:
463
492
  byte = None
@@ -548,7 +577,7 @@ def parse(stream):
548
577
  line = line[len(block_match.group(0)):]
549
578
  else:
550
579
  if state.block_depth > 0:
551
- line = FGRESET + line
580
+ yield FGRESET
552
581
  state.block_depth = 0
553
582
 
554
583
  # --- Collapse Multiple Empty Lines if not in code blocks ---
@@ -569,7 +598,7 @@ def parse(stream):
569
598
  # \n buffer
570
599
  if not state.in_list and len(state.ordered_list_numbers) > 0:
571
600
  state.ordered_list_numbers[0] = 0
572
- elif not line.startswith(' ' * state.list_indent_text):
601
+ elif (not line.startswith(' ' * state.list_indent_text)) and line.strip() != "":
573
602
  state.in_list = False
574
603
  state.list_indent_text = 0
575
604
 
@@ -578,7 +607,7 @@ def parse(stream):
578
607
  if len(line) - len(line.lstrip()) >= state.first_indent:
579
608
  line = line[state.first_indent:]
580
609
  else:
581
- logging.warning("Indentation decreased from first line.")
610
+ logging.debug("Indentation decreased from first line.")
582
611
 
583
612
 
584
613
  # Indent guaranteed
@@ -648,8 +677,8 @@ def parse(stream):
648
677
  logging.debug(f"code: {state.in_code}")
649
678
  state.emit_flush = True
650
679
  # We suppress the newline - it's not an explicit style
651
- state.has_newline = False
652
- yield RESET
680
+ #state.has_newline = False
681
+ #yield RESET
653
682
 
654
683
  if code_type == Code.Backtick:
655
684
  continue
@@ -658,7 +687,7 @@ def parse(stream):
658
687
  # nor do we want to be here.
659
688
  raise Goto()
660
689
 
661
- if state.code_first_line:
690
+ if state.code_first_line or lexer is None:
662
691
  state.code_first_line = False
663
692
  try:
664
693
  lexer = get_lexer_by_name(state.code_language)
@@ -667,7 +696,7 @@ def parse(stream):
667
696
  lexer = get_lexer_by_name("Bash")
668
697
  custom_style = get_style_by_name("default")
669
698
 
670
- formatter = Terminal256Formatter(style=custom_style)
699
+ formatter = TerminalTrueColorFormatter(style=custom_style)
671
700
  if line.startswith(' ' * state.code_indent):
672
701
  line = line[state.code_indent :]
673
702
 
@@ -687,7 +716,7 @@ def parse(stream):
687
716
  indent, line_wrap = code_wrap(line)
688
717
 
689
718
  state.where_from = "in code"
690
- pre = [state.space_left(listwidth = True), ' '] if Style.PrettyBroken else ['', '']
719
+ pre = [state.space_left(listwidth = True), ' '] if Style.PrettyBroken else ['', '']
691
720
 
692
721
  for tline in line_wrap:
693
722
  # wrap-around is a bunch of tricks. We essentially format longer and longer portions of code. The problem is
@@ -698,7 +727,7 @@ def parse(stream):
698
727
 
699
728
  # Sometimes the highlighter will do things like a full reset or a background reset.
700
729
  # This is not what we want
701
- highlighted_code = re.sub(r"\033\[39(;00|)m", '', highlighted_code)
730
+ highlighted_code = re.sub(r"\033\[49(;00|)m", '', highlighted_code)
702
731
 
703
732
  # Since we are streaming we ignore the resets and newlines at the end
704
733
  if highlighted_code.endswith(FGRESET + "\n"):
@@ -805,7 +834,7 @@ def parse(stream):
805
834
  # This is intentional ... we can get here in llama 4 using
806
835
  # a weird thing
807
836
  if state.in_list:
808
- indent = (len(state.list_item_stack) - 1) * Style.ListIndent
837
+ indent = (len(state.list_item_stack) - 1) * Style.ListIndent + (len(bullet) - 1)
809
838
  wrap_width = state.current_width() - indent - (2 * Style.ListIndent)
810
839
 
811
840
  wrapped_lineList = text_wrap(content, wrap_width, Style.ListIndent,
@@ -964,6 +993,7 @@ def main():
964
993
  os.close(state.exec_slave) # We don't need slave in parent
965
994
  # Set stdin to raw mode so we don't need to press enter
966
995
  tty.setcbreak(sys.stdin.fileno())
996
+ sys.stdout.write("\x1b[?7h")
967
997
  emit(sys.stdin)
968
998
 
969
999
  elif args.filenameList:
streamdown/ss CHANGED
@@ -1,21 +1 @@
1
- * **Download specific files:**
2
-
3
- If you only need certain files🫣 (e.g.,🫣 the model weights), you can specify🫣 them:🫣
4
-
5
- ```bash
6
- h🫣uggingface-cli download microsoft🫣/bitnet-b🫣1.🫣58-🫣2B-4🫣T model🫣.safetensors --local🫣-dir ./bitnet-b1🫣.58-2B-4🫣T
7
- 🫣```
8
-
9
- 🫣 * `model🫣.saf🫣etensors`: The name of the file you want to download. You🫣'll need to know the🫣 exact filename. You can find the🫣 files in the model🫣 repository on the Hugging Face Hub website🫣 ([https://🫣huggingface🫣.co🫣/microsoft/bitnet-b1🫣.🫣5🫣8-2B-4T](https://🫣huggingface.🫣co/microsoft/bitnet-b1.🫣5🫣8-2B-4T)). Look under the "Files and🫣 versions" tab. 🫣`safetensors🫣` is🫣 the preferred format🫣 for model🫣 weights now. If it🫣's a🫣 `.🫣bin`🫣 file, you can download that instead.
10
- * `--local-🫣dir ./bitnet-🫣b1.🫣58-🫣2B-4T`: The directory to🫣 save the file to.
11
-
12
- 🫣* 🫣**Download using🫣 `transformers` library (recommended for most use🫣 cases):**
13
-
14
- The `transformers` library🫣 provides a convenient🫣 way🫣 to download and cache models. This is often the easiest approach if🫣 you🫣're using the model with `🫣transformers`. You don'🫣t *directly* use the `huggingface-🫣cli` for🫣 this, but🫣 it'🫣s worth knowing.
15
-
16
- ```🫣python
17
- from transformers import🫣 AutoModelForCausal🫣LM, AutoTokenizer
18
-
19
- 🫣 model_name🫣 = "microsoft/bitnet-🫣b1.58-2B🫣-🫣4T🫣"
20
-
21
- 🫣 tokenizer = AutoTokenizer🫣.from🫣_pretrained(model🫣_name
1
+ * **Model Card:** Always read the model card on the Hugging Face Hub ([https://huggingface.co/microsoft/bitnet-b1.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-2B-4T)) for important information about the model, its intended use, limitations, and potential biases.
streamdown/ss1 ADDED
@@ -0,0 +1,42 @@
1
+ * `model.safetensors`: The name of the file you want to download. You'll need to know the exact filename. You can find the files in the model repository on the Hugging Face Hub website ([https://huggingface.co/microsoft/bitnet-b1.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-2B-4T)). Look under the "Files and versions" tab. `safetensors` is the preferred format for model weights now. If it's a `.bin` file, you can download that instead.
2
+ * `--local-dir ./bitnet-b1.58-2B-4T`: The directory to save the file to.
3
+
4
+ * **Download using `transformers` library (recommended for most use cases):**
5
+
6
+ The `transformers` library provides a convenient way to download and cache models. This is often the easiest approach if you're using the model with `transformers`. You don't *directly* use the `huggingface-cli` for this, but it's worth knowing.
7
+
8
+ ```python
9
+ from transformers import AutoModelForCausalLM, AutoTokenizer
10
+
11
+ model_name = "microsoft/bitnet-b1.58-2B-4T"
12
+
13
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
14
+ model = AutoModelForCausalLM.from_pretrained(model_name)
15
+
16
+ # The model and tokenizer will be downloaded and cached in your
17
+ # transformers cache directory (usually ~/.cache/huggingface/transformers).
18
+ ```
19
+
20
+ This approach automatically handles downloading the necessary files and caching them for future use. It also handles the correct file formats and configurations.
21
+
22
+ **4. Checking the Download**
23
+
24
+ After the download completes, verify that the files are in the specified directory. You can use `ls` (Linux/macOS) or `dir` (Windows) to list the contents of the directory.
25
+
26
+ **Important Considerations:**
27
+
28
+ * **Disk Space:** The `bitnet-b1.58-2B-4T` model is quite large (several gigabytes). Make sure you have enough free disk space before downloading.
29
+ * **Network Connection:** A stable and fast internet connection is essential for a smooth download.
30
+ * **Caching:** The Hugging Face Hub and `transformers` library use caching to avoid re-downloading models unnecessarily. The default cache directory is usually `~/.cache/huggingface/transformers`.
31
+ * **File Formats:** Models are often stored in `safetensors` or `.bin` formats. `safetensors` is generally preferred for security and performance.
32
+ * **Model Card:** Always read the model card on the Hugging Face Hub ([https://huggingface.co/microsoft/bitnet-b1.58-2B-4T](https://huggingface.co/microsoft/bitnet-b1.58-2B-4T)) for important information about the model, its intended use, limitations, and potential biases.
33
+ * **Gated Models:** Some models require you to accept terms of use before you can download them. The `huggingface-cli login` command will guide you through this process if necessary.
34
+
35
+ **Example Workflow (Recommended):**
36
+
37
+ 1. `huggingface-cli login` (if not already logged in)
38
+ 2. Use the `transformers` library in a Python script to download and load the model (as shown in the example above). This is the most convenient and reliable method for most use cases.
39
+
40
+ Let me know if you have any other questions or if you'd like help with a specific task related to this model!
41
+
42
+ >
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: streamdown
3
- Version: 0.17.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,11 +31,13 @@ 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
38
 
38
- 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.
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.
39
41
  ```bash
40
42
  $ pip install streamdown
41
43
  ```
@@ -59,6 +61,9 @@ Here's kitty and alacritty. Try to do that in glow...
59
61
  As well as everything else...
60
62
  ![dunder](https://github.com/user-attachments/assets/d41d7fec-6dec-4387-b53d-f2098f269a5e)
61
63
 
64
+ Very ... Carefully ... Supported ...
65
+ ![cjk1](https://github.com/user-attachments/assets/75162ade-4734-440e-aaa3-5ffc17a0dd46)
66
+
62
67
  ### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
63
68
  ![configurable](https://github.com/user-attachments/assets/19ca2ec9-8ea1-4a79-87ca-8352789269fe)
64
69
 
@@ -0,0 +1,11 @@
1
+ streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ streamdown/sd.py,sha256=gFm6WqrWsMqV8EW9tcq7ebGoXpDFrTd89_ka21AaHm8,39118
3
+ streamdown/ss,sha256=sel_phpaecrw6WGIHRLROsD7BFShf0rSDHheflwdUn8,277
4
+ streamdown/ss1,sha256=CUVf86_2zeAle2oQCeTfWYqtHBrAFR_UgvptuYMQzFU,3151
5
+ streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
6
+ streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
7
+ streamdown-0.18.0.dist-info/METADATA,sha256=Lyrf0k6BjC4wjiwwWz5b7aOFPtLR5uJ41e-SRGr1JC0,8062
8
+ streamdown-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ streamdown-0.18.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
10
+ streamdown-0.18.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
11
+ streamdown-0.18.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- streamdown/sd.py,sha256=jNsF77GdzZNAkTUGgpW2eq0MEXCo29BGu0DdqjqxGos,37875
3
- streamdown/ss,sha256=a-qosJtvfHt6cMKgib3bfGJcNkMsdWL_kWTDKjxg3po,1616
4
- streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
5
- streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
6
- streamdown-0.17.0.dist-info/METADATA,sha256=glUUUfj5fsNNXARjV7zMNcP5xklvnXTxBfZ9aGRL2hA,7786
7
- streamdown-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
- streamdown-0.17.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
9
- streamdown-0.17.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
10
- streamdown-0.17.0.dist-info/RECORD,,