streamdown 0.19.0__py3-none-any.whl → 0.20.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 +88 -23
- {streamdown-0.19.0.dist-info → streamdown-0.20.0.dist-info}/METADATA +24 -15
- {streamdown-0.19.0.dist-info → streamdown-0.20.0.dist-info}/RECORD +6 -6
- {streamdown-0.19.0.dist-info → streamdown-0.20.0.dist-info}/WHEEL +0 -0
- {streamdown-0.19.0.dist-info → streamdown-0.20.0.dist-info}/entry_points.txt +0 -0
- {streamdown-0.19.0.dist-info → streamdown-0.20.0.dist-info}/licenses/LICENSE.MIT +0 -0
streamdown/sd.py
CHANGED
|
@@ -65,7 +65,7 @@ Symbol = { H = 1.00, S = 1.00, V = 1.50 }
|
|
|
65
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
|
-
Syntax = "
|
|
68
|
+
Syntax = "dracula"
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
71
|
def ensure_config_file():
|
|
@@ -92,6 +92,7 @@ BOLD = ["\033[1m", "\033[22m"]
|
|
|
92
92
|
UNDERLINE = ["\033[4m", "\033[24m"]
|
|
93
93
|
ITALIC = ["\033[3m", "\033[23m"]
|
|
94
94
|
STRIKEOUT = ["\033[9m", "\033[29m"]
|
|
95
|
+
LINK = ["\033]8;;", "\033]8;;\033\\"]
|
|
95
96
|
SUPER = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079 ]
|
|
96
97
|
|
|
97
98
|
ESCAPE = r"\033\[[0-9;]*[mK]"
|
|
@@ -100,7 +101,7 @@ KEYCODE_RE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
|
100
101
|
|
|
101
102
|
visible = lambda x: re.sub(ANSIESCAPE, "", x)
|
|
102
103
|
# cjk characters are double width
|
|
103
|
-
visible_length = lambda x: len(visible(x)) +
|
|
104
|
+
visible_length = lambda x: len(visible(x)) + dbl_count(x)
|
|
104
105
|
extract_ansi_codes = lambda text: re.findall(ESCAPE, text)
|
|
105
106
|
remove_ansi = lambda line, codeList: reduce(lambda line, code: line.replace(code, ''), codeList, line)
|
|
106
107
|
|
|
@@ -116,8 +117,8 @@ def savebrace():
|
|
|
116
117
|
if state.Savebrace and state.code_buffer_raw:
|
|
117
118
|
path = os.path.join(tempfile.gettempdir(), "sd", 'savebrace')
|
|
118
119
|
with open(path, "a") as f:
|
|
119
|
-
f.write(state.code_buffer_raw)
|
|
120
|
-
|
|
120
|
+
f.write(state.code_buffer_raw + "\x00")
|
|
121
|
+
f.flush()
|
|
121
122
|
|
|
122
123
|
class Goto(Exception):
|
|
123
124
|
pass
|
|
@@ -208,7 +209,7 @@ class ParseState:
|
|
|
208
209
|
return offset + (state.current_width(listwidth = True) if Style.PrettyBroken else self.WidthFull)
|
|
209
210
|
|
|
210
211
|
def current_width(self, listwidth = False):
|
|
211
|
-
return self.Width - (len(visible(self.space_left(listwidth)))
|
|
212
|
+
return self.Width - (len(visible(self.space_left(listwidth))))
|
|
212
213
|
|
|
213
214
|
def space_left(self, listwidth = False):
|
|
214
215
|
pre = ' ' * (len(state.list_item_stack)) * Style.ListIndent if listwidth else ''
|
|
@@ -216,6 +217,20 @@ class ParseState:
|
|
|
216
217
|
|
|
217
218
|
state = ParseState()
|
|
218
219
|
|
|
220
|
+
def override_background(style_name, background_color):
|
|
221
|
+
base_style = get_style_by_name(style_name)
|
|
222
|
+
base_style.background_color = background_color
|
|
223
|
+
for i in base_style:
|
|
224
|
+
i[1]['bgcolor'] = background_color
|
|
225
|
+
for i,v in base_style.styles.items():
|
|
226
|
+
if v and 'bg' in v:
|
|
227
|
+
base_style.styles[i] = re.sub(r'bg:[^ ]*', '', base_style.styles[i] )
|
|
228
|
+
for k,v in base_style._styles.items():
|
|
229
|
+
if v[4] != '':
|
|
230
|
+
v[4] = ''
|
|
231
|
+
|
|
232
|
+
return base_style
|
|
233
|
+
|
|
219
234
|
def format_table(rowList):
|
|
220
235
|
num_cols = len(rowList)
|
|
221
236
|
row_height = 0
|
|
@@ -224,15 +239,20 @@ def format_table(rowList):
|
|
|
224
239
|
# Calculate max width per column (integer division)
|
|
225
240
|
# Subtract num_cols + 1 for the vertical borders '│'
|
|
226
241
|
available_width = state.current_width() - (num_cols + 1)
|
|
227
|
-
|
|
242
|
+
|
|
243
|
+
width_base = available_width // num_cols
|
|
244
|
+
width_mod = available_width % num_cols
|
|
245
|
+
|
|
246
|
+
col_width_list = [width_base + (1 if i < width_mod else 0) for i in range(num_cols)]
|
|
228
247
|
bg_color = Style.Mid if state.in_table == Style.Head else Style.Dark
|
|
229
248
|
state.bg = f"{BG}{bg_color}"
|
|
230
249
|
|
|
231
250
|
# First Pass: Wrap text and calculate row heights
|
|
232
251
|
# Note this is where every cell is formatted so if
|
|
233
252
|
# you are styling, do it before here!
|
|
234
|
-
for
|
|
235
|
-
|
|
253
|
+
for ix in range(len(rowList)):
|
|
254
|
+
row = rowList[ix]
|
|
255
|
+
wrapped_cell = text_wrap(row, width=col_width_list[ix], force_truncate=True)
|
|
236
256
|
|
|
237
257
|
# Ensure at least one line, even for empty cells
|
|
238
258
|
if not wrapped_cell:
|
|
@@ -248,13 +268,14 @@ def format_table(rowList):
|
|
|
248
268
|
line_segments = []
|
|
249
269
|
|
|
250
270
|
# Now we want to snatch this row index from all our cells
|
|
251
|
-
for
|
|
271
|
+
for iy in range(len(wrapped_cellList)):
|
|
272
|
+
cell = wrapped_cellList[iy]
|
|
252
273
|
segment = ''
|
|
253
274
|
if ix < len(cell):
|
|
254
275
|
segment = cell[ix]
|
|
255
276
|
|
|
256
277
|
# Margin logic is correctly indented here
|
|
257
|
-
margin_needed =
|
|
278
|
+
margin_needed = col_width_list[iy] - visible_length(segment)
|
|
258
279
|
margin_segment = segment + (" " * max(0, margin_needed))
|
|
259
280
|
line_segments.append(f"{BG}{bg_color}{extra} {margin_segment}")
|
|
260
281
|
|
|
@@ -269,7 +290,7 @@ def emit_h(level, text):
|
|
|
269
290
|
text = line_format(text)
|
|
270
291
|
spaces_to_center = (state.current_width() - visible_length(text)) / 2
|
|
271
292
|
if level == 1: #
|
|
272
|
-
return f"{state.space_left()}\n{state.space_left()}{BOLD[
|
|
293
|
+
return f"{state.space_left()}\n{state.space_left()}{BOLD[1]}{' ' * math.floor(spaces_to_center)}{text}{BOLD[1]}"
|
|
273
294
|
elif level == 2: ##
|
|
274
295
|
return 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}"
|
|
275
296
|
elif level == 3: ###
|
|
@@ -338,7 +359,16 @@ def ansi_collapse(codelist, inp):
|
|
|
338
359
|
|
|
339
360
|
def split_text(text):
|
|
340
361
|
return [x for x in re.split(
|
|
341
|
-
r'(?<=[
|
|
362
|
+
r'(?<=['
|
|
363
|
+
r'\u3000-\u303F'
|
|
364
|
+
r'\u4E00-\u9FFF'
|
|
365
|
+
r'\u3400-\u4DBF'
|
|
366
|
+
r'\uF900-\uFAFF'
|
|
367
|
+
r'])|(?=['
|
|
368
|
+
#r'\u4E00-\u9FFF'
|
|
369
|
+
r'\u3400-\u4DBF'
|
|
370
|
+
r'\uF900-\uFAFF'
|
|
371
|
+
r'])|\s+',
|
|
342
372
|
text
|
|
343
373
|
) if x]
|
|
344
374
|
|
|
@@ -347,7 +377,11 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
347
377
|
width = state.Width
|
|
348
378
|
|
|
349
379
|
# The empty word clears the buffer at the end.
|
|
350
|
-
|
|
380
|
+
formatted = line_format(text)
|
|
381
|
+
#print(bytes(formatted, 'utf-8'), formatted)
|
|
382
|
+
words = split_text(formatted) + [""]
|
|
383
|
+
#print([bytes(i, 'utf-8') for i in words])
|
|
384
|
+
|
|
351
385
|
lines = []
|
|
352
386
|
current_line = ""
|
|
353
387
|
current_style = []
|
|
@@ -378,6 +412,10 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
378
412
|
margin = max(0, width - visible_length(line_content))
|
|
379
413
|
|
|
380
414
|
if line_content.strip() != "":
|
|
415
|
+
# We make absolutely positively sure beyond any doubt
|
|
416
|
+
# that we have closed our hyperlink OSC
|
|
417
|
+
if LINK[0] in line_content:
|
|
418
|
+
line_content += LINK[1]
|
|
381
419
|
lines.append(line_content + state.bg + ' ' * margin)
|
|
382
420
|
|
|
383
421
|
current_line = (" " * indent) + "".join(current_style) + word
|
|
@@ -395,14 +433,23 @@ def text_wrap(text, width = -1, indent = 0, first_line_prefix="", subsequent_lin
|
|
|
395
433
|
|
|
396
434
|
return lines
|
|
397
435
|
|
|
436
|
+
def dbl_count(s):
|
|
437
|
+
dbl_re = re.compile(
|
|
438
|
+
r'[\u2e80-\u2eff\u3000-\u303f\u3400-\u4dbf'
|
|
439
|
+
r'\U00004e00-\U00009fff\U0001f300-\U0001f6ff'
|
|
440
|
+
r'\U0001f900-\U0001f9ff\U0001fa70-\U0001faff]',
|
|
441
|
+
re.UNICODE
|
|
442
|
+
)
|
|
443
|
+
return len(dbl_re.findall(visible(s)))
|
|
444
|
+
|
|
398
445
|
def cjk_count(s):
|
|
399
446
|
cjk_re = re.compile(
|
|
400
447
|
r'[\u4E00-\u9FFF' # CJK Unified Ideographs
|
|
401
|
-
r'
|
|
402
|
-
r'
|
|
403
|
-
r'
|
|
404
|
-
r'
|
|
405
|
-
r'
|
|
448
|
+
r'\u3400-\u4DBF' # CJK Unified Ideographs Extension A
|
|
449
|
+
r'\uF900-\uFAFF' # CJK Compatibility Ideographs
|
|
450
|
+
r'\uFF00-\uFFEF' # CJK Compatibility Punctuation
|
|
451
|
+
r'\u3000-\u303F' # CJK Symbols and Punctuation
|
|
452
|
+
r'\U0002F800-\U0002FA1F]' # CJK Compatibility Ideographs Supplement
|
|
406
453
|
)
|
|
407
454
|
|
|
408
455
|
return len(cjk_re.findall(visible(s)))
|
|
@@ -427,7 +474,7 @@ def line_format(line):
|
|
|
427
474
|
def process_links(match):
|
|
428
475
|
description = match.group(1)
|
|
429
476
|
url = match.group(2)
|
|
430
|
-
return f'
|
|
477
|
+
return f'{LINK[0]}{url}\033\\{Style.Link}{description}{UNDERLINE[1]}{LINK[1]}{FGRESET}'
|
|
431
478
|
|
|
432
479
|
line = re.sub(r"\!\[([^\]]*)\]\(([^\)]+)\)", process_images, line)
|
|
433
480
|
line = re.sub(r"\[([^\]]+)\]\(([^\)]+)\)", process_links, line)
|
|
@@ -444,19 +491,24 @@ def line_format(line):
|
|
|
444
491
|
# This trick makes sure that things like `` ` `` render right.
|
|
445
492
|
if "`" in token and (not state.inline_code or state.inline_code == token):
|
|
446
493
|
if state.inline_code:
|
|
494
|
+
if ' ' in state.inline_code:
|
|
495
|
+
savebrace()
|
|
447
496
|
state.inline_code = False
|
|
448
497
|
else:
|
|
449
498
|
state.inline_code = token
|
|
499
|
+
state.code_buffer_raw = ''
|
|
450
500
|
|
|
451
501
|
if state.inline_code:
|
|
452
502
|
result += f'{BG}{Style.Mid}'
|
|
453
503
|
else:
|
|
454
504
|
result += state.bg
|
|
505
|
+
state.code_buffer_raw = ''
|
|
455
506
|
|
|
456
507
|
# This is important here because we ignore formatting
|
|
457
508
|
# inside of our code block.
|
|
458
509
|
elif state.inline_code:
|
|
459
510
|
result += token
|
|
511
|
+
state.code_buffer_raw += token
|
|
460
512
|
|
|
461
513
|
elif token == '~~' and (state.in_strikeout or not_text(prev_token)):
|
|
462
514
|
state.in_strikeout = not state.in_strikeout
|
|
@@ -559,6 +611,7 @@ def parse(stream):
|
|
|
559
611
|
continue
|
|
560
612
|
|
|
561
613
|
state.buffer = b''
|
|
614
|
+
"""
|
|
562
615
|
# Run through the plugins first
|
|
563
616
|
res = latex.Plugin(line, state, Style)
|
|
564
617
|
if res is True:
|
|
@@ -569,6 +622,7 @@ def parse(stream):
|
|
|
569
622
|
for row in res:
|
|
570
623
|
yield row
|
|
571
624
|
continue
|
|
625
|
+
"""
|
|
572
626
|
|
|
573
627
|
# running this here avoids stray |
|
|
574
628
|
block_match = re.match(r"^\s*((>\s*)+|<.?think>)", line)
|
|
@@ -639,7 +693,6 @@ def parse(stream):
|
|
|
639
693
|
state.code_language = 'Bash'
|
|
640
694
|
|
|
641
695
|
if state.in_code:
|
|
642
|
-
savebrace()
|
|
643
696
|
state.code_buffer = state.code_buffer_raw = ""
|
|
644
697
|
state.code_gen = 0
|
|
645
698
|
state.code_first_line = True
|
|
@@ -671,6 +724,7 @@ def parse(stream):
|
|
|
671
724
|
open(os.path.join(state.scrape, f"file_{state.scrape_ix}.{ext}"), 'w').write(state.code_buffer_raw)
|
|
672
725
|
state.scrape_ix += 1
|
|
673
726
|
|
|
727
|
+
savebrace()
|
|
674
728
|
state.code_language = None
|
|
675
729
|
state.code_indent = 0
|
|
676
730
|
code_type = state.in_code
|
|
@@ -698,10 +752,10 @@ def parse(stream):
|
|
|
698
752
|
state.code_first_line = False
|
|
699
753
|
try:
|
|
700
754
|
lexer = get_lexer_by_name(state.code_language)
|
|
701
|
-
custom_style =
|
|
755
|
+
custom_style = override_background(Style.Syntax, ansi2hex(Style.Dark))
|
|
702
756
|
except pygments.util.ClassNotFound:
|
|
703
757
|
lexer = get_lexer_by_name("Bash")
|
|
704
|
-
custom_style =
|
|
758
|
+
custom_style = override_background("default", ansi2hex(Style.Dark))
|
|
705
759
|
|
|
706
760
|
formatter = TerminalTrueColorFormatter(style=custom_style)
|
|
707
761
|
if line.startswith(' ' * state.code_indent):
|
|
@@ -720,6 +774,7 @@ def parse(stream):
|
|
|
720
774
|
else:
|
|
721
775
|
continue
|
|
722
776
|
|
|
777
|
+
highlighted_code = highlight(line, lexer, formatter)
|
|
723
778
|
indent, line_wrap = code_wrap(line)
|
|
724
779
|
|
|
725
780
|
state.where_from = "in code"
|
|
@@ -731,15 +786,18 @@ def parse(stream):
|
|
|
731
786
|
# then naively search back until our visible_lengths() match. This is not fast and there's certainly smarter
|
|
732
787
|
# ways of doing it but this thing is way trickery than you think
|
|
733
788
|
highlighted_code = highlight(state.code_buffer + tline, lexer, formatter)
|
|
789
|
+
#print("(",highlighted_code,")")
|
|
734
790
|
|
|
735
791
|
# Sometimes the highlighter will do things like a full reset or a background reset.
|
|
736
792
|
# This is not what we want
|
|
737
|
-
highlighted_code = re.sub(r"\033\[
|
|
793
|
+
highlighted_code = re.sub(r"\033\[[34]9(;00|)m", '', highlighted_code)
|
|
738
794
|
|
|
739
795
|
# Since we are streaming we ignore the resets and newlines at the end
|
|
740
796
|
if highlighted_code.endswith(FGRESET + "\n"):
|
|
741
797
|
highlighted_code = highlighted_code[: -(1 + len(FGRESET))]
|
|
742
798
|
|
|
799
|
+
#print(bytes(highlighted_code, 'utf-8'))
|
|
800
|
+
|
|
743
801
|
# turns out highlight will eat leading newlines on empty lines
|
|
744
802
|
vislen = visible_length(state.code_buffer.lstrip())
|
|
745
803
|
|
|
@@ -927,6 +985,11 @@ def emit(inp):
|
|
|
927
985
|
if len(buffer):
|
|
928
986
|
print(buffer.pop(0), file=sys.stdout, end="", flush=True)
|
|
929
987
|
|
|
988
|
+
def ansi2hex(ansi_code):
|
|
989
|
+
parts = ansi_code.strip('m').split(";")
|
|
990
|
+
r, g, b = map(int, parts)
|
|
991
|
+
return f"#{r:02x}{g:02x}{b:02x}"
|
|
992
|
+
|
|
930
993
|
def apply_multipliers(name, H, S, V):
|
|
931
994
|
m = _style.get(name)
|
|
932
995
|
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"]))
|
|
@@ -1042,6 +1105,8 @@ def main():
|
|
|
1042
1105
|
os.close(state.exec_master)
|
|
1043
1106
|
if state.exec_sub:
|
|
1044
1107
|
state.exec_sub.wait()
|
|
1108
|
+
|
|
1109
|
+
print(RESET, end="")
|
|
1045
1110
|
sys.exit(state.exit)
|
|
1046
1111
|
|
|
1047
1112
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamdown
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.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
|
|
@@ -36,44 +36,51 @@ Description-Content-Type: text/markdown
|
|
|
36
36
|
</p>
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
Streamdown works with [simonw's llm](https://github.com/simonw/llm)
|
|
40
|
-
|
|
39
|
+
Streamdown works with any streaming markdown such as [simonw's llm](https://github.com/simonw/llm) or even something basic like curl.
|
|
40
|
+
|
|
41
|
+
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.
|
|
41
42
|
```bash
|
|
42
43
|
$ pip install streamdown
|
|
43
44
|
```
|
|
44
45
|

|
|
45
46
|
|
|
46
47
|
### Provides clean copyable code for long code lines
|
|
47
|
-
Other renderers inject line breaks when copying code that wraps around.
|
|
48
|
+
Other renderers inject line breaks when copying code that wraps around. Streamdown's better and now you are too!
|
|
48
49
|

|
|
49
|
-
**Tip**: You can make things prettier if you don't mind if this guarantee is broken. See the `PrettyBroken` flag below!
|
|
50
|
+
**Tip**: You can make things prettier if you don't mind if this guarantee is broken. See the `PrettyBroken` flag below! (There's still 2 other convenient ways of getting code blocks out.)
|
|
50
51
|
|
|
51
52
|
### Supports images
|
|
52
53
|
Here's kitty and alacritty.
|
|
53
54
|

|
|
54
55
|
|
|
55
56
|
### Supports hyperlinks (OSC 8) and clipboard (OSC 52)
|
|
57
|
+
The optional `Clipboard` feature puts the final codeblock into your clipboard. See below for details.
|
|
58
|
+
|
|
56
59
|
[links.webm](https://github.com/user-attachments/assets/a5f71791-7c58-4183-ad3b-309f470c08a3)
|
|
57
60
|
|
|
58
|
-
###
|
|
59
|
-
|
|
61
|
+
### As well as everything else...
|
|
62
|
+
Here's the `Savebrace` feature with `screen-query` and `sd-picker` from [llmehelp](https://github.com/kristopolous/llmehelp). You can have an ongoing conversation in tmux with your terminal session. Then use popups and fzf to insert command or coding blocks all with a keystroke.
|
|
63
|
+
|
|
64
|
+
This allows you to interactively debug in a way that the agent doesn't just wander off doing silly things.
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-

|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
Compare how streamdown wraps
|
|
66
|
-
|
|
69
|
+
### ...It even supports CJK
|
|
70
|
+
Compare how streamdown wraps and spaces this tabular Chinese description of programming languages to other leading markdown renderers.
|
|
71
|
+
|
|
72
|
+
Only one generates the text without truncation. 很美!
|
|
73
|
+

|
|
67
74
|
|
|
68
75
|
### Colors are highly (and quickly) configurable for people who care a lot, or just a little.
|
|
69
76
|

|
|
70
77
|
|
|
71
|
-
### Has a [Plugin](https://github.com/kristopolous/Streamdown/tree/main/streamdown/plugins) system to extend the parser and
|
|
78
|
+
### Has a [Plugin](https://github.com/kristopolous/Streamdown/tree/main/streamdown/plugins) system to extend the parser and renderers.
|
|
72
79
|
For instance, here is the [latex plugin](https://github.com/kristopolous/Streamdown/blob/main/streamdown/plugins/latex.py) doing math inside a table:
|
|
73
80
|

|
|
74
81
|
|
|
75
82
|
|
|
76
|
-
##
|
|
83
|
+
## Configuration
|
|
77
84
|
|
|
78
85
|
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.
|
|
79
86
|
|
|
@@ -81,7 +88,9 @@ Here are the sections:
|
|
|
81
88
|
|
|
82
89
|
**`[style]`**
|
|
83
90
|
|
|
84
|
-
Defines the base Hue (H), Saturation (S), and Value (V) from which all other palette colors are derived.
|
|
91
|
+
Defines the base Hue (H), Saturation (S), and Value (V) from which all other palette colors are derived. This can also be specified at runtime via command line arguments. See below!
|
|
92
|
+
|
|
93
|
+
The default values are [at the beginning of the source](https://github.com/kristopolous/Streamdown/blob/main/streamdown/sd.py#L33).
|
|
85
94
|
|
|
86
95
|
* `HSV`: [ 0.0 - 1.0, 0.0 - 1.0, 0.0 - 1.0 ]
|
|
87
96
|
* `Dark`: Multipliers for background elements, code blocks.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
streamdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
streamdown/sd.py,sha256=
|
|
2
|
+
streamdown/sd.py,sha256=ggXlCGovA6cS3686BLnk0ISzf2xXrx7UwuKjwjs6RUM,41475
|
|
3
3
|
streamdown/ss,sha256=sel_phpaecrw6WGIHRLROsD7BFShf0rSDHheflwdUn8,277
|
|
4
4
|
streamdown/ss1,sha256=CUVf86_2zeAle2oQCeTfWYqtHBrAFR_UgvptuYMQzFU,3151
|
|
5
5
|
streamdown/plugins/README.md,sha256=KWqYELs9WkKJmuDzYv3cvPlZMkArsNCBUe4XDoTLjLA,1143
|
|
6
6
|
streamdown/plugins/latex.py,sha256=xZMGMdx_Sw4X1piZejXFHfEG9qazU4fGeceiMI0h13Y,648
|
|
7
|
-
streamdown-0.
|
|
8
|
-
streamdown-0.
|
|
9
|
-
streamdown-0.
|
|
10
|
-
streamdown-0.
|
|
11
|
-
streamdown-0.
|
|
7
|
+
streamdown-0.20.0.dist-info/METADATA,sha256=LpTl5FFiZJMk_9OC9Kqqq72satHDEHn19rGnJ93XSKY,8843
|
|
8
|
+
streamdown-0.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
streamdown-0.20.0.dist-info/entry_points.txt,sha256=HroKFsFMGf_h9PRTE96NjvjJQWupMW5TGP5RGUr1O_Q,74
|
|
10
|
+
streamdown-0.20.0.dist-info/licenses/LICENSE.MIT,sha256=SnY46EPirUsF20dZDR8HpyVgS2_4Tjxuc6f-4OdqO7U,1070
|
|
11
|
+
streamdown-0.20.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|