-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
math-preview.el
1155 lines (1041 loc) · 49.9 KB
/
math-preview.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; math-preview.el --- Preview TeX math equations inline -*- lexical-binding: t -*-
;; Author: Matsievskiy S.V.
;; Maintainer: Matsievskiy S.V.
;; Version: 5.1.1
;; Package-Requires: ((emacs "26.1") (json "1.4") (dash "2.18.0") (s "1.12.0"))
;; Homepage: https://gitlab.com/matsievskiysv/math-preview
;; Keywords: convenience
;; This file is not part of GNU Emacs
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; For a full copy of the GNU General Public License
;; see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Preview TeX math equations inline using MathJax
;; This package requires external program math-preview.
;; Installation instructions are available in README.md file.
;;; Code:
(require 'json)
(require 'dash)
(require 's)
;; {{{ Customization
(defgroup math-preview nil
"Preview math inline."
:group 'text
:tag "Math Preview"
:prefix "math-preview-"
:link '(url-link :tag "GitLab" "https://gitlab.com/matsievskiysv/math-preview"))
(defface math-preview-face
'((t :inherit default))
"Face for equation.")
(defface math-preview-processing-face
'((t :inherit highlight))
"Face for equation processing.")
(defcustom math-preview-command "math-preview"
"TeX conversion program name."
:tag "Command name"
:type '(choice (string :tag "Command name")
(repeat :tag "Command arguments" (string :tag "Argument")))
:safe (lambda (n) (or (stringp n)
(and (listp n)
(-all-p #'stringp n)))))
(defcustom math-preview-raise 0.4
"Adjust image vertical position."
:tag "Image vertical position"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0)
(< n 1))))
(defcustom math-preview-margin '(5 . 5)
"Adjust image margin."
:tag "Image margin"
:type '(cons :tag "Configure margins" (integer :tag "Horizontal") (integer :tag "Vertical"))
:safe (lambda (l) (and (consp l)
(integerp (car l))
(> (car l) 0)
(integerp (cdr l))
(> (cdr l) 0))))
(defcustom math-preview-relief 0
"Adjust image relief."
:tag "Image relief"
:type 'integer
:safe (lambda (n) (and (integerp n)
(> n 0))))
(defcustom math-preview-scale 1
"Adjust image scale."
:tag "Image scale"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defcustom math-preview-svg-postprocess-functions
'((lambda (x) (puthash 'string (s-replace "width=\"100%\""
(format "width=\"%dem\""
(/ (window-max-chars-per-line) 2))
(gethash 'string x))
x)))
"Functions to call on resulting SVG string before rendering.
Functions are applied in chain from left to right (or from top to bottom, when
in `customize'). Each function accepts one arguments which is a hash table
with field `string'. User may modify `string' in place to edit resulting image."
:tag "Postprocess SVG functions"
:type '(repeat function)
:safe (lambda (n) (and (listp n)
(-all? #'functionp n))))
(defcustom math-preview-scale-increment 0.1
"Image scale interactive increment value."
:tag "Image scale increment"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defcustom math-preview-preprocess-functions (list)
"Functions to call on each matched string.
Functions are applied in chain from left to right (or from top to bottom, when
in `customize'). Each function accepts one arguments which is a hash table
with fields: `match' matched string including marks; `string' matched string
without marks; `type' equation type (`tex', `mathml' or `asciimath');
`inline' equation inline flag; `lmark' and `rmark' are left and right marks
respectively. User may modify `string', `inline' and `type' fields in place to
influence further equation processing (although the intended purpose of these
functions is to edit only `string' field).
These functions are evaluated after `math-preview-tex-preprocess-functions',
`math-preview-mathml-preprocess-functions' and
`math-preview-asciimath-preprocess-functions' functions."
:tag "Preprocess functions"
:type '(repeat function)
:safe (lambda (n) (and (listp n)
(-all? #'functionp n))))
(defgroup math-preview-tex nil
"TeX options."
:group 'math-preview
:prefix "math-preview-tex-")
(defcustom math-preview-tex-marks
'(("\\begin{equation}" "\\end{equation}" 0 nil nil)
("\\begin{equation*}" "\\end{equation*}" 0 nil nil)
("\\[" "\\]" 0 nil nil)
("$$" "$$" 0 nil nil))
"Strings marking beginning and end of TeX equation. By default, when searching for equation marks,
the largest match has the priority."
:tag "TeX equation marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-tex-marks-inline
'(("\\(" "\\)" 0 nil nil)
("$" "$" 0 nil nil)
("`$" "`$" 0 nil nil))
"Strings marking beginning and end of TeX inline equation."
:tag "TeX equation inline marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-tex-preprocess-functions
'((lambda (x) (when (and (s-prefix? "\\begin" (gethash 'lmark x))
(s-prefix? "\\end" (gethash 'rmark x)))
(puthash 'string (gethash 'match x) x))))
"Functions to call on each TeX string.
Functions are applied in chain from left to right (or from top to bottom, when
in `customize'). Each function accepts one arguments which is a hash table
with fields: `match' matched string including marks; `string' matched string
without marks; `type' equation type (`tex', `mathml' or `asciimath');
`inline' equation inline flag; `lmark' and `rmark' are left and right marks
respectively. User may modify `string', `inline' and `type' fields in place to
influence further equation processing (although the intended purpose of these
functions is to edit only `string' field).
These functions are evaluated before `math-preview-preprocess-functions'
functions."
:tag "Preprocess TeX functions"
:type '(repeat function)
:safe (lambda (n) (and (listp n)
(-all? #'functionp n))))
(defcustom math-preview-tex-macros
`(("ddx" . ("\\frac{d#2}{d#1}" 2 "t")))
"List of predefined macros.
`\\' in the name of the macro must be omitted. If macro does not have any
arguments, then macro definition is a string. If macro have arguments, then
macro definition is a list, where first item is a definition string, second
item is a number of macro arguments and third argument is the optional default
value for the first argument. More information at the page
http://docs.mathjax.org/en/latest/input/tex/macros.html#tex-macros and
http://docs.mathjax.org/en/latest/input/tex/extensions/configmacros.html."
:tag "Macro list"
:type '(alist :key-type (string :tag "Name")
:value-type (choice :tag "Definition"
(string :tag "Without arguments")
(list :tag "With arguments"
(string :tag "Definition")
(integer :tag "Number of arguments")
(choice :tag "Default value"
(const :tag "None" nil)
(string :tag "Default value")))
(list :tag "Template based"
(string :tag "Definition")
(integer :tag "Number of arguments")
(repeat :tag "Templates"
(string :tag "Template")))))
:safe (lambda (n) (and (listp n)
(-all? (lambda (x) (and (stringp (car x))
(or (stringp (cdr x))
(listp (cdr x)))))
n))))
(defcustom math-preview-tex-environments
`(("braced" . ("\\left\\{" "\\right\\}")))
"List of predefined environments.
`\\' in the name of the macro must be omitted. If environment does not have
any arguments, then environment definition is a pair of strings. If environment
have arguments, then environment definition is a list, where first two items
are pair of definition strings, third item is a number of environment
arguments and fourth argument is the optional default value for the first
argument. More information at the page
http://docs.mathjax.org/en/latest/input/tex/environments.html#tex-environments
and
http://docs.mathjax.org/en/latest/input/tex/extensions/configenvironments.html."
:tag "Environment list"
:type '(alist :key-type (string :tag "Name")
:value-type (choice :tag "Definition"
(list :tag "Without arguments"
(string :tag "Before")
(string :tag "After "))
(list :tag "With arguments"
(string :tag "Before")
(string :tag "After ")
(integer :tag "Number of arguments")
(choice :tag "Default value"
(const :tag "None" nil)
(string :tag "Default value")))))
:safe (lambda (n) (and (listp n)
(-all? (lambda (x) (and (stringp (car x))
(listp (cdr x))))
n))))
(defgroup math-preview-tex-packages nil
"TeX package options."
:group 'math-preview-tex
:prefix "math-preview-tex-package-")
(defcustom math-preview-tex-default-packages '("autoload" "ams" "cancel" "color")
"List of default `TeX' packages to load.
This array lists the names of the packages (extensions) that should be
initialized by the `TeX' input processor. Packages not in this list must
be loaded using `\\require{}' macro or via `autoload' mechanism.
Extension list is available at the page
http://docs.mathjax.org/en/latest/input/tex/extensions/index.html.
`base', `require', `newcommand' and `configmacros' are always loaded."
:tag "Default TeX packages"
:type '(repeat string)
:safe (lambda (l) (-all? 'stringp l)))
(defgroup math-preview-tex-packages-ams nil
"TeX ams package options.
http://docs.mathjax.org/en/latest/input/tex/extensions/ams.html"
:group 'math-preview-tex-packages
:prefix "math-preview-tex-package-ams-")
(defcustom math-preview-tex-packages-ams-multline-width "100%"
"The width to use for multline environments."
:tag "Multline width"
:type 'string
:safe #'stringp)
(defcustom math-preview-tex-packages-ams-multline-indent "1em"
"The margin to use on both sides of multline environments."
:tag "Multline indent"
:type 'string
:safe #'stringp)
(defgroup math-preview-tex-packages-amscd nil
"TeX amscd package options.
http://docs.mathjax.org/en/latest/input/tex/extensions/amscd.html"
:group 'math-preview-tex-packages
:prefix "math-preview-tex-package-amscd-")
(defcustom math-preview-tex-packages-amsdc-colspace "5pt"
"Column space.
This gives the amount of space to use between columns in the commutative
diagram."
:tag "Column space"
:type 'string
:safe #'stringp)
(defcustom math-preview-tex-packages-amsdc-rowspace "5pt"
"Row space.
This gives the amount of space to use between rows in the commutative
diagram."
:tag "Row space"
:type 'string
:safe #'stringp)
(defcustom math-preview-tex-packages-amsdc-harrowsize "2.75em"
"Horizontal arrow size.
This gives the minimum size for horizontal arrows in the commutative
diagram."
:tag "Horizontal arrow size"
:type 'string
:safe #'stringp)
(defcustom math-preview-tex-packages-amsdc-varrowsize "2.75em"
"Vertical arrow size.
This gives the minimum size for vertical arrows in the commutative
diagram."
:tag "Vertical arrow size"
:type 'string
:safe #'stringp)
(defcustom math-preview-tex-packages-amsdc-hide-horizontal-labels nil
"Hide horizontal labels.
This determines whether horizontal arrows with labels above or below
will use `\\smash' in order to hide the height of the labels.
\(Labels above or below horizontal arrows can cause excess space between rows,
so setting this to true can improve the look of the diagram.)"
:tag "Hide horizontal labels"
:type 'boolean
:safe t)
(defgroup math-preview-tex-packages-autoload nil
"TeX autoload package options.
http://docs.mathjax.org/en/latest/input/tex/extensions/autoload.html"
:group 'math-preview-tex-packages
:prefix "math-preview-tex-package-autoload-")
(defcustom math-preview-tex-packages-autoload-packages
'(("action" . ("toggle" "mathtip" "texttip"))
("amscd" . (() ("CD")))
("bbox" . ("bbox"))
("boldsymbol" . ("boldsymbol"))
("braket" . ("bra" "ket" "braket" "set" "Bra" "Ket" "Braket" "Set" "ketbra" "Ketbra"))
("cancel" . ("cancel" "bcancel" "xcancel" "cancelto"))
("color" . ("color" "definecolor" "textcolor" "colorbox" "fcolorbox"))
("enclose" . ("enclose"))
("extpfeil" . ("xtwoheadrightarrow" "xtwoheadleftarrow" "xmapsto"
"xlongequal" "xtofrom" "Newextarrow"))
("html" . ("href" "class" "style" "cssId"))
("mhchem" . ("ce" "pu"))
("unicode" . ("unicode"))
("upgreek" . ("upalpha" "upbeta" "upchi" "updelta" "Updelta" "upepsilon"
"upeta" "upgamma" "Upgamma" "upiota" "upkappa" "uplambda"
"Uplambda" "upmu" "upnu" "upomega" "Upomega" "upomicron"
"upphi" "Upphi" "uppi" "Uppi" "uppsi" "Uppsi" "uprho"
"upsigma" "Upsigma" "uptau" "uptheta" "Uptheta" "upupsilon"
"Upupsilon" "upvarepsilon" "upvarphi" "upvarpi" "upvarrho"
"upvarsigma" "upvartheta" "upxi" "Upxi" "upzeta"))
("verb" . ("verb")))
"Auto-loading macros and environments.
Adding the autoload extension to the packages array defines an
auto-load sub-block to the `TeX' configuration block. This block
contains key: value pairs where the key is a `TeX' package name,
and the value is an array of macros that cause that package to be loaded,
or an array consisting of two arrays, the first giving names of macros and
the second names of environments; the first time any of them are used,
the extension will be loaded automatically."
:tag "Packages"
:type '(alist :tag "Package"
:key-type (string :tag "Name")
:value-type (choice (repeat :tag "Macros"
(string :tag "Macro"))
(list :tag "Macros and Environments"
(repeat :tag "Macros"
(string :tag "Macro"))
(repeat :tag "Environments"
(string :tag "Environment")))))
:safe (lambda (n) (and (listp n)
(-all? (lambda (x) (and (stringp (car x))
(listp (cdr x))
(or (-all? #'stringp (cdr x))
(and (listp (-first-item (cdr x)))
(listp (-second-item (cdr x)))
(-all? #'stringp (-first-item (cdr x)))
(-all? #'stringp (-second-item (cdr x)))))))
n))))
(defgroup math-preview-tex-packages-physics nil
"TeX physics package options.
http://docs.mathjax.org/en/latest/input/tex/extensions/physics.html"
:group 'math-preview-tex-packages
:prefix "math-preview-tex-package-physics-")
(defcustom math-preview-tex-packages-physics-italicdiff nil
"Italic diff.
This corresponds to the `italicdiff' option of the `physics'
`LaTeX' package to use italic form for the `d' in the `\differential' and
`\derivative' commands."
:tag "Italic diff"
:type 'boolean
:safe t)
(defcustom math-preview-tex-packages-physics-arrowdel nil
"Arrow del.
This corresponds to the `arrowdel' option of the `physics'
`LaTeX' package to use vector notation over the `nabla' symbol."
:tag "Arrow del"
:type 'boolean
:safe t)
(defgroup math-preview-mathml nil
"MathML options."
:group 'math-preview
:prefix "math-preview-mathml-")
(defcustom math-preview-mathml-marks
'(("<math" "</math>" 0 nil nil))
"Strings marking beginning and end of MathML equation."
:tag "MathML equation marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-mathml-marks-inline (list)
"Strings marking beginning and end of MathML inline equation."
:tag "MathML equation inline marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-mathml-preprocess-functions '((lambda (x) (puthash 'string (gethash 'match x) x)))
"Functions to call on each MathML string.
Functions are applied in chain from left to right (or from top to bottom, when
in `customize'). Each function accepts one arguments which is a hash table
with fields: `match' matched string including marks; `string' matched string
without marks; `type' equation type (`tex', `mathml' or `asciimath');
`inline' equation inline flag; `lmark' and `rmark' are left and right marks
respectively. User may modify `string', `inline' and `type' fields in place to
influence further equation processing (although the intended purpose of these
functions is to edit only `string' field).
These functions are evaluated before `math-preview-preprocess-functions'
functions."
:tag "Preprocess MathML functions"
:type '(repeat function)
:safe (lambda (n) (and (listp n)
(-all? #'functionp n))))
(defgroup math-preview-asciimath nil
"AsciiDoc options."
:group 'math-preview
:prefix "math-preview-asciimath-")
(defcustom math-preview-asciimath-marks (list)
"Strings marking beginning and end of AsciiMath equation."
:tag "AsciiMath equation marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-asciimath-marks-inline (list)
"Strings marking beginning and end of AsciiMath inline equation."
:tag "AsciiMath equation inline marks"
:type '(repeat :tag "Mark pairs" (list :tag "Mark pair"
(string :tag "Left mark")
(string :tag "Right mark")
(integer :tag "Priority")
(boolean :tag "Left regexp")
(boolean :tag "Right regexp")))
:safe #'math-preview--check-marks)
(defcustom math-preview-asciimath-preprocess-functions (list)
"Functions to call on each AsciiMath string.
Functions are applied in chain from left to right (or from top to bottom, when
in `customize'). Each function accepts one arguments which is a hash table
with fields: `match' matched string including marks; `string' matched string
without marks; `type' equation type (`tex', `mathml' or `asciimath');
`inline' equation inline flag; `lmark' and `rmark' are left and right marks
respectively. User may modify `string', `inline' and `type' fields in place to
influence further equation processing (although the intended purpose of these
functions is to edit only `string' field).
These functions are evaluated before `math-preview-preprocess-functions'
functions."
:tag "Preprocess AsciiMath functions"
:type '(repeat function)
:safe (lambda (n) (and (listp n)
(-all? #'functionp n))))
(defgroup math-preview-mathjax nil
"MathJax options."
:group 'math-preview
:prefix "math-preview-mathjax-")
(defcustom math-preview-mathjax-em #'window-font-width
"Number giving the number of pixels in an `em' for the surrounding font."
:tag "Em size"
:type '(choice (integer :tag "Constant value")
(function :tag "Calculate using function"))
:safe (lambda (n) (or (functionp n)
(and (numberp n)
(> n 0)))))
(defcustom math-preview-mathjax-ex #'window-font-height
"Number giving the number of pixels in an `ex' for the surrounding font."
:tag "Ex size"
:type '(choice (integer :tag "Constant value")
(function :tag "Calculate using function"))
:safe (lambda (n) (or (functionp n)
(and (numberp n)
(> n 0)))))
(defcustom math-preview-mathjax-container-width #'window-pixel-width
"Number giving the width of the container, in pixels."
:tag "Container width"
:type '(choice (integer :tag "Constant value")
(function :tag "Calculate using function"))
:safe (lambda (n) (or (functionp n)
(and (numberp n)
(> n 0)))))
(defcustom math-preview-mathjax-line-width #'window-max-chars-per-line
"Number giving the line-breaking width in `em' units."
:tag "Max width"
:type '(choice (integer :tag "Constant value")
(function :tag "Calculate using function"))
:safe (lambda (n) (or (functionp n)
(and (numberp n)
(> n 0)))))
(defcustom math-preview-mathjax-scale 1
"Number giving a scaling factor to apply to the resulting conversion."
:tag "Scale"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defgroup math-preview-mathjax-loader nil
"MathJax loader options.
http://docs.mathjax.org/en/latest/options/startup/loader.html"
:group 'math-preview-mathjax
:prefix "math-preview-mathjax-loader-")
(defcustom math-preview-mathjax-loader-load (list "input/tex-full" "input/mml" "input/asciimath" "output/svg")
"This array lists the components that you want to load."
:tag "Load list"
:type '(repeat string)
:safe (lambda (l) (-all? 'stringp l)))
(defgroup math-preview-mathjax-tex nil
"MathJax `TeX' configuration options.
The options below control the operation of the TeX input processor that
is run when you include `input/tex', `input/tex-full', or `input/tex-base'
in the load array of the loader block of your MathJax configuration.
http://docs.mathjax.org/en/latest/options/input/tex.html"
:group 'math-preview-mathjax
:prefix "math-preview-mathjax-tex-")
(defcustom math-preview-mathjax-tex-process-escapes t
"Process escapes.
When set to true, you may use `\\$' to represent a literal dollar sign,
rather than using it as a math delimiter, and `\\\\' to represent a literal
backslash."
:tag "Process escapes"
:type 'boolean
:safe t)
(defcustom math-preview-mathjax-tex-digits "/^(?:[0-9]+(?:\\{,\\}[0-9]*)?|\\{,\\}[0-9]+)/"
"Digit regular expression.
This gives a regular expression that is used to identify numbers
during the parsing of your TeX expressions. By default, the decimal point
is `.' and you can use `,' between every three digits before that.
If you want to use `,' as the decimal indicator, use
`/^(?:[0-9]+(?:\\{,\\}[0-9]*)?|\\{,\\}[0-9]+)/'"
:tag "Digits"
:type 'string
:safe 'stringp)
(defcustom math-preview-mathjax-tags "none"
"Auto-numbering tags.
This controls whether equations are numbered and how.
By default it is set to `none' to be compatible with earlier versions of MathJax where auto-numbering
was not performed (so pages will not change their appearance).
You can change this to `ams' for equations numbered as the AMSmath package would do,
or `all' to get an equation number for every displayed equation."
:tag "Tags"
:type '(choice (const :tag "None" "none")
(const :tag "AMS math" "ams")
(const :tag "All" "all"))
:safe 'stringp)
(defcustom math-preview-mathjax-tags-side "right"
"Tags side.
This specifies the side on which `\\tag{}' macros will place the tags,
and on which automatic equation numbers will appear.
Set it to `left' to place the tags on the left-hand side."
:tag "Tags side"
:type '(choice (const :tag "Left" "left")
(const :tag "Right" "right"))
:safe 'stringp)
(defcustom math-preview-mathjax-tag-indent "0.8em"
"Tag indent.
This is the amount of indentation (from the right or left) for the tags
produced by the `\\tag{}' macro."
:tag "Tag indent"
:type 'string
:safe 'stringp)
(defgroup math-preview-mathjax-svg nil
"MathJax SVG configuration options.
The options below control the operation of the SVG output processor that
is run when you include `output/svg' in the load array of the loader block.
http://docs.mathjax.org/en/latest/options/output/svg.html#svg-options"
:group 'math-preview-mathjax
:prefix "math-preview-mathjax-svg-")
(defcustom math-preview-mathjax-svg-scale 1
"Global scaling factor for all expressions."
:tag "Scale"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defcustom math-preview-mathjax-svg-min-scale 0.5
"Smallest scaling factor to use."
:tag "Min scale"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defcustom math-preview-mathjax-svg-mathml-spacing nil
"Spacing rules.
True for `MathML' spacing rules, false for `TeX' rules."
:tag "MathML spacing"
:type 'boolean
:safe t)
(defcustom math-preview-mathjax-svg-ex-factor 0.5
"Default size of `ex' in `em' units."
:tag "Ex factor"
:type 'number
:safe (lambda (n) (and (numberp n)
(> n 0))))
(defcustom math-preview-mathjax-svg-display-align "left"
"Default for `indentalign' when set to `auto'."
:tag "Display align"
:type '(choice (const :tag "Left" "left")
(const :tag "Center" "center")
(const :tag "Right" "right")))
(defcustom math-preview-mathjax-svg-display-indent "0"
"Default for `indentshift' when set to `auto'."
:tag "Display indent"
:type 'string
:safe 'stringp)
;; }}}
;; {{{ Variables
(defvar math-preview--schema-version 5 "`math-preview' json schema version.")
(defvar math-preview--queue nil "Job queue.")
(defvar math-preview--reset-numbering nil "MathJax reset numbering flag. Number to start new numbering from or `nil'.")
(defvar math-preview-map (let ((keymap (make-keymap)))
(suppress-keymap keymap t)
(define-key keymap (kbd "<delete>") #'math-preview-clear-at-point)
(define-key keymap (kbd "<backspace>") #'math-preview-clear-at-point)
(define-key keymap (kbd "SPC") #'math-preview-clear-at-point)
(define-key keymap (kbd "RET") #'math-preview-clear-at-point)
(define-key keymap (kbd "<mouse-1>") #'math-preview-clear-at-point)
(define-key keymap (kbd "<C-delete>") #'math-preview-clear-all)
(define-key keymap (kbd "<C-backspace>") #'math-preview-clear-all)
(define-key keymap (kbd "<C-mouse-1>") #'math-preview-clear-all)
(define-key keymap (kbd "+") #'math-preview-increment-scale)
(define-key keymap (kbd "p") #'math-preview-increment-scale)
(define-key keymap (kbd "-") #'math-preview-decrement-scale)
(define-key keymap (kbd "n") #'math-preview-decrement-scale)
(define-key keymap (kbd "<C-return>") #'math-preview-copy-svg)
(define-key keymap (kbd "C-SPC") #'math-preview-copy-svg)
keymap)
"Key map for math-preview image overlays.")
(defvar math-preview--input-buffer ""
"Buffer holds input message.")
(defvar math-preview--debug-json nil
"Switch for enabling JSON dump into `math-preview--output-buffer'.")
(defvar math-preview--newline-replacement-string " "
"String that replaces `\n' character before the regexp matching.")
(put 'math-preview 'face 'math-preview-face)
(put 'math-preview 'keymap math-preview-map)
(put 'math-preview 'evaporate t)
(put 'math-preview 'help-echo "mouse-1 to remove")
(put 'math-preview 'mouse-face 'math-preview-processing-face)
(put 'math-preview-processing 'face 'math-preview-processing-face)
;; }}}
;; {{{ Process
(defun math-preview--json-bool (arg)
"Convert boolean `ARG' to `JSON'.
JSON encoder cannot distinguish `null' and `false', therefore we need to
use `json-false' to encode `false'."
(if arg arg json-false))
(defun math-preview--number-or-function (f)
"Get number from field `F' which can be number of a function."
(if (functionp f) (funcall f) f))
(defun math-preview--encode-arguments ()
"Encode program arguments in JSON strings."
(let* ((loader (list (cons "loader" (list (cons "load" math-preview-mathjax-loader-load)))))
(svg (list (cons "svg" (list
(cons "scale" math-preview-mathjax-svg-scale)
(cons "minScale" math-preview-mathjax-svg-min-scale)
(cons "mathmlSpacing" (math-preview--json-bool math-preview-mathjax-svg-mathml-spacing))
(cons "exFactor" math-preview-mathjax-svg-ex-factor)
(cons "displayAlign" math-preview-mathjax-svg-display-align)
(cons "displayIndent" math-preview-mathjax-svg-display-indent)))))
(tex (list (cons "tex" (list
(cons"processEscapes" (math-preview--json-bool math-preview-mathjax-svg-mathml-spacing))
(cons "digits" math-preview-mathjax-tex-digits)
(cons "tags" math-preview-mathjax-tags)
(cons "tagSide" math-preview-mathjax-tags-side)
(cons "tagIndent" math-preview-mathjax-tag-indent)))))
(tex-macros (list (cons "tex/macros" math-preview-tex-macros)))
(tex-environments (list (cons "tex/environments" math-preview-tex-environments)))
(ams (list (cons "multlineWidth" math-preview-tex-packages-ams-multline-width)
(cons "multlineIndent" math-preview-tex-packages-ams-multline-indent)))
(amscd (list (cons "colspace" math-preview-tex-packages-amsdc-colspace)
(cons "rowspace" math-preview-tex-packages-amsdc-rowspace)
(cons "harrowsize" math-preview-tex-packages-amsdc-harrowsize)
(cons "varrowsize" math-preview-tex-packages-amsdc-varrowsize)
(cons "hideHorizontalLabels" (math-preview--json-bool
math-preview-tex-packages-amsdc-hide-horizontal-labels))))
(autoload math-preview-tex-packages-autoload-packages)
(physics (list (cons "italicdiff" (math-preview--json-bool math-preview-tex-packages-physics-italicdiff))
(cons "arrowdel" (math-preview--json-bool math-preview-tex-packages-physics-arrowdel))))
(tex-packages (list (cons "tex/packages" (list (cons "tex/packages/list" math-preview-tex-default-packages)
(cons "ams" ams)
(cons "amscd" amscd)
(cons "autoload" autoload)
(cons "physics" physics))))))
;; assume all nulls are wrongfully encoded empty lists
(--map (s-replace "null" "[]" it) (list
(json-encode-alist loader)
(json-encode-alist svg)
(json-encode-alist tex)
(json-encode-alist tex-macros)
(json-encode-alist tex-environments)
(json-encode-alist tex-packages)))))
;;;###autoload
(defun math-preview-start-process ()
"Start math-preview process."
(interactive)
(let ((proc (get-process "math-preview"))
(process-connection-type nil))
(unless proc
(math-preview-stop-process) ; clear garbage from previous session
(let* ((command (if (listp math-preview-command)
math-preview-command
(list math-preview-command)))
(executable (car command))
(p (executable-find executable)))
(unless p
(error "%s is not an executable" executable))
(setq proc (make-process :name "math-preview"
:command (-concat command
(math-preview--encode-arguments))
:coding 'utf-8
:noquery t
:connection-type 'pipe
:filter #'math-preview--process-filter))
(unless (process-live-p proc)
(error "Cannot start process"))))
proc))
;;;###autoload
(defun math-preview-stop-process ()
"Stop math-preview process."
(interactive)
(let ((proc (get-process "math-preview")))
(setq math-preview--queue nil)
(math-preview--overlays-remove-processing)
(when proc
(kill-process proc))))
(defun math-preview--process-filter (_process message)
"Handle `MESSAGE' from math-preview `PROCESS'.
Call `math-preview--process-input' for strings with carriage return."
(setq message
(s-replace "" ""
(s-concat math-preview--input-buffer message))) ; ignore carriage return
;; buffer incomplete input
(let ((lines (s-lines message)))
(setq math-preview--input-buffer (-first-item (-take-last 1 lines)))
(->> lines
(-drop-last 1)
(-map #'math-preview--process-input))))
(defun math-preview--process-input (message)
"Process input MESSAGE line."
(when math-preview--debug-json
(with-current-buffer (get-buffer-create "*math-preview*")
(goto-char (point-max))
(insert "Incoming:")
(insert message)
(insert "\n")))
(ignore-errors
(let* ((msg (json-read-from-string message))
(id (cdr (assoc 'id msg)))
(type (cdr (assoc 'type msg)))
(payload (cdr (assoc 'payload msg)))
target-overlay)
(unless (= id -1)
(setq target-overlay (cdr (--first (= (car it) id) math-preview--queue)))
(setq math-preview--queue (--remove (= (car it) id) math-preview--queue)))
(cond
((string= "error" type) (message "%s" payload) (when target-overlay (delete-overlay target-overlay)))
((string= "svg" type)
(let ((table (make-hash-table :size 1)))
(puthash 'string payload table)
(run-hook-with-args 'math-preview-svg-postprocess-functions table)
(overlay-put target-overlay 'category 'math-preview)
(overlay-put target-overlay 'display
(list (list 'raise math-preview-raise)
(cons 'image
(list :type 'svg
:data (gethash 'string table)
:scale math-preview-scale
:pointer 'hand
:margin math-preview-margin
:relief math-preview-relief))))))))))
(defun math-preview--submit (beg end string type inline)
"Submit equation processing job.
`BEG' and `END' are the positions of the overlay region.
`STRING' is an equation.
`TYPE' is `tex' or `mathml' or `asciimath'.
`INLINE' is a display style flag."
(unless (math-preview--overlays beg end)
(let ((proc (math-preview-start-process))
(target-overlay (make-overlay beg end))
(id (1+ (or (-> math-preview--queue (-first-item) (car)) 0)))
msg)
(overlay-put target-overlay 'category 'math-preview-processing)
(setq math-preview--queue (-insert-at 0 (-cons* id target-overlay) math-preview--queue))
(setq msg (concat
(json-encode
(list :version math-preview--schema-version
:id id
:em (math-preview--number-or-function math-preview-mathjax-em)
:ex (math-preview--number-or-function math-preview-mathjax-ex)
:scale math-preview-mathjax-scale
:inline (math-preview--json-bool inline)
:containerWidth (math-preview--number-or-function math-preview-mathjax-container-width)
:lineWidth (math-preview--number-or-function math-preview-mathjax-line-width)
:payload string
:from type
:to "svg"
:reset_numbering (math-preview--json-bool (not (null math-preview--reset-numbering)))
:reset_from (- (or math-preview--reset-numbering 1) 1)))
"\n"))
(setq math-preview--reset-numbering nil)
(when math-preview--debug-json
(with-current-buffer (get-buffer-create "*math-preview*")
(goto-char (point-max))
(insert "Outgoing:")
(insert msg)))
(process-send-string proc msg))))
;; }}}
;; {{{ Search
(defun math-preview--overlays (beg end)
"Get math-preview overlays in region between `BEG' and `END'."
(->> (if (= beg end) (overlays-at beg) (overlays-in beg end))
(--filter (let ((cat (overlay-get it 'category)))
(or (eq cat 'math-preview)
(eq cat 'math-preview-processing))))))
(defun math-preview--overlays-remove-processing ()
"Get math-preview overlays in region."
(->> (overlays-in (point-min) (point-max))
(--filter (eq (overlay-get it 'category) 'math-preview-processing))
(--map (delete-overlay it))))
(defun math-preview--check-marks (arg)
"Check that ARG is a valid `math-preview-marks' value."
(and (listp arg)
(not (-filter 'null (--map (and
(listp it)
(stringp (-first-item it))
(not (s-matches? "^\s*$" (-first-item it)))
(stringp (-second-item it))
(not (s-matches? "^\s*$" (-second-item it)))
(integerp (-third-item it))
(booleanp (-fourth-item it))
(booleanp (-fifth-item it)))
arg)))))
(defun math-preview--find-gaps (beg end)
"Find gaps in math-preview overlays in region between `BEG' and `END'."
(let ((o (math-preview--overlays beg end)))
(->> (-zip-pair (-concat (list beg) (-sort #'< (-map #'overlay-end o)))
(-concat (-sort #'< (-map #'overlay-start o)) (list end)))
(--filter (> (cdr it) beg))
(--filter (< (car it) end)))))
(defun math-preview--search (beg end)
"Search for equations in region between `BEG' and `END'."
(let ((text (buffer-substring beg end))
(regex (concat "\\(?:"
(s-join "\\|"
(--map
(s-join ".+?"
(list (if (nth 5 it)
(-second-item it)
(regexp-quote (-second-item it)))
(if (nth 6 it)
(-third-item it)
(regexp-quote (-third-item it)))))
(math-preview--create-mark-list)))
"\\)")))
(->> (s-matched-positions-all
regex
(s-replace-all `(("\n" . ,math-preview--newline-replacement-string)) text))
(-filter #'identity)
(-flatten)
(--map (cons (+ beg (car it))
(+ beg (cdr it)))))))
(defun math-preview--create-mark-list ()
"Concatenate and reformat mark lists.
Output list format `(type left right inline? priority regexp?)'"
(->> (-concat
(--map (list "asciimath" (-first-item it) (-second-item it) t
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-asciimath-marks-inline)
(--map (list "asciimath" (-first-item it) (-second-item it) nil
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-asciimath-marks)
(--map (list "mathml" (-first-item it) (-second-item it) t
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-mathml-marks-inline)
(--map (list "mathml" (-first-item it) (-second-item it) nil
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-mathml-marks)
(--map (list "tex" (-first-item it) (-second-item it) t
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-tex-marks-inline)
(--map (list "tex" (-first-item it) (-second-item it) nil
(or (-third-item it) 0) (-fourth-item it) (-fifth-item it))
math-preview-tex-marks))
(--sort (cond
((not (= (-fifth-item it) (-fifth-item other)))
(> (-fifth-item it) (-fifth-item other)))
((not (= (length (-second-item it)) (length (-second-item other))))
(> (length (-second-item it)) (length (-second-item other))))
((not (= (length (-third-item it)) (length (-third-item other))))
(> (length (-third-item it)) (length (-third-item other))))
(t t)))))
(defun math-preview--extract-match (string)
"Extract match data from given `STRING'.
Return hash table containing original string, string with stripped marks,
type of equation, left and right marks."
(let* ((string-no-newlines (s-replace-all `(("\n" . ,math-preview--newline-replacement-string)) string))
(match (->> (math-preview--create-mark-list)
(--first (s-matches-p
(s-concat "^"
(if (nth 5 it)
(-second-item it)
(regexp-quote (-second-item it)))
".+?"
(if (nth 6 it)
(-third-item it)
(regexp-quote (-third-item it)))
"$")
string-no-newlines))))
(lmark (-second-item match))
(rmark (-third-item match))