├── .gitignore ├── Readme.org ├── imgs ├── alignment.gif ├── nested_blocks.gif ├── readability.gif └── screencast.gif └── magic-latex-buffer.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | 3 | -------------------------------------------------------------------------------- /Readme.org: -------------------------------------------------------------------------------- 1 | * magic-latex-buffer.el 2 | 3 | Magically enhance LaTeX-mode font-locking for semi-WYSIWYG editing. 4 | 5 | LaTeX-mode のシンタックスハイライトを激しく 6 | 7 | ** Screencast 8 | 9 | [[imgs/screencast.gif]] 10 | 11 | ** Other examples 12 | 13 | - make it readable a mess of commands 14 | 15 | [[imgs/readability.gif]] 16 | 17 | - highlight decorated blocks (note that nested blocks are also 18 | highlighted correctly) 19 | 20 | [[imgs/nested_blocks.gif]] 21 | 22 | - align centered/flushed blocks 23 | 24 | [[imgs/alignment.gif]] 25 | 26 | - inline image preview powered by =iimage-mode= 27 | 28 | - fix inline-math highlighting for documents with multi-byte characters 29 | 30 | ** Installation 31 | 32 | 1. Install an unicode font supporting many symbols (eg. Symbolas, 33 | Unifont, etc). 34 | 35 | 2. Put magic-latex-buffer.el into a "load-path"ed directory, and load 36 | it in your init file: 37 | 38 | : (require 'magic-latex-buffer) 39 | 40 | You can activate the magic with =M-x magic-latex-buffer= in a 41 | =latex-mode= buffer. If you want to activate automatically, add the 42 | function to the mode hook. 43 | 44 | : (add-hook 'latex-mode-hook 'magic-latex-buffer) 45 | 46 | ** Customizations 47 | 48 | You can disable some features independently, if they're too fancy. 49 | 50 | : (setq magic-latex-enable-block-highlight nil 51 | : magic-latex-enable-suscript t 52 | : magic-latex-enable-pretty-symbols t 53 | : magic-latex-enable-block-align nil 54 | : magic-latex-enable-inline-image nil 55 | : magic-latex-enable-minibuffer-echo nil) 56 | 57 | ** Known Issues 58 | 59 | - Not perfectly compatible with =multiple-cursors= (but still usable) 60 | -------------------------------------------------------------------------------- /imgs/alignment.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/magic-latex-buffer/903ec91872760e47c0e5715795f8465173615098/imgs/alignment.gif -------------------------------------------------------------------------------- /imgs/nested_blocks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/magic-latex-buffer/903ec91872760e47c0e5715795f8465173615098/imgs/nested_blocks.gif -------------------------------------------------------------------------------- /imgs/readability.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/magic-latex-buffer/903ec91872760e47c0e5715795f8465173615098/imgs/readability.gif -------------------------------------------------------------------------------- /imgs/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zk-phi/magic-latex-buffer/903ec91872760e47c0e5715795f8465173615098/imgs/screencast.gif -------------------------------------------------------------------------------- /magic-latex-buffer.el: -------------------------------------------------------------------------------- 1 | ;;; -*- lexical-binding: t -*- 2 | ;;; magic-latex-buffer.el --- Magically enhance LaTeX-mode font-locking for semi-WYSIWYG editing 3 | 4 | ;; Copyright (C) 2014-2015 zk_phi 5 | 6 | ;; This program is free software; you can redistribute it and/or modify 7 | ;; it under the terms of the GNU General Public License as published by 8 | ;; the Free Software Foundation; either version 2 of the License, or 9 | ;; (at your option) any later version. 10 | ;; 11 | ;; This program is distributed in the hope that it will be useful, 12 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | ;; GNU General Public License for more details. 15 | ;; 16 | ;; You should have received a copy of the GNU General Public License 17 | ;; along with this program; if not, write to the Free Software 18 | ;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | ;; Author: zk_phi 21 | ;; URL: http://zk-phi.github.io/ 22 | ;; Version: 0.4.0 23 | ;; Package-Requires: ((cl-lib "0.5") (emacs "25.1")) 24 | 25 | ;;; Commentary: 26 | 27 | ;; Put this script into a "load-path"ed directory, and load it in your 28 | ;; init file. 29 | ;; 30 | ;; (require 'magic-latex-buffer) 31 | ;; 32 | ;; Then you can enable highlighting with "M-x magic-latex-buffer" in a 33 | ;; latex-mode-buffer. If you may enable highlighting automatically, 34 | ;; add to the mode hook. 35 | ;; 36 | ;; (add-hook 'latex-mode-hook 'magic-latex-buffer) 37 | ;; 38 | ;; For more informations, see Readme.org. 39 | 40 | ;;; Change Log: 41 | ;; 0.0.0 test release 42 | ;; 0.1.0 add highlights, fix fatal bugs 43 | ;; 0.1.1 implement nested sub/super-scripts 44 | ;; 0.2.0 add option to disable some prettifiers 45 | ;; 0.3.0 add support for alignment commands 46 | ;; 0.4.0 add option `magic-latex-enable-minibuffer-echo' 47 | ;; 0.4.1 migrate to nadvice.el 48 | 49 | ;;; Code: 50 | 51 | (require 'font-lock) 52 | (require 'jit-lock) 53 | (require 'tex-mode) 54 | (require 'iimage) 55 | (require 'cl-lib) 56 | 57 | (defconst magic-latex-buffer-version "0.4.1") 58 | 59 | ;; + customizable vars 60 | 61 | (defgroup magic-latex-buffer nil 62 | "magical syntax highlighting for LaTeX-mode buffers" 63 | :group 'emacs) 64 | 65 | (defcustom magic-latex-ignored-properties 66 | '(font-lock-comment-face 67 | font-lock-comment-delimiter-face 68 | font-lock-constant-face 69 | tex-verbatim) 70 | "List of faces which magic-latex should ignore." 71 | :type '(list face) 72 | :group 'magic-latex-buffer) 73 | 74 | (defcustom magic-latex-enable-block-highlight t 75 | "When non-nil, prettify blocks like \"{\\large ...}\"." 76 | :type 'boolean 77 | :group 'magic-latex-buffer) 78 | 79 | (defcustom magic-latex-enable-block-align t 80 | "When non-nil, align blocks like \"{\\centering ...}\"." 81 | :type 'boolean 82 | :group 'magic-latex-buffer) 83 | 84 | (defcustom magic-latex-enable-suscript t 85 | "When non-nil, prettify subscripts and superscripts like 86 | \"a_1\", \"e^{-x}\"." 87 | :type 'boolean 88 | :group 'magic-latex-buffer) 89 | 90 | (defcustom magic-latex-enable-pretty-symbols t 91 | "When non-nil, prettify symbols with unicode characters and 92 | character composition." 93 | :type 'boolean 94 | :group 'magic-latex-buffer) 95 | 96 | (defcustom magic-latex-enable-inline-image t 97 | "When non-nil, `iimage-mode' is enabled automatically." 98 | :type 'boolean 99 | :group 'magic-latex-buffer) 100 | 101 | (defcustom magic-latex-enable-minibuffer-echo t 102 | "When non-nil, actual command is displayed in the modeline." 103 | :type 'boolean 104 | :group 'magic-latex-buffer) 105 | 106 | ;; + vars, consts 107 | 108 | (defconst ml/syntax-table 109 | (let ((st (copy-syntax-table tex-mode-syntax-table))) 110 | (modify-syntax-entry ?$ "\"" st) 111 | (modify-syntax-entry ?\' "." st) 112 | st) 113 | "like `tex-mode-syntax-table' but treat $ as a string quote, 114 | for correct inline-math recognition. Also make the quote ' be considered a delimiter (to correctly detect symbols)") 115 | 116 | (defvar-local ml/jit-point nil 117 | "store the point while font-locking") 118 | (define-advice jit-lock-fontify-now (:around (fn &rest args) ml/ad-jit-lock) 119 | (let ((ml/jit-point (point))) 120 | (apply fn args))) 121 | 122 | ;; + faces 123 | 124 | (defface ml/title '((t (:inherit font-lock-function-name-face :height 2.0))) 125 | "Face used for title command in magic LaTeX buffers." 126 | :group 'magic-latex-buffer) 127 | (defface ml/chapter '((t (:inherit font-lock-function-name-face :height 1.8))) 128 | "Face used for chapter command in magic LaTeX buffers." 129 | :group 'magic-latex-buffer) 130 | (defface ml/section '((t (:inherit font-lock-function-name-face :height 1.6))) 131 | "Face used for section command in magic LaTeX buffers." 132 | :group 'magic-latex-buffer) 133 | (defface ml/subsection '((t (:inherit font-lock-function-name-face :height 1.2))) 134 | "Face used for subsection command in magic LaTeX buffers." 135 | :group 'magic-latex-buffer) 136 | 137 | (defface ml/box '((t (:box t))) 138 | "Face used for box command in magic LaTeX buffers." 139 | :group 'magic-latex-buffer) 140 | (defface ml/overline '((t (:overline t))) 141 | "Face used for overline command in magic LaTeX buffers." 142 | :group 'magic-latex-buffer) 143 | (defface ml/type '((t (:inherit 'fixed-pitch))) 144 | "Face used for type command in magic LaTeX buffers." 145 | :group 'magic-latex-buffer) 146 | 147 | (defface ml/black '((t (:foreground "black"))) 148 | "Face used for color{black} command in magic LaTeX buffers." 149 | :group 'magic-latex-buffer) 150 | (defface ml/white '((t (:foreground "white"))) 151 | "Face used for color{white} command in magic LaTeX buffers." 152 | :group 'magic-latex-buffer) 153 | (defface ml/red '((t (:foreground "red"))) 154 | "Face used for color{red} command in magic LaTeX buffers." 155 | :group 'magic-latex-buffer) 156 | (defface ml/green '((t (:foreground "green"))) 157 | "Face used for color{green} command in magic LaTeX buffers." 158 | :group 'magic-latex-buffer) 159 | (defface ml/blue '((t (:foreground "blue"))) 160 | "Face used for color{blue} command in magic LaTeX buffers." 161 | :group 'magic-latex-buffer) 162 | (defface ml/cyan '((t (:foreground "cyan"))) 163 | "Face used for color{cyan} command in magic LaTeX buffers." 164 | :group 'magic-latex-buffer) 165 | (defface ml/magenta '((t (:foreground "magenta"))) 166 | "Face used for color{magenta} command in magic LaTeX buffers." 167 | :group 'magic-latex-buffer) 168 | (defface ml/yellow '((t (:foreground "yellow"))) 169 | "Face used for color{yellow} command in magic LaTeX buffers." 170 | :group 'magic-latex-buffer) 171 | 172 | (defface ml/tiny '((t (:height 0.7))) 173 | "Face used for tiny command in magic LaTeX buffers." 174 | :group 'magic-latex-buffer) 175 | (defface ml/script '((t (:height 0.8))) 176 | "Face used for script command in magic LaTeX buffers." 177 | :group 'magic-latex-buffer) 178 | (defface ml/footnote '((t (:height 0.8))) 179 | "Face used for footnote command in magic LaTeX buffers." 180 | :group 'magic-latex-buffer) 181 | (defface ml/small '((t (:height 0.9))) 182 | "Face used for small command in magic LaTeX buffers." 183 | :group 'magic-latex-buffer) 184 | (defface ml/large '((t (:height 1.2))) 185 | "Face used for large command in magic LaTeX buffers." 186 | :group 'magic-latex-buffer) 187 | (defface ml/llarge '((t (:height 1.44))) 188 | "Face used for Large command in magic LaTeX buffers." 189 | :group 'magic-latex-buffer) 190 | (defface ml/xlarge '((t (:height 1.72))) 191 | "Face used for LARGE command in magic LaTeX buffers." 192 | :group 'magic-latex-buffer) 193 | (defface ml/huge '((t (:height 2.07))) 194 | "Face used for huge command in magic LaTeX buffers." 195 | :group 'magic-latex-buffer) 196 | (defface ml/hhuge '((t (:height 2.49))) 197 | "Face used for Huge command in magic LaTeX buffers." 198 | :group 'magic-latex-buffer) 199 | 200 | (defface ml/lbar '((t (:background "red"))) 201 | "Face used to render vertical line for quote environments in 202 | magic LaTeX buffers." 203 | :group 'magic-latex-buffer) 204 | 205 | ;; + utilities 206 | ;; + general 207 | 208 | (defmacro ml/safe-excursion (&rest body) 209 | "Like `progn' but moves the point only when BODY succeeded." 210 | `(let ((pos (point))) 211 | (condition-case err (progn ,@body) 212 | (error (goto-char pos) (error (error-message-string err)))))) 213 | 214 | (defun ml/command-regexp-opt (strings) 215 | "Like `regexp-opt' but for LaTeX command names." 216 | (concat "\\\\" (regexp-opt strings) "\\>")) 217 | 218 | (defun ml/overlay-at (point prop val) 219 | "Return an overlay at point, whose property PROP is VAL. If 220 | some overlays satisfy the condition, overlay with the highest 221 | priority is returned. If there's no such overlays, return nil." 222 | (cl-some (lambda (ov) (when (equal (overlay-get ov prop) val) ov)) 223 | (sort (overlays-at point) 224 | (lambda (a b) (let ((pa (overlay-get a 'priority)) 225 | (pb (overlay-get b 'priority))) 226 | (or (null pb) (and pa (>= pa pb)))))))) 227 | 228 | (defun ml/column-at-eol () 229 | (save-excursion (end-of-line) (current-column))) 230 | 231 | (defun ml/column-at-indentation () 232 | (save-excursion (back-to-indentation) (current-column))) 233 | 234 | ;; + LaTeX-specific 235 | 236 | (defun ml/skip-comments-and-verbs (&optional backward) 237 | "Skip forward this comment or verbish environment. Return 238 | non-nil iff the cursor is moved." 239 | (when (and (not (eobp)) ; return non-nil only when moved 240 | (memq (get-text-property (point) 'face) 241 | magic-latex-ignored-properties) 242 | (memq (get-text-property (1- (point)) 'face) 243 | magic-latex-ignored-properties)) 244 | (let ((pos (if backward 245 | (previous-single-property-change (point) 'face) 246 | (next-single-property-change (point) 'face)))) 247 | (goto-char (or pos (if backward (point-min) (point-max)))) 248 | (when pos (ml/skip-comments-and-verbs backward)) 249 | t))) 250 | 251 | (defun ml/search-regexp (regex &optional bound backward point-safe) 252 | "Like `search-regexp' but skips escaped chars, comments and 253 | verbish environments. This function raise an error on 254 | failure. When POINT-SAFE is non-nil, the point must not be in the 255 | matching string." 256 | (ml/safe-excursion 257 | (let ((case-fold-search nil)) 258 | (if backward 259 | (search-backward-regexp regex bound) 260 | (search-forward-regexp regex bound))) 261 | (or (save-match-data 262 | (save-excursion 263 | (and (goto-char (match-beginning 0)) 264 | (not (and point-safe 265 | (< (point) ml/jit-point) 266 | (< ml/jit-point (match-end 0)))) 267 | (looking-back "\\([^\\\\]\\|^\\)\\(\\\\\\\\\\)*" (point-min)) 268 | (not (ml/skip-comments-and-verbs backward))))) 269 | (ml/search-regexp regex bound backward point-safe)))) 270 | 271 | (defun ml/skip-blocks (n &optional exclusive backward brace-only) 272 | "Skip blocks forward until the point reaches n-level upwards. 273 | examples: 274 | [n=1] 275 | inclusive (a b (|c d (e f) g) h) -> (a b (c d (e f) g)| h) 276 | exclusive (a b (|c d (e f) g) h) -> (a b (c d (e f) g|) h) 277 | [n=0] 278 | inclusive (a b (|c d (e f) g) h) -> (a b (c d (e f)| g) h) 279 | exclusive (a b (|c d (e f) g) h) -> (a b (c d (e f|) g) h)" 280 | (ml/safe-excursion 281 | (save-match-data 282 | (condition-case nil 283 | (ml/search-regexp 284 | (if brace-only 285 | "\\({\\)\\|\\(}\\)" 286 | "\\(\\\\begin\\>\\|{\\|\\[\\)\\|\\(\\\\end\\>\\|}\\|]\\)") 287 | nil backward) 288 | (error (error "unmatched blocks"))) 289 | (setq n (if backward 290 | (+ n 291 | (if (match-beginning 1) -1 0) 292 | (if (match-beginning 2) 1 0)) 293 | (+ n 294 | (if (match-beginning 1) 1 0) 295 | (if (match-beginning 2) -1 0)))) 296 | (cond ((< n 0) 297 | (error "unexpected end-of-block")) 298 | ((> n 0) 299 | (ml/skip-blocks n exclusive backward brace-only)) 300 | (exclusive 301 | (if backward 302 | (goto-char (match-end 0)) 303 | (goto-char (match-beginning 0)))) 304 | (t 305 | t))))) 306 | 307 | (defun ml/read-args (&optional option args) 308 | "Look forward something like \"[OPT]{ARG0}...{ARGn}}\" and 309 | set (match-string k) to K-th ARG. this function does not moves 310 | the point." 311 | (while (and option (looking-at " *\\[")) 312 | (ml/skip-blocks 0)) 313 | (when args 314 | (let (res) 315 | (dotimes (_ args) 316 | (unless (looking-at " *{") 317 | (error "too few arguments")) 318 | (push (match-end 0) res) 319 | (ml/skip-blocks 0 nil nil t) 320 | (push (1- (point)) res)) 321 | (setq res (reverse res)) 322 | (set-match-data res) 323 | res))) 324 | 325 | ;; + basic keyword highlighting via font-lock 326 | 327 | (defun ml/command-matcher (regex &optional option args point-safe) 328 | "Generate a forward search function that matches something like 329 | \"REGEX[OPT]{ARG1}...{ARGn}\" and moves the cursor just after the 330 | NAME. (match-string 0) will be NAME and (match-string k) will be 331 | K-th ARG if succeeded." 332 | `(lambda (&optional limit) 333 | (ignore-errors 334 | (ml/search-command ,regex ,option ,args ,point-safe limit)))) 335 | 336 | (defun ml/search-command (regex &optional option args point-safe limit) 337 | "(Internal function for `ml/command-matcher')" 338 | (ml/safe-excursion 339 | (ml/search-regexp regex limit nil point-safe) 340 | (let ((beg (match-beginning 0)) 341 | (end (match-end 0))) 342 | (condition-case nil 343 | (let* ((args (save-excursion (ml/read-args option args))) 344 | (res (cons beg (cons end args)))) 345 | (set-match-data res) 346 | res) 347 | (error (ml/search-command regex option args point-safe limit)))))) 348 | 349 | (defconst ml/keywords-1 350 | (let ((headings 351 | (ml/command-matcher 352 | (ml/command-regexp-opt 353 | '("title" "begin" "end" "chapter" "part" "section" "subsection" 354 | "subsubsection" "paragraph" "subparagraph" "subsubparagraph" 355 | "newcommand" "renewcommand" "providecommand" "newenvironment" 356 | "renewenvironment" "newtheorem" "renewtheorem" 357 | "title*" "begin*" "end*" "chapter*" "part*" "section*" "subsection*" 358 | "subsubsection*" "paragraph*" "subparagraph*" "subsubparagraph*" 359 | "newcommand*" "renewcommand*" "providecommand*" "newenvironment*" 360 | "renewenvironment*" "newtheorem*" "renewtheorem*")) t 1)) 361 | (variables 362 | (ml/command-matcher 363 | (ml/command-regexp-opt 364 | '("newcounter" "newcounter*" "setcounter" "addtocounter" 365 | "setlength" "addtolength" "settowidth")) nil 1)) 366 | (includes 367 | (ml/command-matcher 368 | (ml/command-regexp-opt 369 | '("input" "include" "includeonly" "bibliography" 370 | "epsfig" "psfig" "epsf" "nofiles" "usepackage" 371 | "documentstyle" "documentclass" "verbatiminput" 372 | "includegraphics" "includegraphics*")) t 1)) 373 | (verbish 374 | (ml/command-matcher 375 | (ml/command-regexp-opt '("url" "nolinkurl" "path")) t 1)) 376 | (definitions ; i have no idea what this is 377 | "^[ \t]*\\\\def *\\\\\\(\\(\\w\\|@\\)+\\)")) 378 | `((,headings 1 font-lock-function-name-face keep) 379 | (,variables 1 font-lock-variable-name-face) 380 | (,includes 1 font-lock-constant-face) 381 | (,verbish 1 'tex-verbatim) 382 | (,definitions 1 font-lock-function-name-face))) 383 | "Highlighting keywords based on `tex-font-lock-keywords-1'.") 384 | 385 | (defconst ml/keywords-2 386 | (let ((bold 387 | (ml/command-matcher 388 | (ml/command-regexp-opt 389 | '("textbf" "mathbf" "textsc" "textup" "boldsymbol" "pmb" "bm")) nil 1)) 390 | (italic 391 | (ml/command-matcher 392 | (ml/command-regexp-opt '("textit" "mathit" "textsl" "emph")) nil 1)) 393 | (citations 394 | (ml/command-matcher 395 | (ml/command-regexp-opt 396 | '("label" "ref" "pageref" "vref" "eqref" "cite" "Cite" 397 | "nocite" "index" "glossary" "bibitem" "citep" "citet")) t 1)) 398 | (quotes 399 | (concat (regexp-opt `("``" "\"<" "\"`" "<<" "«") t) 400 | "[^'\">{]+" (regexp-opt `("''" "\">" "\"'" ">>" "»") t))) 401 | (specials-1 402 | (ml/command-matcher "\\\\\\(?:\\\\\\*?\\)" nil nil)) 403 | (specials-2 404 | (ml/command-matcher 405 | (ml/command-regexp-opt 406 | '("linebreak" "nolinebreak" "pagebreak" "nopagebreak" 407 | "newline" "newpage" "clearpage" "cleardoublepage" 408 | "displaybreak" "allowdisplaybreaks" "enlargethispage")) nil nil)) 409 | (other-commands 410 | (ml/command-matcher "\\\\\\(?:[a-zA-Z@]+\\**\\|[^ \t\n]\\)"))) 411 | `((,bold 1 'bold append) 412 | (,italic 1 'italic append) 413 | (,citations 1 font-lock-constant-face) 414 | (,quotes . font-lock-string-face) 415 | (,specials-1 . font-lock-warning-face) 416 | (,specials-2 . font-lock-warning-face) 417 | (,other-commands . font-lock-keyword-face))) 418 | "Highlighting keywords based on `tex-font-lock-keywords-2'.") 419 | 420 | (defconst ml/keywords-3 421 | (let ((title (ml/command-matcher "\\\\title\\>\\*?" nil 1)) 422 | (chapter (ml/command-matcher "\\\\chapter\\>\\*?" t 1)) 423 | (section (ml/command-matcher "\\\\section\\>\\*?" t 1)) 424 | (subsection (ml/command-matcher "\\\\subsection\\>\\*?" t 1)) 425 | (diminish "{}\\|&") 426 | (underline (ml/command-matcher "\\\\underline\\>" nil 1)) 427 | (overline (ml/command-matcher "\\\\overline\\>" nil 1)) 428 | (color (ml/command-matcher "\\\\textcolor\\>" nil 2)) 429 | (type 430 | (ml/command-matcher 431 | (ml/command-regexp-opt 432 | '("texttt" "mathtt" "textmd" "textrm" "mathrm" "textsf" "mathsf")) nil 1)) 433 | (box 434 | (ml/command-matcher 435 | (ml/command-regexp-opt 436 | '("ovalbox" "Ovalbox" "fbox" "doublebox" "shadowbox")) nil 1))) 437 | `((,title 1 'ml/title append) 438 | (,chapter 1 'ml/chapter append) 439 | (,section 1 'ml/section append) 440 | (,subsection 1 'ml/subsection append) 441 | (,diminish 0 'shadow append) 442 | (,underline 1 'underline append) 443 | (,overline 1 'ml/overline append) 444 | (,color 2 (let ((str (match-string 1))) 445 | (cond ((string= str "black") 'ml/black) 446 | ((string= str "white") 'ml/white) 447 | ((string= str "red") 'ml/red) 448 | ((string= str "green") 'ml/green) 449 | ((string= str "blue") 'ml/blue) 450 | ((string= str "cyan") 'ml/cyan) 451 | ((string= str "magenta") 'ml/magenta) 452 | ((string= str "yellow") 'ml/yellow))) append) 453 | (,type 1 'ml/type append) 454 | (,box 1 'ml/box append))) 455 | "Extra highlighting keywords.") 456 | 457 | (defconst ml/keywords 458 | (append ml/keywords-1 ml/keywords-2 ml/keywords-3) 459 | "Font lock keywords for `latex-mode' buffers.") 460 | 461 | ;; + block highlighter 462 | 463 | (defun ml/block-matcher (regex &optional option args point-safe) 464 | "Generate a forward search function that matches something like 465 | \"REGEX[OPT]{ARG1}...{ARGn} ... BODY ... \\end{env}\" and moves 466 | the cursor just after the NAME. (match-string 0) will be 467 | NAME, (match-string 1) will be BODY, and (match-string (1+ k)) 468 | will be K-th ARG if succeeded." 469 | `(lambda (&optional limit) 470 | (ignore-errors 471 | (ml/search-block ,regex ,option ,args ,point-safe limit)))) 472 | 473 | (defun ml/search-block (regex &optional option args point-safe limit) 474 | "(Internal function for `ml/block-matcher')" 475 | (ml/safe-excursion 476 | (ml/search-regexp regex limit nil point-safe) 477 | (let ((command-beg (match-beginning 0)) 478 | (command-end (match-end 0))) 479 | (condition-case nil 480 | (save-excursion 481 | (let* ((res (ml/read-args option args)) 482 | (content-beg (point)) 483 | (content-end (condition-case nil 484 | (progn (ml/skip-blocks 1 t) (point)) 485 | (error (1+ (buffer-size)))))) 486 | (setq res (cons command-beg 487 | (cons command-end 488 | (cons content-beg 489 | (cons content-end res))))) 490 | (set-match-data res) 491 | res)) 492 | (error (ml/search-block regex option args point-safe limit)))))) 493 | 494 | (defconst ml/block-commands 495 | (let ((tiny (ml/block-matcher "\\\\tiny\\>" nil nil)) 496 | (script (ml/block-matcher "\\\\scriptsize\\>" nil nil)) 497 | (footnote (ml/block-matcher "\\\\footnotesize\\>" nil nil)) 498 | (small (ml/block-matcher "\\\\small\\>" nil nil)) 499 | (large (ml/block-matcher "\\\\large\\>" nil nil)) 500 | (llarge (ml/block-matcher "\\\\Large\\>" nil nil)) 501 | (xlarge (ml/block-matcher "\\\\LARGE\\>" nil nil)) 502 | (huge (ml/block-matcher "\\\\huge\\>" nil nil)) 503 | (hhuge (ml/block-matcher "\\\\Huge\\>" nil nil)) 504 | (type (ml/block-matcher "\\\\tt\\>" nil nil)) 505 | (italic (ml/block-matcher "\\\\\\(?:em\\|it\\|sl\\)\\>" nil nil)) 506 | (bold (ml/block-matcher "\\\\bf\\(?:series\\)?\\>" nil nil)) 507 | (color (ml/block-matcher "\\\\color" nil 1))) 508 | `((,tiny . 'ml/tiny) 509 | (,script . 'ml/script) 510 | (,footnote . 'ml/footnote) 511 | (,small . 'ml/small) 512 | (,large . 'ml/large) 513 | (,llarge . 'ml/llarge) 514 | (,xlarge . 'ml/xlarge) 515 | (,huge . 'ml/huge) 516 | (,hhuge . 'ml/hhuge) 517 | (,type . 'ml/type) 518 | (,italic . 'italic) 519 | (,bold . 'bold) 520 | (,color . (let ((col (match-string 2))) 521 | (cond ((string= col "black") 'ml/black) 522 | ((string= col "white") 'ml/white) 523 | ((string= col "red") 'ml/red) 524 | ((string= col "green") 'ml/green) 525 | ((string= col "blue") 'ml/blue) 526 | ((string= col "cyan") 'ml/cyan) 527 | ((string= col "magenta") 'ml/magenta) 528 | ((string= col "yellow") 'ml/yellow)))))) 529 | "An alist of (MATCHER . FACE). MATCHER is a function that takes 530 | an argument, limit of the search, and does a forward search like 531 | `search-forward-regexp' then sets match-data as needed. FACE is 532 | *a sexp* which evaluates to a face. (match-string 1) will be 533 | propertized with the face.") 534 | 535 | (defun ml/make-block-overlay (command-beg command-end content-beg content-end &rest props) 536 | "Make a pair of overlays, a content overlay and a command 537 | overlay. The command overlay will have `partner' property, that 538 | points the content overlay which the command is associated 539 | with. The content overlay will have PROPS as its properties." 540 | (let* ((ov1 (make-overlay command-beg command-end)) 541 | (ov2 (make-overlay content-beg content-end))) 542 | (overlay-put ov1 'category 'ml/ov-block) 543 | (overlay-put ov1 'partner ov2) 544 | (while props 545 | (overlay-put ov2 (car props) (cadr props)) 546 | (setq props (cddr props))) 547 | ov2)) 548 | 549 | (defun ml/remove-block-overlays (beg end) 550 | "Remove all command overlays and their content overlays from 551 | BEG END." 552 | (dolist (ov (overlays-in beg end)) 553 | (when (eq (overlay-get ov 'category) 'ml/ov-block) 554 | (delete-overlay (overlay-get ov 'partner)) 555 | (delete-overlay ov)))) 556 | 557 | (defun ml/jit-block-highlighter (_ end) 558 | (when magic-latex-enable-block-highlight 559 | (condition-case nil 560 | (progn (ml/skip-blocks 1 nil t) (point)) 561 | (error (goto-char 1))) 562 | (ml/remove-block-overlays (point) end) 563 | (dolist (command ml/block-commands) 564 | (save-excursion 565 | (while (funcall (car command) end) 566 | (ml/make-block-overlay (match-beginning 0) (match-end 0) 567 | (match-beginning 1) (match-end 1) 568 | 'face (eval (cdr command)))))))) 569 | 570 | ;; + block aligner 571 | 572 | (defconst ml/align-commands 573 | `((,(ml/block-matcher "\\\\\\(?:centering\\>\\|begin{center}\\)" nil nil) . center) 574 | (,(ml/block-matcher "\\\\\\(?:raggedleft\\>\\|begin{flushleft}\\)" nil nil) . left) 575 | (,(ml/block-matcher "\\\\\\(?:raggedright\\>\\|begin{flushright}\\)" nil nil) . right) 576 | (,(ml/block-matcher "\\\\begin{\\(?:quot\\(?:e\\|ation\\)\\|leftbar\\)}" nil nil) . quote)) 577 | "An alist of (MATCHER . POSITION). MATCHER is a function that 578 | takes an argument, limit of the search, and does a forward search 579 | like `search-forward-regexp' then sets match-data as 580 | needed. POSITION can be one of 'center 'right 'left 'quote.") 581 | 582 | (defun ml/make-align-overlay (command-beg command-end content-beg content-end position) 583 | "Make a command overlay and alignment overlay(s) like 584 | `ml/make-block-overlay'. The command overlay will have `partners' 585 | property, which is bound to the list of all alignment 586 | overlay(s)." 587 | (save-excursion 588 | (goto-char content-end) 589 | (end-of-line 0) 590 | (setq content-end (point)) 591 | (when (< content-beg content-end) 592 | (remove-overlays content-beg content-end 'category 'ml/ov-align-alignment) 593 | (let ((ov (make-overlay command-beg command-end)) 594 | ovs) 595 | (goto-char content-beg) 596 | (forward-line 1) 597 | (while (<= (point-at-eol) content-end) 598 | (cond ((ignore-errors 599 | (ml/search-regexp "{\\|\\\\begin\\_>" (min content-end (point-at-eol)))) 600 | (let* ((block-end (condition-case nil 601 | (save-excursion (ml/skip-blocks 1) (point)) 602 | (error (point-max)))) 603 | (bols (list (point-at-bol))) 604 | (width (ml/column-at-eol)) 605 | (indentation (ml/column-at-indentation))) 606 | (while (progn 607 | (forward-line 1) 608 | (< (point) block-end)) 609 | (push (point) bols) 610 | (setq width (max width (ml/column-at-eol)) 611 | indentation (min indentation (ml/column-at-indentation)))) 612 | (setq ovs (nconc (mapcar 613 | (lambda (p) 614 | (ml/make-align-overlay-1 p width indentation position)) 615 | bols) 616 | ovs)))) 617 | (t 618 | (push (ml/make-align-overlay-1 619 | (point) (ml/column-at-eol) (ml/column-at-indentation) position) 620 | ovs) 621 | (forward-line 1)))) 622 | (overlay-put ov 'category 'ml/ov-align) 623 | (overlay-put ov 'partners ovs))))) 624 | 625 | (defun ml/make-align-overlay-1 (pos width indentation position) 626 | "*internal function for `ml/make-align-overlay'*" 627 | (let ((prop (cl-case position 628 | ((left) `((space :align-to left))) 629 | ((center) `((space :align-to (- center ,(/ width 2) ,(/ indentation 2))))) 630 | ((right) `((space :align-to (- right ,width)))) 631 | ((quote) (propertize " " 'face 'ml/lbar)))) 632 | (ov (make-overlay pos pos))) 633 | (overlay-put ov 'before-string (propertize " " 'display prop)) 634 | (overlay-put ov 'category 'ml/ov-align-alignment) 635 | ov)) 636 | 637 | (defun ml/remove-align-overlays (beg end) 638 | "Remove all command overlays and their alignment overlays 639 | between BEG and END." 640 | (dolist (ov (overlays-in beg end)) 641 | (when (eq (overlay-get ov 'category) 'ml/ov-align) 642 | (mapc 'delete-overlay (overlay-get ov 'partners)) 643 | (delete-overlay ov)))) 644 | 645 | (defun ml/jit-block-aligner (_ end) 646 | (when magic-latex-enable-block-align 647 | (condition-case nil 648 | (progn (ml/skip-blocks 1 nil t) (point)) 649 | (error (goto-char 1))) 650 | (ml/remove-align-overlays (point) end) 651 | (dolist (command ml/align-commands) 652 | (save-excursion 653 | (while (funcall (car command) end) 654 | (ml/make-align-overlay (match-beginning 0) (match-end 0) 655 | (match-beginning 1) (match-end 1) 656 | (cdr command))))))) 657 | 658 | ;; + pretty symbol/suscript 659 | 660 | (defconst ml/decoration-commands 661 | '(("\\\\\\(\\(?:\\(?:text\\|math\\)\\(?:md\\|rm\\|sf\\|tt\\)\\)\\|verb\\)\\>" 662 | . (propertize "T" 'face 'ml/type)) 663 | ("\\\\\\(?:emph\\|\\(?:text\\|math\\)\\(?:it\\|sl\\)\\)\\>" 664 | . (propertize "I" 'face 'italic)) 665 | ("\\\\\\(?:b\\(?:m\\|oldsymbol\\)\\|pmb\\|text\\(?:bf\\|sc\\|up\\)\\)\\>" 666 | . (propertize "B" 'face 'bold)) 667 | ("\\\\underline\\>" 668 | . (propertize "U" 'face 'underline)) 669 | ("\\\\overline\\>" 670 | . (propertize "O" 'face 'ml/overline)) 671 | ("\\\\mbox\\>" . "'") 672 | ("\\\\mathcal\\>" . "ℭ") 673 | )) 674 | 675 | (defconst ml/relation-symbols 676 | '( 677 | ;; basic 678 | ("\\\\eq\\>" . "=") ("\\\\doteq\\>" . "≐") ("\\\\equiv\\>" . "≡") 679 | ("\\\\sim\\>" . "~") ("\\\\simeq\\>" . "≃") ("\\\\cong\\>" . "≅") 680 | ("\\\\succ\\>" . "≻") ("\\\\prec\\>" . "≺") 681 | ("\\\\succeq\\>" . "≽") ("\\\\preceq\\>" . "≼") 682 | ("\\\\approx\\>" . "≒") 683 | ("\\\\le\\(?:q\\)?\\>" . "≦") ("\\\\ge\\(?:q\\)?\\>" . "≧") 684 | ("\\\\lesssim\\>" . "≲") ("\\\\gtrsim\\>" . "≳") 685 | ("\\\\ll\\>" . "≪") ("\\\\gg\\>" . "≫") 686 | ("\\\\to\\>" . "→") ("\\\\mapsto\\>" . "↦") 687 | ("\\\\colon\\>" . ":") 688 | ("\\\\propto\\>" . "∝") 689 | 690 | ;; set 691 | ("\\\\subseteq\\>" . "⊆") ("\\\\subset\\>" . "⊂") 692 | ("\\\\supseteq\\>" . "⊇") ("\\\\supset\\>" . "⊃") 693 | ("\\\\in\\>" . "∈") ("\\\\ni\\>" . "∋") 694 | ("\\\\sqsubseteq\\>" . "⊑") ("\\\\sqsupseteq\\>" . "⊒") 695 | 696 | ;; logic 697 | ("\\\\models\\>" . "⊧") ("\\\\vDash\\>" . "⊨") 698 | ("\\\\vdash\\>" . "⊢") ("\\\\dashv\\>" . "⊣") 699 | ("\\\\rightarrow\\>" . "→") ("\\\\leftarrow\\>" . "←") 700 | ("\\\\leftrightarrow\\>" . "↔") ("\\\\Leftarrow\\>" . "⇐") 701 | ("\\\\Rightarrow\\>" . "⇒") ("\\\\Leftrightarrow\\>" . "⇔") 702 | ("\\\\implies\\>" . "⟹") 703 | 704 | ;; geometry 705 | ("\\\\parallel\\>" . "∥") ("\\\\perp\\>" . "⊥") 706 | 707 | ;; ??? 708 | ("\\\\asymp\\>" . "≍") ("\\\\smile\\>" . "⌣") ("\\\\frown\\>" . "⌢") 709 | ("\\\\lhd//>" . "⊲") ("\\\\unlhd\\>" . "⊴") 710 | ("\\\\rhd\\>" . "⊳") ("\\\\unrhd\\>" . "⊵") 711 | ("\\\\\\(?:bowtie\\|Join\\)\\>" . "⋈") 712 | )) 713 | 714 | ;; Use composition so that it matches with \not command 715 | (defconst ml/negrel-symbols 716 | '(("\\\\neq\\>" . (compose-chars ?/ ?=)) 717 | ("\\\\notin\\>" . (compose-chars ?/ ?∈)) 718 | ("\\\\notni\\>" . (compose-chars ?/ ?∋)))) 719 | 720 | (defconst ml/operator-symbols 721 | '( 722 | ;; set 723 | ("\\\\mid\\>" . "|") ("\\\\\\(?:set\\)?minus\\>" . "\") 724 | ("\\\\\\(?:big\\)?cup\\>" . "∪") ("\\\\\\(?:big\\)?cap\\>" . "∩") 725 | ("\\\\\\(?:big\\)?sqcup\\>" . "⊔") ("\\\\\\(?:big\\)?sqcap\\>" . "⊓") 726 | ("\\\\\\(?:big\\)?uplus\\>" . "⨄") ("\\\\amalg\\>" . "⨿") 727 | 728 | ;; logic 729 | ("\\\\exists\\>" . "∃") ("\\\\forall\\>" . "∀") ("\\\\\\(?:neg\\|lnot\\)\\>" . "¬") 730 | ("\\\\land\\>" . "∧") ("\\\\lor\\>" . "∨") 731 | 732 | ;; algebra 733 | ("\\\\cdot\\>" . "・") ("\\\\times\\>" . "×") ("\\\\div)\\>" . "÷") 734 | ("\\\\\\(?:big\\)?wedge\\>" . "∧") ("\\\\\\(?:big\\)?vee\\>" . "∨") 735 | ("\\\\prod\\>" . "∏") ("\\\\coprod\\>" . "∐") ("\\\\sum\\>" . "∑") 736 | 737 | ("\\\\\\(?:big\\)?odot\\>" . "⊙") ("\\\\oslash\\>" . "⊘") 738 | ("\\\\\\(?:big\\)?otimes\\>" . "⊗") ("\\\\\\(?:big\\)?oplus\\>" . "⊕") 739 | ("\\\\ominus\\>" . "⊖") ("\\\\ast\\>" . "∗") 740 | 741 | ;; analysis 742 | ("\\\\mp\\>" . "∓") ("\\\\pm\\>" . "±") 743 | ("\\\\Re\\>" . "ℜ") ("\\\\Im\\>" . "ℑ") ("\\\\angle\\>" . "∠") 744 | ("\\\\s\\(?:urd\\|qrt\\)\\>" . "√") ("\\\\partial\\>" . "∂") 745 | ("\\\\int\\>" . "∫") ("\\\\iint\\>" . "∬") 746 | ("\\\\iiint\\>" . "∭") ("\\\\iiiint\\>" . "⨌") 747 | ("\\\\oint\\>" . "∮") ("\\\\oiint\\>" . "∯") ("\\\\oiiint\\>" . "∰") 748 | ("\\\\ointclockwise\\>" . "∲") ("\\\\ointctrclockwise\\>" . "∳") 749 | ("\\\\varlimsup\\>" . (propertize "lim" 'face 'ml/overline)) 750 | ("\\\\varliminf\\>" . (propertize "lim" 'face 'underline)) 751 | 752 | ;; ??? 753 | ("\\\\wp\\>" . "≀") 754 | ("\\\\bullet\\>" . "●") 755 | ("\\\\circ\\>" . "o") ("\\\\bigcirc\\>" . "○") 756 | ("\\\\diamond\\>" . "♢") ("\\\\Diamond\\>" . "◇") 757 | ("\\\\star\\>" . "★") ("\\\\triangle\\>" . "△") 758 | ("\\\\triangleleft\\>" . "◁") ("\\\\triangleright\\>" . "▷") 759 | ("\\\\trianglelefteq\\>" . "⊴") ("\\\\trianglerighteq\\>" . "⊵") 760 | ("\\\\bigtriangleup\\>" . "△") ("\\\\bigtriangledown\\>" . "▽") 761 | )) 762 | 763 | (defconst ml/arrow-symbols 764 | '( 765 | ;; harpoon 766 | ("\\\\leftharpoonup\\>" . "↼") ("\\\\rightharpoonup\\>" . "⇀") 767 | ("\\\\leftharpoondown\\>" . "↽") ("\\\\rightharpoondown\\>" . "⇁") 768 | ("\\\\leftrightharpoons\\>" . "⇋") ("\\\\rightleftharpoons\\>" . "⇌") 769 | ("\\\\upharpoonleft\\>" . "↿") ("\\\\upharpoonright\\>" . "↾") 770 | ("\\\\downharpoonleft\\>" . "⇃") ("\\\\downharpoonright\\>" . "⇂") 771 | 772 | ;; long 773 | ("\\\\longrightarrow\\>" . "⟶") ("\\\\longleftarrow\\>" . "⟵") 774 | ("\\\\Longrightarrow\\>" . "⟹") ("\\\\Longleftarrow\\>" . "⟸") 775 | ("\\\\longmapsto\\>" . "⟼") 776 | ("\\\\longleftrightarrow\\>" . "⟷") ("\\\\Longleftrightarrow\\>" . "⟺") 777 | 778 | ;; 45deg 779 | ("\\\\nearrow\\>" . "↗") ("\\\\searrow\\>" . "↘") 780 | ("\\\\nwarrow\\>" . "↖") ("\\\\swarrow\\>" . "↙") 781 | 782 | ;; 90deg 783 | ("\\\\uparrow\\>" . "↑") ("\\\\downarrow\\>" . "↓") ("\\\\updownarrow\\>" . "↕") 784 | ("\\\\upuparrows\\>" . "⇈") ("\\\\downdownarrows\\>" . "⇊") 785 | ("\\\\Uparrow\\>" . "⇑") ("\\\\Downarrow\\>" . "⇓") ("\\\\Updownarrow\\>" . "⇕") 786 | 787 | ;; others 788 | ("\\\\hookleftarrow\\>" . "↩") ("\\\\hookrightarrow\\>" . "↪") 789 | ("\\\\twoheadleftarrow\\>" . "↞") ("\\\\twoheadrightarrow\\>" . "↠") 790 | ("\\\\looparrowleft\\>" . "↫") ("\\\\looparrowright\\>" . "↬") 791 | ("\\\\rightsquigarrow\\>" . "⇝") ("\\\\leftrightsquigarrow\\>" . "↭") 792 | ("\\\\leftleftarrows\\>" . "⇇") ("\\\\rightrightarrows\\>" . "⇉") 793 | ("\\\\leftrightarrows\\>" . "⇆") ("\\\\rightleftarrows\\>" . "⇄") 794 | ("\\\\Lleftarrow\\>" . "⇚") ("\\\\Rrightarrow\\>" . "⇛") 795 | )) 796 | 797 | (defconst ml/letter-symbols 798 | '( 799 | ;; greek (upper) 800 | ("\\\\Alpha\\>" . "Α") ("\\\\Beta\\>" . "Β") 801 | ("\\\\Gamma\\>" . "Γ") ("\\\\Delta\\>" . "Δ") 802 | ("\\\\Epsilon\\>" . "Ε") ("\\\\Zeta\\>" . "Ζ") 803 | ("\\\\Eta\\>" . "Η") ("\\\\Theta\\>" . "Θ") 804 | ("\\\\Iota\\>" . "Ι") ("\\\\Kappa\\>" . "Κ") 805 | ("\\\\Lambda\\>" . "Λ") ("\\\\Mu\\>" . "Μ") 806 | ("\\\\Nu\\>" . "Ν") ("\\\\Xi\\>" . "Ξ") 807 | ("\\\\Omicron\\>" . "Ο") ("\\\\Pi\\>" . "Π") 808 | ("\\\\Rho\\>" . "Ρ") ("\\\\Sigma\\>" . "Σ") 809 | ("\\\\Tau\\>" . "Τ") ("\\\\Upsilon\\>" . "Υ") 810 | ("\\\\Phi\\>" . "Φ") ("\\\\Chi\\>" . "Χ") 811 | ("\\\\Psi\\>" . "Ψ") ("\\\\Omega\\>" . "Ω") 812 | 813 | ;; greek (lower) 814 | ("\\\\alpha\\>" . "α") ("\\\\beta\\>" . "β") 815 | ("\\\\gamma\\>" . "γ") ("\\\\delta\\>" . "δ") 816 | ("\\\\\\(?:var\\)?epsilon\\>" . "ε") ("\\\\zeta\\>" . "ζ") 817 | ("\\\\eta\\>" . "η") ("\\\\\\(?:var\\)?theta\\>" . "θ") 818 | ("\\\\iota\\>" . "ι") ("\\\\kappa\\>" . "κ") 819 | ("\\\\lambda\\>" . "λ") ("\\\\mu\\>" . "μ") 820 | ("\\\\nu\\>" . "ν") ("\\\\xi\\>" . "ξ") 821 | ("\\\\omicron\\>" . "ο") ("\\\\\\(?:var\\)?pi\\>" . "π") 822 | ("\\\\\\(?:var\\)?rho\\>" . "ρ") ("\\\\\\(?:var\\)?sigma\\>" . "σ") 823 | ("\\\\tau\\>" . "τ") ("\\\\upsilon\\>" . "υ") 824 | ("\\\\\\(?:var\\)?phi\\>" . "φ") ("\\\\chi\\>" . "χ") 825 | ("\\\\psi\\>" . "ψ") ("\\\\omega\\>" . "ω") 826 | 827 | ;; latin / accented 828 | ("\\\\ss\\>" . "ß") ("\\\\aa\\>" . "å") ("\\\\AA\\>" . "Å") 829 | ("\\\\ae\\>" . "æ") ("\\\\oe\\>" . "œ") ("\\\\AE\\>" . "Æ") ("\\\\OE\\>" . "Œ") 830 | ("\\\\o\\>" . "ø") ("\\\\O\\>" . "Ø") 831 | 832 | ;; others 833 | ("\\\\aleph\\>" . "ℵ") 834 | ("\\\\hbar\\>" . "ℏ") ("\\\\ell\\>" . "ℓ") ("\\\\wp\\>" . "℘") 835 | ("\\\\l\\>" . "ł") ("\\\\L\\>" . "Ł") ("\\\\S\\>" . "§") 836 | )) 837 | 838 | (defconst ml/other-symbols 839 | '( 840 | ;; TeX commands 841 | ("\\\\begin\\>" . "▽") ("\\\\end\\>" . "△") 842 | ;; ("\\\\begin\\>" . "◸") ("\\\\end\\>" . "◺") 843 | ("\\\\\\(?:bib\\)?item\\>" . "*") ("\\\\par\\>" . "¶") 844 | ("\\\\ref\\>" . "☞") ("\\\\\\(?:c\\|C\\)ite\\>" . "†") 845 | ("\\\\footnote\\(?:mark\\)?\\>" . "‡") 846 | ("\\\\left\\>" . "¡") ("\\\\right\\>" . "!") 847 | ("~\\|\\\\\\(?:[,;\s]\\|\\(?:hs\\(?:pace\\|kip\\)\\|q?quad\\)\\>\\)" . "␣") 848 | ("\\\\\\(?:newline\\>\\|\\\\\\)" . "⏎") 849 | ("\\\\multicolumn\\>" . "|↔|") 850 | ("\\\\TeX\\>" . (compose-chars ?T '(cr cl -20 -45) ?E '(cr cl -20 24) ?X)) 851 | ("\\\\LaTeX\\>" . (compose-chars ?L '(cr cl -60 35) ?A '(cr cl -18 -20) 852 | ?T '(cr cl -18 -60) ?E '(cr cl -20 5) ?X)) 853 | 854 | ;; escaped symbols 855 | ("\\\\\\$" . "$") ("\\\\%" . "%") ("\\\\#" . "#") ("\\\\_" . "_") 856 | ;; alignment character (&) 857 | ("&" . (compose-chars ?& ?|)) 858 | 859 | ;; parens 860 | ("\\\\\\(?:{\\|lbrace\\>\\)" . "⎨") 861 | ("\\\\\\(?:}\\|rbrace\\>\\)" . "⎬") 862 | ("\\\\|" . "║") 863 | ("\\\\lbrack\\>" . "[") ("\\\\rbrack\\>" . "]") 864 | ("\\\\\\(?:double\\[\\|lBrack\\>\\)" 865 | . (compose-chars ?\[ '(cr cl -90 0) ?\[)) 866 | ("\\\\\\(?:double\\]\\|rBrack\\>\\)" 867 | . (compose-chars ?\] '(cr cl -90 0) ?\])) 868 | ("\\\\lceil\\>" . "⌈") ("\\\\rceil\\>" . "⌉") 869 | ("\\\\lgroup\\>" . "〔") ("\\\\rgroup" . "〕") 870 | ("\\\\langle\\>" . "〈") ("\\\\rangle\\>" . "〉") 871 | ("\\\\lfloor\\>" . "⌊") ("\\\\rfloor\\>" . "⌋") 872 | 873 | ;; math 874 | ("\\\\emptyset\\>" . "∅") ("\\\\bot\\>" . "⊥") ("\\\\top\\>" . "⊤") 875 | ("\\\\therefore\\>" . "∴") ("\\\\because\\>" . "∵") 876 | ("\\\\infty\\>" . "∞") ("\\\\nabla\\>" . "∇") 877 | 878 | ;; music (?) 879 | ("\\\\flat\\>" . "♭") ("\\\\natural\\>" . "♮") ("\\\\sharp\\>" . "#") 880 | 881 | ;; others 882 | ("\\\\dots\\>" . "…") ("\\\\cdots\\>" . "⋯") 883 | ("\\\\vdots\\>" . "⋮") ("\\\\ddots\\>" . "⋱") 884 | ("\\\\\\(?:text\\)?backslash\\>" . "\") 885 | ("\\\\\\(?:qed\\|Box\\)\\>" . "□") ("\\\\lightning\\>" . "Ϟ") 886 | ("\\\\dag\\(?:ger\\)?\\>" . "†") ("\\\\ddag\\(?:ger\\)?\\>" . "‡") 887 | ("\\\\copyright\\>" . "©") ("\\\\texistregistered\\?" . "®") 888 | ("\\\\texttrademark\\>" . "™") 889 | ("\\\\clubsuit\\>" . "♣") ("\\\\diamondsuit\\>" . "♦") 890 | ("\\\\heartsuit\\>" . "♥") ("\\\\spadesuit\\>" . "♠") 891 | ("\\\\pounds\\>" . "£") ("\\\\P\\>" . "¶") 892 | ("\\\\lvert\\>" . "|") ("\\\\rvert\\>" . "|") 893 | ("\\\\lVert\\>" . "ǁ") ("\\\\rVert\\>" . "ǁ") 894 | )) 895 | 896 | (defconst ml/accents 897 | `(("\\\\\\(?:mathbb\\){\\([^}]\\)}" 898 | ;; . (let ((ch (string-to-char (match-string 1)))) (compose-chars ch '(cc cl -85 0) ch)) 899 | . (let ((ch (string-to-char (match-string 1)))) (compose-chars ch '(cc cl -96 0) ch)) 900 | ) 901 | ("\\\\\\(?:vec\\){\\([^}]\\)}" 902 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?→)) 903 | ("\\\\\\(?:tilde\\|~\\){\\([^}]\\)}" 904 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?~)) 905 | ("\\\\\\(?:bar\\|=\\){\\([^}]\\)}" 906 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?-)) 907 | ("\\\\\\(?:dot\\|\\.\\){\\([^}]\\)}" 908 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?・)) 909 | ("\\\\\\(?:hat\\|\\^\\){\\([^}]\\)}" 910 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 45) ?^)) 911 | ("\\\\\\(?:acute\\|'\\){\\([^}]\\)}" 912 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 45) ?')) 913 | ("\\\\\\(?:\"\\|H\\|ddot\\){\\([^}]\\)}" 914 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 45) ?\")) 915 | ("\\\\\\(?:grave\\|`\\){\\([^}]\\)}" 916 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 30) ?`)) 917 | ("\\\\\\(?:check\\|`\\){\\([^}]\\)}" 918 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?v)) 919 | ("\\\\\\(?:breve\\|`\\){\\([^}]\\)}" 920 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?⌣)) 921 | ("\\\\r{\\([^}]\\)}" 922 | . (compose-chars (string-to-char (match-string 1)) '(cc Bc 0 60) ?o)) 923 | )) 924 | 925 | (defconst ml/symbols 926 | (append (mapcar (lambda (pattern) 927 | (cons (concat "\\\\not[ \t\n]*" (car pattern)) 928 | (compose-string (concat "/" (cdr pattern))))) 929 | (append ml/relation-symbols ml/arrow-symbols)) 930 | ml/decoration-commands 931 | ml/relation-symbols 932 | ml/negrel-symbols 933 | ml/operator-symbols 934 | ml/arrow-symbols 935 | ml/letter-symbols 936 | ml/other-symbols 937 | ml/accents) 938 | "An alist of (REGEXP . EXPR). REGEXP is a regular expression 939 | that matches to a command that will be prettified, and EXPR is *a 940 | sexp* which evaluates to a display string for the command. You 941 | can assume that the expressions are evaluated immediately after 942 | regex search, so that you can use match data in the 943 | expressions.") 944 | 945 | (defun ml/make-pretty-overlay (from to &rest props) 946 | "Make an overlay from FROM to TO, that has PROPS as its 947 | properties. The overlay is removed as soon as the text between 948 | FROM and TO is modified." 949 | (let* ((ov (make-overlay from to)) 950 | (hooks (list `(lambda (&rest _) (delete-overlay ,ov))))) 951 | (overlay-put ov 'category 'ml/ov-pretty) 952 | (overlay-put ov 'modification-hooks hooks) 953 | (overlay-put ov 'insert-in-front-hooks hooks) 954 | (while props 955 | (overlay-put ov (car props) (cadr props)) 956 | (setq props (cddr props))) 957 | ov)) 958 | 959 | (defun ml/remove-pretty-overlays (beg end) 960 | "Remove all overlays created with `ml/make-pretty-overlay' 961 | between BEG and END." 962 | (remove-overlays beg end 'category 'ml/ov-pretty)) 963 | 964 | (defun ml/search-suscript (point-safe limit) 965 | "Search forward something like \"^{BODY}\" or \"_{BODY}\" and 966 | set (match-string 0) to \"_\" or \"^\", (match-string 1) to 967 | \"{BODY}\". when POINT-SAFE is non-nil, the point must not be in 968 | the command name." 969 | (ml/safe-excursion 970 | (ml/search-regexp 971 | ;; 1( _^ delims ) 2( character | \ ( command-name ) | 3( { ) ) 972 | "\\([_^][ \t]*\\)\\([^ \t\n\\{}]\\|\\\\\\(?:[a-zA-Z@]+\\**\\|[^ \t\n]\\)\\|\\({\\)\\)" 973 | limit nil point-safe) 974 | (let ((delim-beg (match-beginning 1)) 975 | (delim-end (match-end 1)) 976 | (body-beg (match-beginning 2)) 977 | (body-end (match-end 2)) 978 | (brace-beg (match-beginning 3))) 979 | (condition-case nil 980 | (let* ((brace-end (when brace-beg 981 | (ml/skip-blocks 1 nil nil t) 982 | (point))) 983 | (res (list delim-beg delim-end body-beg 984 | (or brace-end body-end)))) 985 | (goto-char delim-end) 986 | (set-match-data res) 987 | res) 988 | (error (ml/search-suscript point-safe limit)))))) 989 | 990 | (defun ml/jit-prettifier (beg end) 991 | (goto-char beg) 992 | (ml/remove-pretty-overlays beg end) 993 | ;; prettify suscripts 994 | (when magic-latex-enable-suscript 995 | (save-excursion 996 | (while (ignore-errors (ml/search-suscript t end)) 997 | (let* ((body-beg (match-beginning 1)) 998 | (body-end (match-end 1)) 999 | (delim-beg (match-beginning 0)) 1000 | (delim-end (match-end 0)) 1001 | ;; the point can be already prettified in a recursive 1002 | ;; suscript like "a_{b_c}". 1003 | (oldov (ml/overlay-at body-beg 'category 'ml/ov-pretty)) 1004 | (oldprop (and oldov (overlay-get oldov 'display))) 1005 | (priority-base (and oldov (or (overlay-get oldov 'priority) 0))) 1006 | (raise-base (or (cadr (assoc 'raise oldprop)) 0.0)) 1007 | (height-base (or (cadr (assoc 'height oldprop)) 1.0)) 1008 | (_ (ml/make-pretty-overlay delim-beg delim-end 'invisible t)) 1009 | ;; new overlay must have higher priority than the old 1010 | ;; one. 1011 | (ov (ml/make-pretty-overlay 1012 | body-beg body-end 'priority (when oldov (1+ priority-base))))) 1013 | (cl-case (string-to-char (match-string 0)) 1014 | ((?_) (overlay-put 1015 | ov 'display 1016 | `((raise ,(- raise-base 0.2)) (height ,(* height-base 0.8))))) 1017 | ((?^) (overlay-put 1018 | ov 'display 1019 | `((raise ,(+ raise-base 0.2)) (height ,(* height-base 0.8)))))))))) 1020 | ;; prettify symbols 1021 | (when magic-latex-enable-pretty-symbols 1022 | (dolist (symbol ml/symbols) 1023 | (save-excursion 1024 | (let ((regex (car symbol))) 1025 | (while (ignore-errors (ml/search-regexp regex end nil t)) 1026 | (let* ((oldov (ml/overlay-at (match-beginning 0) 'category 'ml/ov-pretty)) 1027 | (priority-base (and oldov (or (overlay-get oldov 'priority) 1))) 1028 | (oldprop (and oldov (overlay-get oldov 'display)))) 1029 | (unless (stringp oldprop) 1030 | (ml/make-pretty-overlay 1031 | (match-beginning 0) (match-end 0) 1032 | 'priority (when oldov (1+ priority-base)) 1033 | 'display (propertize (eval (cdr symbol)) 'display oldprop)))))))))) 1034 | 1035 | ;; + activate 1036 | 1037 | (defun ml/post-command-function () 1038 | (let ((ov (ml/overlay-at (point) 'category 'ml/ov-pretty)) 1039 | (message-log-max nil)) ; do not to insert to *Messages* buffer 1040 | (when (and ov magic-latex-enable-minibuffer-echo) 1041 | (message (buffer-substring-no-properties (overlay-start ov) (overlay-end ov)))))) 1042 | 1043 | ;;;###autoload 1044 | (define-minor-mode magic-latex-buffer 1045 | "Minor mode that highlights latex document magically." 1046 | :init-value nil 1047 | :global nil 1048 | :lighter " mLaTeX" 1049 | (if magic-latex-buffer 1050 | (progn 1051 | (add-hook 'post-command-hook 'ml/post-command-function nil t) 1052 | (jit-lock-mode 1) 1053 | (setq-local font-lock-multiline t) 1054 | (set-syntax-table ml/syntax-table) 1055 | (font-lock-add-keywords nil ml/keywords 'set) 1056 | (jit-lock-register 'ml/jit-prettifier) 1057 | (jit-lock-register 'ml/jit-block-highlighter) 1058 | (jit-lock-register 'ml/jit-block-aligner) 1059 | ;; our prettifiers assume that the region is already fontified 1060 | ;; (to recognize verbatim environments, constants and 1061 | ;; comments), thus we need to push `font-lock-fontify-region' 1062 | ;; before our prettifiers. 1063 | (jit-lock-register 'font-lock-fontify-region) 1064 | (set (make-local-variable 'iimage-mode-image-regex-alist) 1065 | `((,(concat "\\\\includegraphics[\s\t]*\\(?:\\[[^]]*\\]\\)?[\s\t]*" 1066 | "{\\(" iimage-mode-image-filename-regex "\\)}") . 1))) 1067 | (when magic-latex-enable-inline-image (iimage-mode 1))) 1068 | (remove-hook 'post-command-hook 'ml/post-command-function t) 1069 | (set-syntax-table tex-mode-syntax-table) 1070 | (jit-lock-unregister 'ml/jit-prettifier) 1071 | (jit-lock-unregister 'ml/jit-block-highlighter) 1072 | (jit-lock-unregister 'ml/jit-block-aligner) 1073 | (jit-lock-unregister 'font-lock-fontify-region) 1074 | (ml/remove-block-overlays (point-min) (point-max)) 1075 | (ml/remove-pretty-overlays (point-min) (point-max)) 1076 | (ml/remove-align-overlays (point-min) (point-max)) 1077 | (font-lock-refresh-defaults) 1078 | (iimage-mode -1))) 1079 | 1080 | ;; + provide 1081 | 1082 | (provide 'magic-latex-buffer) 1083 | 1084 | ;;; magic-latex-buffer.el ends here 1085 | --------------------------------------------------------------------------------