From 8124f2ae8b0b27da4c8083c44acb295c9fceb169 Mon Sep 17 00:00:00 2001 From: Tobias Zawada Date: Tue, 30 Apr 2019 02:26:14 +0200 Subject: [PATCH] Fixes #9, #10, #11. Feature: Show images of in html files. Issue #9: TeXfrag leaves LaTeX buffers and output buffers open. Issue #10: Query whether LaTeX should be killed at opening text with `texfrag-global-mode` and `texfrag-preview-buffer-at-start` turned on. Issue #11: LaTeX does not accept the UTF8 Byte Order Mark. --- README.md | 13 ++++ test/README_TEST.org | 77 ++++++++++--------- texfrag.el | 177 +++++++++++++++++++++++++++++++++---------- 3 files changed, 192 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index ef06f6e..02c21a9 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,16 @@ At the time of writing the list of supported major modes is: All major modes derived from the listed modes are automatically also supported. Please, inspect the help of the variable `texfrag-setup-alist` for a complete list of supported modes. + +## Known issues + +- texfrag-global-mode does not switch on texfrag-mode for eww + #13 opened 2 minutes ago by TobiasZawada +- sx: equation end with $$ is misinterpreted as new equation beginning + #12 opened 7 hours ago by TobiasZawada +- LaTeX does not accept the UTF8 Byte Order Mark. + #11 opened 8 hours ago by TobiasZawada +- Query whether LaTeX should be killed at opening text with `texfrag-global-mode` and `texfrag-preview-buffer-at-start` turned on. + #10 opened a day ago by TobiasZawada +- TeXfrag leaves LaTeX buffers and output buffers open. + #9 opened 14 days ago by TobiasZawada diff --git a/test/README_TEST.org b/test/README_TEST.org index 7faf0f2..46c5b75 100644 --- a/test/README_TEST.org +++ b/test/README_TEST.org @@ -93,40 +93,49 @@ b(x) = (x_{i+d}-x_i)\cdot [x_i,\ldots,x_{i+d}]_y (y-x)^d_+ ** eww - #+BEGIN_SRC html :tangle /tmp/test.html :results silent - - - - texfrag html test - - -

texfrag html test

-

- MathJax test. -

-

- +#+BEGIN_SRC svg :tangle /tmp/test.svg :results silent + + + + + +#+END_SRC + +#+BEGIN_SRC html :tangle /tmp/test.html :results silent + + + + texfrag html test + + +

texfrag html test

+

Testing replacement of images in html-mode with the following example from How minimal can an SVG be?: .

+

+ MathJax test. +

+

