python-color-math 0.2.1__py3-none-any.whl → 0.2.12__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.
- color_math/__init__.py +1 -1
- color_math/adapters.py +35 -2
- color_math/colormath.default.json +57 -0
- color_math/config.py +184 -40
- color_math/converters/block.py +5 -0
- color_math/converters/derivative.py +21 -4
- color_math/converters/generic.py +15 -1
- color_math/converters/matrix.py +3 -3
- color_math/converters/unicode_converter.py +279 -0
- color_math/custom_definitions.py +100 -0
- color_math/main.py +114 -25
- color_math/mcp_server.py +109 -70
- color_math/parsers/__init__.py +11 -1
- color_math/parsers/braket.py +43 -9
- color_math/parsers/constants.py +2 -2
- color_math/parsers/delimiters.py +225 -26
- color_math/parsers/differentials.py +4 -4
- color_math/parsers/frontmatter.py +168 -0
- color_math/parsers/markdown_scanner.py +16 -4
- color_math/parsers/math_parser.py +2 -2
- color_math/parsers/physics.py +44 -0
- color_math/parsers/taxonomy.py +13 -0
- color_math/parsers/units.py +15 -9
- color_math/parsers/variable_hash.py +4 -4
- color_math/tutorial.py +247 -10
- color_math/unicode.py +217 -0
- {python_color_math-0.2.1.dist-info → python_color_math-0.2.12.dist-info}/METADATA +105 -6
- python_color_math-0.2.12.dist-info/RECORD +56 -0
- python_color_math-0.2.1.dist-info/RECORD +0 -50
- {python_color_math-0.2.1.dist-info → python_color_math-0.2.12.dist-info}/WHEEL +0 -0
- {python_color_math-0.2.1.dist-info → python_color_math-0.2.12.dist-info}/entry_points.txt +0 -0
- {python_color_math-0.2.1.dist-info → python_color_math-0.2.12.dist-info}/licenses/LICENSE +0 -0
- {python_color_math-0.2.1.dist-info → python_color_math-0.2.12.dist-info}/top_level.txt +0 -0
color_math/__init__.py
CHANGED
color_math/adapters.py
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import copy
|
|
5
6
|
import json
|
|
6
7
|
import re
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from .config import ColorMathOptions
|
|
10
11
|
from .converters.block import convert_math_block, convert_text
|
|
12
|
+
from .parsers.frontmatter import detect_note_field
|
|
11
13
|
from .undo import uncolor_fragment, uncolor_text
|
|
12
14
|
from .utils.latex_helpers import read_comment_end, read_verb_end
|
|
13
15
|
|
|
@@ -50,6 +52,19 @@ def detect_format(path: Path | None, requested: str = "auto") -> str:
|
|
|
50
52
|
return "markdown"
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
def has_potential_math(text: str, format_name: str = "auto") -> bool:
|
|
56
|
+
"""Ultra-fast probe to bypass documents that cannot possibly contain math."""
|
|
57
|
+
if "$" in text:
|
|
58
|
+
return True
|
|
59
|
+
if r"\[" in text or r"\(" in text:
|
|
60
|
+
return True
|
|
61
|
+
if format_name in ("tex", "auto") and r"\begin{" in text:
|
|
62
|
+
return True
|
|
63
|
+
if format_name in ("anki", "auto") and ("[$]" in text or "[$$]" in text):
|
|
64
|
+
return True
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
53
68
|
def transform_document(
|
|
54
69
|
text: str,
|
|
55
70
|
format_name: str,
|
|
@@ -57,10 +72,28 @@ def transform_document(
|
|
|
57
72
|
palette: dict[str, str] | None = None,
|
|
58
73
|
options: ColorMathOptions | None = None,
|
|
59
74
|
) -> str:
|
|
60
|
-
if format_name == "markdown":
|
|
61
|
-
return uncolor_text(text) if undo else convert_text(text, palette=palette, options=options)
|
|
62
75
|
if format_name == "jupyter":
|
|
63
76
|
return _transform_notebook(text, undo, palette=palette, options=options)
|
|
77
|
+
|
|
78
|
+
if undo:
|
|
79
|
+
if r"\textcolor" not in text:
|
|
80
|
+
return text
|
|
81
|
+
elif not has_potential_math(text, format_name):
|
|
82
|
+
return text
|
|
83
|
+
|
|
84
|
+
if format_name == "markdown":
|
|
85
|
+
if undo:
|
|
86
|
+
return uncolor_text(text)
|
|
87
|
+
opts = options
|
|
88
|
+
detection = detect_note_field(text)
|
|
89
|
+
if detection.is_quantum:
|
|
90
|
+
if opts is None:
|
|
91
|
+
opts = ColorMathOptions(color_quantum_operators=True, field="quantum")
|
|
92
|
+
else:
|
|
93
|
+
opts = copy.copy(opts)
|
|
94
|
+
opts.color_quantum_operators = True
|
|
95
|
+
opts.field = "quantum"
|
|
96
|
+
return convert_text(text, palette=palette, options=opts)
|
|
64
97
|
if format_name == "anki":
|
|
65
98
|
return _transform_delimited(text, ANKI_DELIMITERS, undo, palette=palette, options=options)
|
|
66
99
|
if format_name == "tex":
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Python Color Math factory default configuration. This file serves as an immutable reference. To customize settings, edit '.colormath.json'. If '.colormath.json' has syntax errors or missing keys, Color Math safely falls back to these defaults.",
|
|
3
|
+
"theme": "default",
|
|
4
|
+
"colors": {
|
|
5
|
+
"main": "#7aa2f7",
|
|
6
|
+
"orange": "#e0af68",
|
|
7
|
+
"dot": "white",
|
|
8
|
+
"derivative": "#bb9af7",
|
|
9
|
+
"chain": "#9ece6a",
|
|
10
|
+
"upper": "#bb9af7",
|
|
11
|
+
"relation": "white",
|
|
12
|
+
"arrow": "#f7768e",
|
|
13
|
+
"set": "#bb9af7",
|
|
14
|
+
"spacing": "white",
|
|
15
|
+
"parameter": "#bb9af7",
|
|
16
|
+
"unit": "#73daca"
|
|
17
|
+
},
|
|
18
|
+
"options": {
|
|
19
|
+
"enable_taxonomy": true,
|
|
20
|
+
"rainbow_delimiters": true,
|
|
21
|
+
"variable_data_flow": false,
|
|
22
|
+
"color_units": true,
|
|
23
|
+
"color_differentials": true,
|
|
24
|
+
"color_braket": true,
|
|
25
|
+
"color_dimensionless": true,
|
|
26
|
+
"rainbow_bare_braces": false,
|
|
27
|
+
"highlight_unmatched_braces": false
|
|
28
|
+
},
|
|
29
|
+
"unicode": {
|
|
30
|
+
"greek_style": "plane1",
|
|
31
|
+
"convert_definite_integrals": false,
|
|
32
|
+
"convert_bounded_operators": false,
|
|
33
|
+
"convert_prose_to_unicode": false,
|
|
34
|
+
"convert_prose_to_latex": false
|
|
35
|
+
},
|
|
36
|
+
"_role_descriptions": {
|
|
37
|
+
"main": "Primary functions, terms, and outermost operations (e.g. f(x))",
|
|
38
|
+
"derivative": "Derivative marks and differentiated functions (e.g. f'(x))",
|
|
39
|
+
"chain": "Chain rule factors, inner differential stages (e.g. g'(x), y')",
|
|
40
|
+
"orange": "Big operators, sums, integrals, and limits (e.g. \\sum, \\int, \\lim)",
|
|
41
|
+
"dot": "Multiplication symbols, dots, and cross products (e.g. \\cdot, \\times)",
|
|
42
|
+
"relation": "Equals, inequalities, and comparison symbols (e.g. =, <, \\le)",
|
|
43
|
+
"arrow": "Implication and mapping arrows (e.g. \\to, \\implies)",
|
|
44
|
+
"set": "Set theory relations and operators (e.g. \\in, \\subset)",
|
|
45
|
+
"spacing": "LaTeX spacing and alignment commands (e.g. \\quad, \\,)",
|
|
46
|
+
"upper": "Matrix exponents, transposes, and top indices (e.g. A^T, M^{-1})",
|
|
47
|
+
"parameter": "Inner function parameters and indexed variables",
|
|
48
|
+
"unit": "Physical units and dimensions (e.g. m/s, \\mu m, ^\\circ C)"
|
|
49
|
+
},
|
|
50
|
+
"_unicode_descriptions": {
|
|
51
|
+
"greek_style": "Greek letter typography: 'plane1' (Mathematical Italic: 𝝍, 𝝰) or 'standard' (ψ, α)",
|
|
52
|
+
"convert_definite_integrals": "Convert bounded integrals like \\int_a^b to ∫_a^b (default: false to preserve vertical TeX limits)",
|
|
53
|
+
"convert_bounded_operators": "Convert bounded summations/products like \\sum_{i=1}^n to ∑_{i=1}^n (default: false)",
|
|
54
|
+
"convert_prose_to_unicode": "Convert LaTeX commands like \\psi to 𝜓 in regular prose outside math blocks (default: false to protect prose)",
|
|
55
|
+
"convert_prose_to_latex": "Convert Unicode symbols like 𝝍 back to \\psi in regular prose outside math blocks (default: false)"
|
|
56
|
+
}
|
|
57
|
+
}
|
color_math/config.py
CHANGED
|
@@ -18,6 +18,7 @@ DEFAULT_COLORS = {
|
|
|
18
18
|
"spacing": "white",
|
|
19
19
|
"parameter": "#bb9af7",
|
|
20
20
|
"unit": "#73daca",
|
|
21
|
+
"energy_operator": "#2ac3de",
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
THEMES: dict[str, dict[str, str]] = {
|
|
@@ -84,6 +85,39 @@ ROLE_DESCRIPTIONS: dict[str, str] = {
|
|
|
84
85
|
COLORS = dict(DEFAULT_COLORS)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
from .unicode import (
|
|
89
|
+
UNICODE_BIG_OPERATORS,
|
|
90
|
+
UNICODE_INTEGRALS,
|
|
91
|
+
UNICODE_RELATIONS,
|
|
92
|
+
UNICODE_ARROWS,
|
|
93
|
+
UNICODE_SETS,
|
|
94
|
+
UNICODE_MULTIPLICATION,
|
|
95
|
+
UNICODE_CONSTANTS,
|
|
96
|
+
UNICODE_VECTORS,
|
|
97
|
+
UNICODE_GREEK_LOWER_STANDARD,
|
|
98
|
+
UNICODE_GREEK_LOWER_PLANE1,
|
|
99
|
+
UNICODE_GREEK_UPPER_STANDARD,
|
|
100
|
+
UNICODE_GREEK_UPPER_PLANE1,
|
|
101
|
+
)
|
|
102
|
+
from .custom_definitions import (
|
|
103
|
+
CUSTOM_CONSTANTS,
|
|
104
|
+
CUSTOM_FUNCTIONS,
|
|
105
|
+
CUSTOM_OPERATORS,
|
|
106
|
+
CUSTOM_PARAMETERS,
|
|
107
|
+
CUSTOM_QUANTUM_OPERATORS,
|
|
108
|
+
CUSTOM_RELATIONS,
|
|
109
|
+
sanitize_definition,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
CUSTOM_BARE_FUNCTIONS: set[str] = set()
|
|
113
|
+
CUSTOM_MACRO_FUNCTIONS: set[str] = set()
|
|
114
|
+
for _raw_fn in CUSTOM_FUNCTIONS:
|
|
115
|
+
_macro, _bare = sanitize_definition(_raw_fn)
|
|
116
|
+
if _bare:
|
|
117
|
+
CUSTOM_BARE_FUNCTIONS.add(_bare)
|
|
118
|
+
if _macro:
|
|
119
|
+
CUSTOM_MACRO_FUNCTIONS.add(_macro)
|
|
120
|
+
|
|
87
121
|
BIG_OPERATORS = {
|
|
88
122
|
r"\sum",
|
|
89
123
|
r"\prod",
|
|
@@ -95,6 +129,8 @@ BIG_OPERATORS = {
|
|
|
95
129
|
r"\bigwedge",
|
|
96
130
|
r"\bigoplus",
|
|
97
131
|
r"\bigotimes",
|
|
132
|
+
*UNICODE_BIG_OPERATORS.values(),
|
|
133
|
+
*CUSTOM_OPERATORS,
|
|
98
134
|
}
|
|
99
135
|
|
|
100
136
|
|
|
@@ -103,6 +139,7 @@ INTEGRALS = {
|
|
|
103
139
|
r"\iint",
|
|
104
140
|
r"\iiint",
|
|
105
141
|
r"\oint",
|
|
142
|
+
*UNICODE_INTEGRALS.values(),
|
|
106
143
|
}
|
|
107
144
|
|
|
108
145
|
|
|
@@ -138,6 +175,8 @@ RELATIONS = {
|
|
|
138
175
|
"=",
|
|
139
176
|
"<",
|
|
140
177
|
">",
|
|
178
|
+
*UNICODE_RELATIONS.values(),
|
|
179
|
+
*CUSTOM_RELATIONS,
|
|
141
180
|
}
|
|
142
181
|
|
|
143
182
|
|
|
@@ -160,6 +199,7 @@ ARROWS = {
|
|
|
160
199
|
r"\Leftrightarrow",
|
|
161
200
|
r"\mapsto",
|
|
162
201
|
r"\to",
|
|
202
|
+
*UNICODE_ARROWS.values(),
|
|
163
203
|
}
|
|
164
204
|
|
|
165
205
|
|
|
@@ -182,6 +222,7 @@ SET_SYMBOLS = {
|
|
|
182
222
|
r"\in",
|
|
183
223
|
r"\cup",
|
|
184
224
|
r"\cap",
|
|
225
|
+
*UNICODE_SETS.values(),
|
|
185
226
|
}
|
|
186
227
|
|
|
187
228
|
|
|
@@ -199,6 +240,8 @@ MULTIPLICATION_SYMBOLS = {
|
|
|
199
240
|
r"\times",
|
|
200
241
|
"·",
|
|
201
242
|
"*",
|
|
243
|
+
"×",
|
|
244
|
+
"✕",
|
|
202
245
|
}
|
|
203
246
|
|
|
204
247
|
|
|
@@ -216,6 +259,7 @@ FUNCTION_COMMANDS = {
|
|
|
216
259
|
r"\sinh",
|
|
217
260
|
r"\tan",
|
|
218
261
|
r"\tanh",
|
|
262
|
+
*CUSTOM_MACRO_FUNCTIONS,
|
|
219
263
|
}
|
|
220
264
|
|
|
221
265
|
|
|
@@ -229,6 +273,7 @@ COLOR_COMMANDS = (
|
|
|
229
273
|
| SET_SYMBOLS
|
|
230
274
|
| SPACING_COMMANDS
|
|
231
275
|
| MULTIPLICATION_SYMBOLS
|
|
276
|
+
| set(CUSTOM_QUANTUM_OPERATORS)
|
|
232
277
|
)
|
|
233
278
|
|
|
234
279
|
|
|
@@ -256,6 +301,8 @@ MATH_CONSTANTS = {
|
|
|
256
301
|
r"\mathrm{e}",
|
|
257
302
|
r"\mathrm{i}",
|
|
258
303
|
r"\mathrm{j}",
|
|
304
|
+
*UNICODE_CONSTANTS.values(),
|
|
305
|
+
*CUSTOM_CONSTANTS,
|
|
259
306
|
}
|
|
260
307
|
|
|
261
308
|
|
|
@@ -377,14 +424,13 @@ EXTENDED_BARE_FUNCTIONS = frozenset({
|
|
|
377
424
|
})
|
|
378
425
|
|
|
379
426
|
FULL_BARE_FUNCTIONS = STANDARD_BARE_FUNCTIONS | EXTENDED_BARE_FUNCTIONS
|
|
380
|
-
ALL_BARE_FUNCTIONS = FULL_BARE_FUNCTIONS
|
|
381
|
-
BARE_FUNCTIONS =
|
|
427
|
+
ALL_BARE_FUNCTIONS = FULL_BARE_FUNCTIONS | CUSTOM_BARE_FUNCTIONS
|
|
428
|
+
BARE_FUNCTIONS = ALL_BARE_FUNCTIONS
|
|
382
429
|
|
|
383
430
|
|
|
384
431
|
def get_bare_functions(options: ColorMathOptions | None = None) -> frozenset[str]:
|
|
385
|
-
if options is
|
|
386
|
-
|
|
387
|
-
return FULL_BARE_FUNCTIONS
|
|
432
|
+
base = FULL_BARE_FUNCTIONS if (options is None or options.extended_functions) else STANDARD_BARE_FUNCTIONS
|
|
433
|
+
return frozenset(base | CUSTOM_BARE_FUNCTIONS)
|
|
388
434
|
|
|
389
435
|
|
|
390
436
|
MATH_ACCENTS = {
|
|
@@ -445,8 +491,18 @@ MATH_PARAMETERS = {
|
|
|
445
491
|
r"\Phi",
|
|
446
492
|
r"\Psi",
|
|
447
493
|
r"\Omega",
|
|
494
|
+
*UNICODE_GREEK_LOWER_STANDARD.values(),
|
|
495
|
+
*UNICODE_GREEK_LOWER_PLANE1.values(),
|
|
496
|
+
*UNICODE_GREEK_UPPER_STANDARD.values(),
|
|
497
|
+
*UNICODE_GREEK_UPPER_PLANE1.values(),
|
|
498
|
+
"𝜓",
|
|
499
|
+
"𝝍",
|
|
500
|
+
*CUSTOM_PARAMETERS,
|
|
448
501
|
}
|
|
449
502
|
|
|
503
|
+
NON_SLASH_MATH_CONSTANTS = {c for c in MATH_CONSTANTS if not c.startswith("\\")}
|
|
504
|
+
NON_SLASH_MATH_PARAMETERS = {c for c in MATH_PARAMETERS if not c.startswith("\\")}
|
|
505
|
+
|
|
450
506
|
|
|
451
507
|
MATH_FUNCTIONS = {
|
|
452
508
|
r"\sin",
|
|
@@ -541,6 +597,7 @@ MATH_FUNCTIONS = {
|
|
|
541
597
|
r"\dn",
|
|
542
598
|
r"\avg",
|
|
543
599
|
r"\len",
|
|
600
|
+
*CUSTOM_MACRO_FUNCTIONS,
|
|
544
601
|
}
|
|
545
602
|
|
|
546
603
|
|
|
@@ -588,6 +645,10 @@ class ColorMathOptions:
|
|
|
588
645
|
color_single_constants: bool = False
|
|
589
646
|
normalize_braces: bool = False
|
|
590
647
|
extended_functions: bool = True
|
|
648
|
+
color_quantum_operators: bool = False
|
|
649
|
+
rainbow_bare_braces: bool = False
|
|
650
|
+
highlight_unmatched_braces: bool = False
|
|
651
|
+
field: str = "math"
|
|
591
652
|
|
|
592
653
|
@classmethod
|
|
593
654
|
def extended(cls) -> ColorMathOptions:
|
|
@@ -604,6 +665,8 @@ class ColorMathOptions:
|
|
|
604
665
|
color_single_constants=True,
|
|
605
666
|
normalize_braces=False,
|
|
606
667
|
extended_functions=True,
|
|
668
|
+
rainbow_bare_braces=False,
|
|
669
|
+
highlight_unmatched_braces=True,
|
|
607
670
|
)
|
|
608
671
|
|
|
609
672
|
@classmethod
|
|
@@ -621,6 +684,8 @@ class ColorMathOptions:
|
|
|
621
684
|
color_single_constants=True,
|
|
622
685
|
normalize_braces=False,
|
|
623
686
|
extended_functions=True,
|
|
687
|
+
rainbow_bare_braces=True,
|
|
688
|
+
highlight_unmatched_braces=True,
|
|
624
689
|
)
|
|
625
690
|
|
|
626
691
|
|
|
@@ -665,11 +730,51 @@ def get_global_config_path() -> Path | None:
|
|
|
665
730
|
return None
|
|
666
731
|
|
|
667
732
|
|
|
668
|
-
|
|
733
|
+
DEFAULT_UNICODE_CONFIG: dict[str, object] = {
|
|
734
|
+
"greek_style": "plane1",
|
|
735
|
+
"convert_definite_integrals": False,
|
|
736
|
+
"convert_bounded_operators": False,
|
|
737
|
+
"convert_prose_to_unicode": False,
|
|
738
|
+
"convert_prose_to_latex": False,
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def get_bundled_default_config_path() -> Path:
|
|
743
|
+
"""Return path to bundled colormath.default.json."""
|
|
744
|
+
return Path(__file__).parent / "colormath.default.json"
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def get_bundled_default_config() -> dict[str, object]:
|
|
748
|
+
"""Load the factory defaults configuration object."""
|
|
749
|
+
default_path = get_bundled_default_config_path()
|
|
750
|
+
if default_path.exists():
|
|
751
|
+
try:
|
|
752
|
+
return json.loads(default_path.read_text(encoding="utf-8"))
|
|
753
|
+
except Exception:
|
|
754
|
+
pass
|
|
755
|
+
return {
|
|
756
|
+
"_comment": "Python Color Math factory default configuration",
|
|
757
|
+
"theme": "default",
|
|
758
|
+
"colors": dict(DEFAULT_COLORS),
|
|
759
|
+
"options": asdict(ColorMathOptions()),
|
|
760
|
+
"unicode": dict(DEFAULT_UNICODE_CONFIG),
|
|
761
|
+
"_role_descriptions": ROLE_DESCRIPTIONS,
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def load_config(
|
|
766
|
+
path: Path | None = None,
|
|
767
|
+
) -> tuple[dict[str, str], ColorMathOptions, dict[str, object]]:
|
|
669
768
|
"""
|
|
670
769
|
Load configuration from path, local '.colormath.json', or global user config.
|
|
671
|
-
|
|
770
|
+
Fault-tolerant: If the user file contains JSON syntax errors or invalid types,
|
|
771
|
+
a warning is emitted and it safely falls back to factory defaults without crashing.
|
|
772
|
+
Returns (palette_dict, ColorMathOptions, unicode_config_dict).
|
|
672
773
|
"""
|
|
774
|
+
palette = dict(DEFAULT_COLORS)
|
|
775
|
+
options = ColorMathOptions()
|
|
776
|
+
unicode_config = dict(DEFAULT_UNICODE_CONFIG)
|
|
777
|
+
|
|
673
778
|
target = path
|
|
674
779
|
if target is None:
|
|
675
780
|
local_candidate = Path(".colormath.json")
|
|
@@ -681,48 +786,87 @@ def load_config(path: Path | None = None) -> tuple[dict[str, str], ColorMathOpti
|
|
|
681
786
|
target = global_candidate
|
|
682
787
|
|
|
683
788
|
if target is None or not target.exists():
|
|
684
|
-
return
|
|
789
|
+
return palette, options, unicode_config
|
|
685
790
|
|
|
686
791
|
try:
|
|
687
792
|
data = json.loads(target.read_text(encoding="utf-8"))
|
|
793
|
+
if not isinstance(data, dict):
|
|
794
|
+
sys.stderr.write(
|
|
795
|
+
f"color-math warning: config file '{target}' must contain a JSON object. "
|
|
796
|
+
"Safely falling back to default configuration.\n"
|
|
797
|
+
)
|
|
798
|
+
return palette, options, unicode_config
|
|
688
799
|
except Exception as err:
|
|
689
|
-
|
|
800
|
+
sys.stderr.write(
|
|
801
|
+
f"color-math warning: failed to parse config file '{target}': {err}. "
|
|
802
|
+
"Safely falling back to default configuration.\n"
|
|
803
|
+
)
|
|
804
|
+
return palette, options, unicode_config
|
|
805
|
+
|
|
806
|
+
# 1. Apply theme
|
|
807
|
+
if "theme" in data and isinstance(data["theme"], str):
|
|
808
|
+
try:
|
|
809
|
+
palette.update(get_theme(data["theme"]))
|
|
810
|
+
except ValueError:
|
|
811
|
+
pass
|
|
812
|
+
|
|
813
|
+
# 2. Apply color overrides
|
|
814
|
+
if "colors" in data and isinstance(data["colors"], dict):
|
|
815
|
+
for k, v in data["colors"].items():
|
|
816
|
+
if isinstance(k, str) and isinstance(v, str) and k in DEFAULT_COLORS:
|
|
817
|
+
palette[k] = v
|
|
818
|
+
|
|
819
|
+
# 3. Apply engine options
|
|
820
|
+
if "options" in data and isinstance(data["options"], dict):
|
|
821
|
+
raw_options = data["options"]
|
|
822
|
+
options = ColorMathOptions(
|
|
823
|
+
enable_taxonomy=bool(raw_options.get("enable_taxonomy", options.enable_taxonomy)),
|
|
824
|
+
rainbow_delimiters=bool(raw_options.get("rainbow_delimiters", options.rainbow_delimiters)),
|
|
825
|
+
variable_data_flow=bool(raw_options.get("variable_data_flow", options.variable_data_flow)),
|
|
826
|
+
color_units=bool(raw_options.get("color_units", options.color_units)),
|
|
827
|
+
color_differentials=bool(raw_options.get("color_differentials", options.color_differentials)),
|
|
828
|
+
color_braket=bool(raw_options.get("color_braket", options.color_braket)),
|
|
829
|
+
color_dimensionless=bool(raw_options.get("color_dimensionless", options.color_dimensionless)),
|
|
830
|
+
color_alignment=bool(raw_options.get("color_alignment", options.color_alignment)),
|
|
831
|
+
color_single_constants=bool(raw_options.get("color_single_constants", options.color_single_constants)),
|
|
832
|
+
normalize_braces=bool(raw_options.get("normalize_braces", options.normalize_braces)),
|
|
833
|
+
extended_functions=bool(raw_options.get("extended_functions", options.extended_functions)),
|
|
834
|
+
color_quantum_operators=bool(raw_options.get("color_quantum_operators", options.color_quantum_operators)),
|
|
835
|
+
rainbow_bare_braces=bool(raw_options.get("rainbow_bare_braces", options.rainbow_bare_braces)),
|
|
836
|
+
highlight_unmatched_braces=bool(raw_options.get("highlight_unmatched_braces", options.highlight_unmatched_braces)),
|
|
837
|
+
field=str(raw_options.get("field", options.field)),
|
|
838
|
+
)
|
|
690
839
|
|
|
691
|
-
|
|
692
|
-
if isinstance(data, dict):
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if "
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
rainbow_delimiters=bool(raw_options.get("rainbow_delimiters", False)),
|
|
707
|
-
variable_data_flow=bool(raw_options.get("variable_data_flow", False)),
|
|
708
|
-
color_units=bool(raw_options.get("color_units", False)),
|
|
709
|
-
color_differentials=bool(raw_options.get("color_differentials", False)),
|
|
710
|
-
color_braket=bool(raw_options.get("color_braket", False)),
|
|
711
|
-
color_dimensionless=bool(raw_options.get("color_dimensionless", False)),
|
|
712
|
-
)
|
|
713
|
-
return palette, options
|
|
840
|
+
# 4. Apply unicode options
|
|
841
|
+
if "unicode" in data and isinstance(data["unicode"], dict):
|
|
842
|
+
raw_unicode = data["unicode"]
|
|
843
|
+
if "greek_style" in raw_unicode and raw_unicode["greek_style"] in ("plane1", "standard"):
|
|
844
|
+
unicode_config["greek_style"] = raw_unicode["greek_style"]
|
|
845
|
+
if "convert_definite_integrals" in raw_unicode:
|
|
846
|
+
unicode_config["convert_definite_integrals"] = bool(raw_unicode["convert_definite_integrals"])
|
|
847
|
+
if "convert_bounded_operators" in raw_unicode:
|
|
848
|
+
unicode_config["convert_bounded_operators"] = bool(raw_unicode["convert_bounded_operators"])
|
|
849
|
+
if "convert_prose_to_unicode" in raw_unicode:
|
|
850
|
+
unicode_config["convert_prose_to_unicode"] = bool(raw_unicode["convert_prose_to_unicode"])
|
|
851
|
+
if "convert_prose_to_latex" in raw_unicode:
|
|
852
|
+
unicode_config["convert_prose_to_latex"] = bool(raw_unicode["convert_prose_to_latex"])
|
|
853
|
+
|
|
854
|
+
return palette, options, unicode_config
|
|
714
855
|
|
|
715
856
|
|
|
716
857
|
def save_default_config(path: Path) -> Path:
|
|
717
|
-
"""Save the clean default configuration to path."""
|
|
718
|
-
content =
|
|
719
|
-
"_comment": "Python Color Math configuration file",
|
|
720
|
-
"theme": "default",
|
|
721
|
-
"colors": dict(DEFAULT_COLORS),
|
|
722
|
-
"options": asdict(ColorMathOptions()),
|
|
723
|
-
"_role_descriptions": ROLE_DESCRIPTIONS,
|
|
724
|
-
}
|
|
858
|
+
"""Save the clean default configuration to path, and copy the reference default file."""
|
|
859
|
+
content = get_bundled_default_config()
|
|
725
860
|
path.write_text(json.dumps(content, indent=2), encoding="utf-8")
|
|
861
|
+
|
|
862
|
+
# Also save .colormath.default.json alongside .colormath.json if appropriate
|
|
863
|
+
target_dir = path.parent
|
|
864
|
+
default_ref_path = target_dir / ".colormath.default.json"
|
|
865
|
+
try:
|
|
866
|
+
default_ref_path.write_text(json.dumps(content, indent=2), encoding="utf-8")
|
|
867
|
+
except OSError:
|
|
868
|
+
pass
|
|
869
|
+
|
|
726
870
|
return path
|
|
727
871
|
|
|
728
872
|
|
color_math/converters/block.py
CHANGED
|
@@ -55,6 +55,8 @@ def _has_semantic_options(options: ColorMathOptions | None) -> bool:
|
|
|
55
55
|
or options.color_differentials
|
|
56
56
|
or options.color_braket
|
|
57
57
|
or options.color_dimensionless
|
|
58
|
+
or options.color_quantum_operators
|
|
59
|
+
or options.field in ("quantum", "physics")
|
|
58
60
|
)
|
|
59
61
|
)
|
|
60
62
|
|
|
@@ -133,6 +135,9 @@ def convert_text(
|
|
|
133
135
|
- inline $ ... $ math expressions
|
|
134
136
|
- normal text passthrough
|
|
135
137
|
"""
|
|
138
|
+
if "$" not in text:
|
|
139
|
+
return text
|
|
140
|
+
|
|
136
141
|
scan = scan_markdown(text)
|
|
137
142
|
all_spans = sorted(
|
|
138
143
|
[*scan.math_blocks, *scan.math_inlines],
|
|
@@ -25,10 +25,10 @@ NUMERIC_FRACTION_RE = re.compile(
|
|
|
25
25
|
)
|
|
26
26
|
PLAIN_COEFFICIENT_RE = re.compile(r"[A-Za-z]")
|
|
27
27
|
ADDITIVE_SEPARATOR_RE = re.compile(
|
|
28
|
-
r"[+\-=<>]|\\(?:pm|mp|leq|geq|neq|approx|sim|equiv)(?![A-Za-z])"
|
|
28
|
+
r"[+\-=<>]|\\(?:pm|mp|leq|geq|neq|approx|sim|equiv)(?![A-Za-z])|[±∓≤≥≠≈∼≡]"
|
|
29
29
|
)
|
|
30
30
|
MULTIPLICATIVE_GAP_RE = re.compile(
|
|
31
|
-
r"(?:\s|[
|
|
31
|
+
r"(?:\s|[·*×✕]|\\(?:cdot|times|,|:|;|!|quad|qquad)(?![A-Za-z]))*"
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
|
|
@@ -39,12 +39,25 @@ def _compact(value: str) -> str:
|
|
|
39
39
|
def _is_derivative_prefix(value: str) -> bool:
|
|
40
40
|
compact = _compact(value)
|
|
41
41
|
return compact.startswith(
|
|
42
|
-
(
|
|
42
|
+
(
|
|
43
|
+
r"\frac{d}{d",
|
|
44
|
+
r"\dfrac{d}{d",
|
|
45
|
+
r"\tfrac{d}{d",
|
|
46
|
+
r"\frac{\mathrm{d}}{\mathrm{d",
|
|
47
|
+
r"\dfrac{\mathrm{d}}{\mathrm{d",
|
|
48
|
+
r"\tfrac{\mathrm{d}}{\mathrm{d",
|
|
49
|
+
r"\frac{\partial}{\partial",
|
|
50
|
+
r"\dfrac{\partial}{\partial",
|
|
51
|
+
r"\tfrac{\partial}{\partial",
|
|
52
|
+
r"\frac{∂}{∂",
|
|
53
|
+
r"\dfrac{∂}{∂",
|
|
54
|
+
r"\tfrac{∂}{∂",
|
|
55
|
+
)
|
|
43
56
|
)
|
|
44
57
|
|
|
45
58
|
|
|
46
59
|
def _is_prime(value: str) -> bool:
|
|
47
|
-
return bool(re.match(r"(?:[A-Za-z]|\\[A-Za-z]
|
|
60
|
+
return bool(re.match(r"(?:[A-Za-z]|\\[A-Za-z]+|[\u0370-\u03FF\U0001D400-\U0001D7FF])'", value.lstrip()))
|
|
48
61
|
|
|
49
62
|
|
|
50
63
|
def _is_numeric(value: str) -> bool:
|
|
@@ -227,6 +240,10 @@ def convert_derivative_line(source: str) -> str | None:
|
|
|
227
240
|
if block is None:
|
|
228
241
|
return None
|
|
229
242
|
|
|
243
|
+
# Delegate matrix/tensor equations to convert_matrix_block
|
|
244
|
+
if re.search(r"\\(?:mathbf|mathcal|begin\s*\{(?:Bmatrix|Vmatrix|array|bmatrix|matrix|pmatrix|smallmatrix|vmatrix)\})", block.body):
|
|
245
|
+
return None
|
|
246
|
+
|
|
230
247
|
body_start = skip_ignorable(block.body, 0, len(block.body))
|
|
231
248
|
prefix = read_operand(block.body, body_start)
|
|
232
249
|
if prefix is None or not _is_derivative_prefix(prefix.text(block.body)):
|
color_math/converters/generic.py
CHANGED
|
@@ -15,6 +15,7 @@ from ..parsers.dimensionless import find_dimensionless_spans, collect_dimensionl
|
|
|
15
15
|
from ..parsers.delimiters import collect_delimiter_spans
|
|
16
16
|
from ..parsers.taxonomy import collect_taxonomy_spans
|
|
17
17
|
from ..parsers.variable_hash import collect_variable_spans
|
|
18
|
+
from ..parsers.physics import collect_quantum_operator_spans
|
|
18
19
|
from ..utils.latex_helpers import contains_color_wrapper, normalize_latex_braces
|
|
19
20
|
from ..utils.spans import ColorSpan, apply_color_spans
|
|
20
21
|
from ..undo import uncolor_fragment
|
|
@@ -104,7 +105,14 @@ def color_latex_body(
|
|
|
104
105
|
spans.extend(collect_single_constant_spans(normalized, pal))
|
|
105
106
|
|
|
106
107
|
if opts.rainbow_delimiters:
|
|
107
|
-
spans.extend(
|
|
108
|
+
spans.extend(
|
|
109
|
+
collect_delimiter_spans(
|
|
110
|
+
normalized,
|
|
111
|
+
for_latex_wrap=True,
|
|
112
|
+
include_bare_braces=opts.rainbow_bare_braces,
|
|
113
|
+
highlight_unmatched=opts.highlight_unmatched_braces,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
108
116
|
|
|
109
117
|
if opts.enable_taxonomy:
|
|
110
118
|
spans.extend(collect_taxonomy_spans(normalized, pal, unit_spans, diff_spans, dim_spans))
|
|
@@ -112,6 +120,12 @@ def color_latex_body(
|
|
|
112
120
|
if opts.variable_data_flow:
|
|
113
121
|
spans.extend(collect_variable_spans(normalized, None, unit_spans, diff_spans, dim_spans, bare_functions))
|
|
114
122
|
|
|
123
|
+
if opts.color_quantum_operators or opts.field in ("quantum", "physics"):
|
|
124
|
+
quantum_spans = collect_quantum_operator_spans(normalized, pal, opts)
|
|
125
|
+
if quantum_spans:
|
|
126
|
+
spans = [s for s in spans if not any(q.start <= s.start and s.end <= q.end for q in quantum_spans)]
|
|
127
|
+
spans.extend(quantum_spans)
|
|
128
|
+
|
|
115
129
|
return apply_color_spans(normalized, spans)
|
|
116
130
|
|
|
117
131
|
|
color_math/converters/matrix.py
CHANGED
|
@@ -15,7 +15,7 @@ from .semantic import first_equality, parse_math_block, relation_spans
|
|
|
15
15
|
|
|
16
16
|
MATRIX_COMMAND_RE = re.compile(
|
|
17
17
|
r"\\(?:mathbf|mathcal|nabla|det|tr|Tr|trace|Vert|lVert)"
|
|
18
|
-
r"(?![A-Za-z])
|
|
18
|
+
r"(?![A-Za-z])|∇|\\\|(?![A-Za-z])|"
|
|
19
19
|
r"\\operatorname\s*\{\s*tr\s*\}"
|
|
20
20
|
)
|
|
21
21
|
MATRIX_ENV_RE = re.compile(
|
|
@@ -126,13 +126,13 @@ def convert_matrix_block(source: str) -> str | None:
|
|
|
126
126
|
if lhs_first.startswith((r"\det", r"\operatorname{tr}"))
|
|
127
127
|
else ("main",)
|
|
128
128
|
)
|
|
129
|
-
elif lhs_first.startswith((r"\frac{\partial}", r"\nabla")):
|
|
129
|
+
elif lhs_first.startswith((r"\frac{\partial}", r"\dfrac{\partial}", r"\tfrac{\partial}", r"\frac{∂}", r"\dfrac{∂}", r"\tfrac{∂}", r"\nabla", "∇")):
|
|
130
130
|
lhs_colors = ("upper", "main")
|
|
131
131
|
else:
|
|
132
132
|
lhs_colors = ("upper", "chain", "orange")
|
|
133
133
|
|
|
134
134
|
lhs_text = re.sub(r"\s+", "", block.body[:equality[0]])
|
|
135
|
-
if lhs_first.startswith(r"\frac{\partial}"):
|
|
135
|
+
if lhs_first.startswith((r"\frac{\partial}", r"\dfrac{\partial}", r"\tfrac{\partial}", r"\frac{∂}", r"\dfrac{∂}", r"\tfrac{∂}")):
|
|
136
136
|
rhs_colors = ("chain", "main")
|
|
137
137
|
elif len(lhs) > 1 and len(rhs) == 1:
|
|
138
138
|
rhs_colors = ("main",)
|