+ #+END_SRC Second html document for testing with eww: diff --git a/texfrag.el b/texfrag.el index f42b217..b88ef75 100644 --- a/texfrag.el +++ b/texfrag.el @@ -175,6 +175,18 @@ This can be a file name or a sexp that generates the file name." :group 'texfrag :type '(choice file sexp)) +(defconst texfrag-frag-alist-type + '(repeat + (list :tag "Equation filter" + (regexp :tag "Regexp matching the fragment beginning") + (texfrag-regexp :tag "Regexp matching the fragment end") + (string :tag "Replacement of beginning in LaTeX buffer") + (string :tag "Replacement of end in LaTeX buffer") + (set :tag "Fragment type" :inline t + (list :tag "Show as display equation" :inline t (const :display) (const t)) + (list :tag "Generator function" :inline t (const :generator) (function identity))))) + "Customization type for `texfrag-frag-alist'.") + (define-widget 'texfrag-regexp 'string "A regular expression." :match 'texfrag-widget-regexp-match @@ -211,21 +223,15 @@ If VAL is a widget instead of a string (widget-value val) is tested." widget))) (defcustom texfrag-LaTeX-frag-alist - '(("\\$\\$" "\\$\\$" "$$" "$$" display) - ("\\$" "\\$" "$" "$" embedded) - ("\\\\\\[" "\\\\\\]" "\\\\[" "\\\\]" display) - ("\\\\(" "\\\\)" "\\\\(" "\\\\)" embedded) - ("\\\\begin{\\([a-z*]+\\)}" "\\\\end{\\1}" "\\\\begin{\\2}" "\\\\end{\\2}" display) + '(("\\$\\$" "\\$\\$" "$$" "$$" :display t) + ("\\$" "\\$" "$" "$") + ("\\\\\\[" "\\\\\\]" "\\\\[" "\\\\]" :display t) + ("\\\\(" "\\\\)" "\\\\(" "\\\\)") + ("\\\\begin{\\([a-z*]+\\)}" "\\\\end{\\1}" "\\\\begin{\\2}" "\\\\end{\\2}" :display t) ) "`texfrag-frag-alist' for LaTeX." :group 'texfrag - :type '(repeat - (list :tag "Equation filter" - (regexp :tag "Regexp matching the equation beginning") - (texfrag-regexp :tag "Regexp matching the equation end") - (string :tag "Replacement of beginning in LaTeX buffer") - (string :tag "Replacement of end in LaTeX buffer") - (choice :tag "Equation type" (const display) (const embedded))))) + :type texfrag-frag-alist-type) (defcustom texfrag-org-class-options "" "LaTeX class options for texfrag in orgmode. @@ -246,17 +252,17 @@ Defaults to the empty string. Should include the enclosing brackets." (require 'tex) (require 'subr-x) -(defvar texfrag-header-function #'texfrag-header-default +(defvar-local texfrag-header-function #'texfrag-header-default "Function that collects all LaTeX header contents from the current buffer.") -(defvar texfrag-tail-function "\n\\end{document}\n%%Local Variables:\n%%TeX-master: t\n%%End:\n" +(defvar-local texfrag-tail-function "\n\\end{document}\n%%Local Variables:\n%%TeX-master: t\n%%End:\n" "String with the LaTeX tail or Function that collects all preview-LaTeX tail contents from the current buffer.") -(defvar texfrag-next-frag-function #'texfrag-next-frag-default +(defvar-local texfrag-next-frag-function #'texfrag-next-frag-default "Function that searches for the next LaTeX fragment starting at point. It is called with: -\(funcall texfrag-next-frag-functionx BOUND) +\(funcall texfrag-next-frag-function BOUND) It returns nil if there is no LaTeX fragment within the region. Otherwise it returns a list @@ -269,7 +275,7 @@ eqn: equation text inclusive delimiters, e.g., (it is not necessarily equal to (buffer-substring b e)) match: entry from `texfrag-frag-alist' associated with the match") -(defvar texfrag-previous-frag-function #'texfrag-previous-frag-default +(defvar-local texfrag-previous-frag-function #'texfrag-previous-frag-default "Function that searches for the previous LaTeX fragment starting at point. It is called with: @@ -300,7 +306,7 @@ Override the default in the hook for the major mode. The default works for some LaTeX fragments in doxygen. The value is a list of equation-filters. -Each equation-filter is a list of two matchers, two replacement strings, and an optional flag. +Each equation-filter is a list of two matchers, two replacement strings, and further property-value pairs. The first matcher can be a regular expression string matching the beginning of a formula or a list with the regular expression string as its first element and a function as its second element. The function returns non-nil if the match really starts a LaTeX fragment. @@ -311,11 +317,19 @@ The last two strings are the beginning and the end of the corresponding equation They have the format of the NEWTEXT argument of `replace-match'. You need to escape the backslash character ?\\\\ and you can refer to groups as explained further below. -If the optional flag equals the symbol display then this equation should be displayed on a separate line. It should be embedded otherwise. - Capturing groups can be used in the first two regular expressions. These groups can be referred to in the last two replacement strings. The indexes for the captures are determined as match for the combined regular expression -\\(beginning regexp\\)\\(end regexp\\).") +\\(beginning regexp\\)\\(end regexp\\). + +Following properties are recognized: + +:display (default: nil) if the :display property is non-nil fragments are wrapped +into newlines by `texfrag-fix-display-math'. + +:generator (default: nil) +May be nil or a generator function to be called with the transformed match as argument. +The generator should return the image or a file name to the image to be displayed. +") (defvar-local texfrag-equation-filter #'identity "Filter function transforming the equation text from the original buffer into the equation text in the LaTeX buffer.") @@ -326,7 +340,15 @@ Thereby use the previous `match-data' DATA with corresponding string STR if this was a `string-match'. Let N be the number of sub-expressions of the previous match then \\n with n=0,...,N are replaced by the quoted matches from the previous match -and \\n with n=N+1,\ldots are replaced by n-(N+1)." +and \\n with n=N+1,... are replaced by n-(N+1). + +Example: +With str equal to \"foo bar\" and preceeding match +\(string-match \"f\\\\(o+\\\\)\" str) +the form +\(texfrag-combine-regexps \"\\\\(ba\\\\)zz.*\\\\1G\\\\2\" (match-data) str) +returns: +\"\\\\(ba\\\\)zz.*ooG\\\\1\"" (let ((data-n (/ (length data) 2)) new-pos (pos 0) @@ -353,6 +375,9 @@ and \\n with n=N+1,\ldots are replaced by n-(N+1)." (t (setq re (concat re "\\"))))) (setq re (concat re (substring re-template pos))) re)) +;; Test: +;; (string-equal "\\(ba\\)zz.*ooG\\1" (let ((str "foo bar")) (string-match "f\\(o+\\)" str) (texfrag-combine-regexps "\\(ba\\)zz.*\\1G\\2" (match-data) str))) +;; (defun texfrag-combine-match-data (&rest args) "Combines the match data in ARGS of multiple `string-match' commands. @@ -375,6 +400,8 @@ returns the result of offset (+ offset str-length) args (cddr args)))) (cons 0 (cons offset ret)))) +;; Test: +;; (equal '(0 15 4 7 5 6 11 14 12 14) (let* ((str1 "foo bar") (data1 (progn (string-match "b\\(a\\)r" str1) (match-data))) (str2 "baz booz") (data2 (progn (string-match "b\\(o+\\)" str2) (match-data)))) (texfrag-combine-match-data str1 data1 str2 data2))) (defun texfrag-regexp-begin (frag) "Extract the beginning regexp from FRAG. @@ -385,7 +412,7 @@ FRAG is a LaTeX fragment entry in `texfrag-frag-alist'." (defun texfrag-next-frag-default (bound) "Search for the next LaTeX fragment in region from `point' to BOUND. -See the documentation of `texfrag-next-frag-function' +See the documentation of variable `texfrag-next-frag-function' for further details about the argument and the return value." (let ((re-b (concat (mapconcat #'texfrag-regexp-begin texfrag-frag-alist "\\|"))) @@ -735,7 +762,21 @@ or the string itself." (user-error "Unrecognized format of %s: %s" (quote ,fun) ,fun) ))))) -(defun texfrag-region (b e) +(defun texfrag-generator-place-preview (ov img _box &optional _counters _tempdir &rest _place-opts) + "Call me like `preview-gs-place'. +Return OV after setting 'preview-image to a cons (img . img) +assuming that img is already an image." + (when (stringp img) + (setq img (expand-file-name img)) + (if (and (file-readable-p img) + (null (file-directory-p img))) + (progn + (setq img (create-image img)) + (overlay-put ov 'preview-image (cons img img))) + (warn "Cannot find image file %S" img))) + ov) + +(defun texfrag-region (&optional b e) "Collect LaTeX fragments in region from B to E in the LaTeX target file. Thereby, the LaTeX target file is that one returned by function `texfrag-LaTeX-file'. @@ -743,16 +784,22 @@ Afterwards start `preview-document' on the target file. The function `texfrag-after-tex' is hooked into `texfrag-after-preview-hook' which runs after `preview-document'. `texfrag-after-tex' transfers the preview images -from the LaTeX target file buffer to the source buffer." +from the LaTeX target file buffer to the source buffer. +B defaults to `point-min' and E defaults to `point-max'." (interactive "r") (cl-declare (special auto-insert-alist auto-insert)) + (unless b (setq b (point-min))) + (unless e (setq e (point-max))) (let ((tex-path (texfrag-LaTeX-file t t)) - (src-buf (current-buffer)) - (coding-sys (or coding-system-for-write buffer-file-coding-system)) - tex-buf - found - found-str - (texfrag-preview-scale preview-scale)) ; only non-nil if there are LaTeX-fragments in the document + (src-buf (current-buffer)) + (coding-sys (intern-soft ;; LaTeX does not accept a byte order mark: + (replace-regexp-in-string + "-with-signature" "" + (symbol-name (or coding-system-for-write buffer-file-coding-system))))) + tex-buf + found + found-str + (texfrag-preview-scale preview-scale)) ; only non-nil if there are LaTeX-fragments in the document (let (auto-insert-alist auto-insert) (setq tex-buf (find-file-noselect tex-path) texfrag-tex-buffer tex-buf)) @@ -762,15 +809,25 @@ from the LaTeX target file buffer to the source buffer." (while (setq found (texfrag-search-forward-fragment e)) (let* ((found-b (nth 0 found)) (found-e (nth 1 found)) - (ol (make-overlay found-b found-e))) - (unless found-str ; only modify the buffer there if at least one LaTeX fragment + ol + (generator (plist-get (texfrag-match-plist found) :generator))) + (if generator + (progn + (unless (functionp generator) + (user-error "Generator %S used at %s in buffer %s is not a function" generator found-b (current-buffer))) + (let ((preview-image-type 'texfrag) + (preview-image-creators '((texfrag (place texfrag-generator-place-preview))))) + (setq ol (preview-place-preview (funcall generator (texfrag-match-transformed-eqn found)) found-b found-e nil nil (cl-copy-list TeX-active-tempdir) nil)) + )) + (setq ol (make-overlay found-b found-e)) + (unless found-str ; only modify the buffer there if at least one LaTeX fragment + (with-current-buffer tex-buf + (set-buffer-file-coding-system coding-sys) + (delete-region (point-min) (point-max)))) + (setq found-str (buffer-substring-no-properties found-b found-e)) + (overlay-put ol 'texfrag-string found-str) (with-current-buffer tex-buf - (setq buffer-file-coding-system coding-sys) - (delete-region (point-min) (point-max)))) - (setq found-str (buffer-substring-no-properties found-b found-e)) - (overlay-put ol 'texfrag-string found-str) - (with-current-buffer tex-buf - (insert "\n" (propertize (nth 2 found) 'texfrag-src ol)))))) + (insert "\n" (propertize (nth 2 found) 'texfrag-src ol))))))) ;;; end of tex-buf: (if found-str ;; avoid error messages if no LaTeX fragments were found (progn @@ -1003,6 +1060,22 @@ B and E are the boundaries of the region to be processed." (interactive) (TeX-recenter-output-buffer nil)) +(defmacro texfrag-match-transformed-eqn (match) + "Get the transformed equation entry from return value MATCH of `texfrag-next-frag-function'." + `(nth 2 ,match)) + +(defmacro texfrag-match-frag (match) + "Get FRAG entry from return value MATCH of `texfrag-next-frag-function'." + `(nth 3 ,match)) + +(defmacro texfrag-frag-plist (frag) + "Get the property list in entry FRAG of `texfrag-frag-alist'." + `(nthcdr 4 ,frag)) + +(defmacro texfrag-match-plist (match) + "Get the property list in the FRAG entry of MATCH." + `(texfrag-frag-plist (texfrag-match-frag ,match))) + (defun texfrag-fix-display-math (&optional b e) "Insert line breaks around displayed math environments in region from B to E." (save-excursion @@ -1014,7 +1087,11 @@ B and E are the boundaries of the region to be processed." (goto-char (or b (point-min))) (let (math) (while (setq math (funcall texfrag-next-frag-function end-marker)) - (when (eq 'display (nth 4 (nth 3 math))) + (when (let ((props (texfrag-match-plist math))) + (or + (plist-get props :display) + (eq 'display (car props)) ;;< compatibility + )) (set-marker eq-end-marker (nth 1 math)) (goto-char eq-end-marker) (unless (looking-at "[[:space:]]*$") @@ -1024,6 +1101,7 @@ B and E are the boundaries of the region to be processed." (insert "\n")) (goto-char eq-end-marker) )))) + (set-marker eq-end-marker nil) (set-marker end-marker nil))))) (defun texfrag-MathJax-filter (str) @@ -1155,6 +1233,13 @@ nil: dont preserve any minor modes" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; html-mode +(defcustom texfrag-html-frag-alist + (append texfrag-LaTeX-frag-alist + '(("]*>" "" "" :generator identity))) + "Regular expression for TeX fragments in HTML pages." + :group 'texfrag + :type texfrag-frag-alist-type) + (defun texfrag-html-region-function (b e) "Special `texfrag-region' for region from B to E in html mode. It differs from `texfrag-region' by skipping the text from buffer-beginning up to ." @@ -1171,7 +1256,7 @@ It differs from `texfrag-region' by skipping the text from buffer-beginning up t (interactive) (setq texfrag-comments-only nil texfrag-equation-filter #'texfrag-MathJax-filter - texfrag-frag-alist texfrag-LaTeX-frag-alist + texfrag-frag-alist texfrag-html-frag-alist texfrag-preview-region-function #'texfrag-html-region-function)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1237,5 +1322,15 @@ or as `sx-question-mode-after-print-hook'." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun texfrag-scale (scale) + "Set SCALE factor for `preview-scale'." + (interactive "nScale Factor:") + (setq-local preview-scale scale) + (texfrag-region (point-min) (point-max))) + +(easy-menu-add-item texfrag-mode-map '(menu-bar TeX) ["Set Preview Scale" texfrag-scale t]) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (provide 'texfrag) ;;; texfrag.el ends here