New color link in org 9.0 using font-lock to color the text

| categories: orgmode, emacs | tags:

Table of Contents

I previously blogged about colored text in org-mode using links. Back in org 8.0, we had to do some tricky work with font-lock to get the colors to work, and particularly had to use overlays to get reliable coloring. Org 9.0 improves this a lot, with the font-lock built right in, and some other nice features like completion. The integration of font-lock means we do not have to deal with overlays! Here is what it looks like in my buffer:

And it exports nicely to HTML. You can see the code here: Code for the color link. This is good for small bits of text you want colored. It is not good for paragraphs, or text with line breaks in it, because links do not support that. It also is not useful in code blocks since the link syntax would break the code. You need an alternative approach for those applications which is more complicated than these simple links. One nice feature of this is the persistence (I mean it comes back when you open the file after closing it) of the color since it is explicit markup in the file.

(loop for color in color-name-rgb-alist
      do
      (princ (format "[[color:%s][Text colored in %s]] " (car color) (car color))))

Text colored in snow Text colored in ghostwhite Text colored in whitesmoke Text colored in gainsboro Text colored in floralwhite Text colored in oldlace Text colored in linen Text colored in antiquewhite Text colored in papayawhip Text colored in blanchedalmond Text colored in bisque Text colored in peachpuff Text colored in navajowhite Text colored in moccasin Text colored in cornsilk Text colored in ivory Text colored in lemonchiffon Text colored in seashell Text colored in honeydew Text colored in mintcream Text colored in azure Text colored in aliceblue Text colored in lavender Text colored in lavenderblush Text colored in mistyrose Text colored in white Text colored in black Text colored in darkslategray Text colored in darkslategrey Text colored in dimgray Text colored in dimgrey Text colored in slategray Text colored in slategrey Text colored in lightslategray Text colored in lightslategrey Text colored in gray Text colored in grey Text colored in lightgrey Text colored in lightgray Text colored in midnightblue Text colored in navy Text colored in navyblue Text colored in cornflowerblue Text colored in darkslateblue Text colored in slateblue Text colored in mediumslateblue Text colored in lightslateblue Text colored in mediumblue Text colored in royalblue Text colored in blue Text colored in dodgerblue Text colored in deepskyblue Text colored in skyblue Text colored in lightskyblue Text colored in steelblue Text colored in lightsteelblue Text colored in lightblue Text colored in powderblue Text colored in paleturquoise Text colored in darkturquoise Text colored in mediumturquoise Text colored in turquoise Text colored in cyan Text colored in lightcyan Text colored in cadetblue Text colored in mediumaquamarine Text colored in aquamarine Text colored in darkgreen Text colored in darkolivegreen Text colored in darkseagreen Text colored in seagreen Text colored in mediumseagreen Text colored in lightseagreen Text colored in palegreen Text colored in springgreen Text colored in lawngreen Text colored in green Text colored in chartreuse Text colored in mediumspringgreen Text colored in greenyellow Text colored in limegreen Text colored in yellowgreen Text colored in forestgreen Text colored in olivedrab Text colored in darkkhaki Text colored in khaki Text colored in palegoldenrod Text colored in lightgoldenrodyellow Text colored in lightyellow Text colored in yellow Text colored in gold Text colored in lightgoldenrod Text colored in goldenrod Text colored in darkgoldenrod Text colored in rosybrown Text colored in indianred Text colored in saddlebrown Text colored in sienna Text colored in peru Text colored in burlywood Text colored in beige Text colored in wheat Text colored in sandybrown Text colored in tan Text colored in chocolate Text colored in firebrick Text colored in brown Text colored in darksalmon Text colored in salmon Text colored in lightsalmon Text colored in orange Text colored in darkorange Text colored in coral Text colored in lightcoral Text colored in tomato Text colored in orangered Text colored in red Text colored in hotpink Text colored in deeppink Text colored in pink Text colored in lightpink Text colored in palevioletred Text colored in maroon Text colored in mediumvioletred Text colored in violetred Text colored in magenta Text colored in violet Text colored in plum Text colored in orchid Text colored in mediumorchid Text colored in darkorchid Text colored in darkviolet Text colored in blueviolet Text colored in purple Text colored in mediumpurple Text colored in thistle Text colored in snow1 Text colored in snow2 Text colored in snow3 Text colored in snow4 Text colored in seashell1 Text colored in seashell2 Text colored in seashell3 Text colored in seashell4 Text colored in antiquewhite1 Text colored in antiquewhite2 Text colored in antiquewhite3 Text colored in antiquewhite4 Text colored in bisque1 Text colored in bisque2 Text colored in bisque3 Text colored in bisque4 Text colored in peachpuff1 Text colored in peachpuff2 Text colored in peachpuff3 Text colored in peachpuff4 Text colored in navajowhite1 Text colored in navajowhite2 Text colored in navajowhite3 Text colored in navajowhite4 Text colored in lemonchiffon1 Text colored in lemonchiffon2 Text colored in lemonchiffon3 Text colored in lemonchiffon4 Text colored in cornsilk1 Text colored in cornsilk2 Text colored in cornsilk3 Text colored in cornsilk4 Text colored in ivory1 Text colored in ivory2 Text colored in ivory3 Text colored in ivory4 Text colored in honeydew1 Text colored in honeydew2 Text colored in honeydew3 Text colored in honeydew4 Text colored in lavenderblush1 Text colored in lavenderblush2 Text colored in lavenderblush3 Text colored in lavenderblush4 Text colored in mistyrose1 Text colored in mistyrose2 Text colored in mistyrose3 Text colored in mistyrose4 Text colored in azure1 Text colored in azure2 Text colored in azure3 Text colored in azure4 Text colored in slateblue1 Text colored in slateblue2 Text colored in slateblue3 Text colored in slateblue4 Text colored in royalblue1 Text colored in royalblue2 Text colored in royalblue3 Text colored in royalblue4 Text colored in blue1 Text colored in blue2 Text colored in blue3 Text colored in blue4 Text colored in dodgerblue1 Text colored in dodgerblue2 Text colored in dodgerblue3 Text colored in dodgerblue4 Text colored in steelblue1 Text colored in steelblue2 Text colored in steelblue3 Text colored in steelblue4 Text colored in deepskyblue1 Text colored in deepskyblue2 Text colored in deepskyblue3 Text colored in deepskyblue4 Text colored in skyblue1 Text colored in skyblue2 Text colored in skyblue3 Text colored in skyblue4 Text colored in lightskyblue1 Text colored in lightskyblue2 Text colored in lightskyblue3 Text colored in lightskyblue4 Text colored in slategray1 Text colored in slategray2 Text colored in slategray3 Text colored in slategray4 Text colored in lightsteelblue1 Text colored in lightsteelblue2 Text colored in lightsteelblue3 Text colored in lightsteelblue4 Text colored in lightblue1 Text colored in lightblue2 Text colored in lightblue3 Text colored in lightblue4 Text colored in lightcyan1 Text colored in lightcyan2 Text colored in lightcyan3 Text colored in lightcyan4 Text colored in paleturquoise1 Text colored in paleturquoise2 Text colored in paleturquoise3 Text colored in paleturquoise4 Text colored in cadetblue1 Text colored in cadetblue2 Text colored in cadetblue3 Text colored in cadetblue4 Text colored in turquoise1 Text colored in turquoise2 Text colored in turquoise3 Text colored in turquoise4 Text colored in cyan1 Text colored in cyan2 Text colored in cyan3 Text colored in cyan4 Text colored in darkslategray1 Text colored in darkslategray2 Text colored in darkslategray3 Text colored in darkslategray4 Text colored in aquamarine1 Text colored in aquamarine2 Text colored in aquamarine3 Text colored in aquamarine4 Text colored in darkseagreen1 Text colored in darkseagreen2 Text colored in darkseagreen3 Text colored in darkseagreen4 Text colored in seagreen1 Text colored in seagreen2 Text colored in seagreen3 Text colored in seagreen4 Text colored in palegreen1 Text colored in palegreen2 Text colored in palegreen3 Text colored in palegreen4 Text colored in springgreen1 Text colored in springgreen2 Text colored in springgreen3 Text colored in springgreen4 Text colored in green1 Text colored in green2 Text colored in green3 Text colored in green4 Text colored in chartreuse1 Text colored in chartreuse2 Text colored in chartreuse3 Text colored in chartreuse4 Text colored in olivedrab1 Text colored in olivedrab2 Text colored in olivedrab3 Text colored in olivedrab4 Text colored in darkolivegreen1 Text colored in darkolivegreen2 Text colored in darkolivegreen3 Text colored in darkolivegreen4 Text colored in khaki1 Text colored in khaki2 Text colored in khaki3 Text colored in khaki4 Text colored in lightgoldenrod1 Text colored in lightgoldenrod2 Text colored in lightgoldenrod3 Text colored in lightgoldenrod4 Text colored in lightyellow1 Text colored in lightyellow2 Text colored in lightyellow3 Text colored in lightyellow4 Text colored in yellow1 Text colored in yellow2 Text colored in yellow3 Text colored in yellow4 Text colored in gold1 Text colored in gold2 Text colored in gold3 Text colored in gold4 Text colored in goldenrod1 Text colored in goldenrod2 Text colored in goldenrod3 Text colored in goldenrod4 Text colored in darkgoldenrod1 Text colored in darkgoldenrod2 Text colored in darkgoldenrod3 Text colored in darkgoldenrod4 Text colored in rosybrown1 Text colored in rosybrown2 Text colored in rosybrown3 Text colored in rosybrown4 Text colored in indianred1 Text colored in indianred2 Text colored in indianred3 Text colored in indianred4 Text colored in sienna1 Text colored in sienna2 Text colored in sienna3 Text colored in sienna4 Text colored in burlywood1 Text colored in burlywood2 Text colored in burlywood3 Text colored in burlywood4 Text colored in wheat1 Text colored in wheat2 Text colored in wheat3 Text colored in wheat4 Text colored in tan1 Text colored in tan2 Text colored in tan3 Text colored in tan4 Text colored in chocolate1 Text colored in chocolate2 Text colored in chocolate3 Text colored in chocolate4 Text colored in firebrick1 Text colored in firebrick2 Text colored in firebrick3 Text colored in firebrick4 Text colored in brown1 Text colored in brown2 Text colored in brown3 Text colored in brown4 Text colored in salmon1 Text colored in salmon2 Text colored in salmon3 Text colored in salmon4 Text colored in lightsalmon1 Text colored in lightsalmon2 Text colored in lightsalmon3 Text colored in lightsalmon4 Text colored in orange1 Text colored in orange2 Text colored in orange3 Text colored in orange4 Text colored in darkorange1 Text colored in darkorange2 Text colored in darkorange3 Text colored in darkorange4 Text colored in coral1 Text colored in coral2 Text colored in coral3 Text colored in coral4 Text colored in tomato1 Text colored in tomato2 Text colored in tomato3 Text colored in tomato4 Text colored in orangered1 Text colored in orangered2 Text colored in orangered3 Text colored in orangered4 Text colored in red1 Text colored in red2 Text colored in red3 Text colored in red4 Text colored in deeppink1 Text colored in deeppink2 Text colored in deeppink3 Text colored in deeppink4 Text colored in hotpink1 Text colored in hotpink2 Text colored in hotpink3 Text colored in hotpink4 Text colored in pink1 Text colored in pink2 Text colored in pink3 Text colored in pink4 Text colored in lightpink1 Text colored in lightpink2 Text colored in lightpink3 Text colored in lightpink4 Text colored in palevioletred1 Text colored in palevioletred2 Text colored in palevioletred3 Text colored in palevioletred4 Text colored in maroon1 Text colored in maroon2 Text colored in maroon3 Text colored in maroon4 Text colored in violetred1 Text colored in violetred2 Text colored in violetred3 Text colored in violetred4 Text colored in magenta1 Text colored in magenta2 Text colored in magenta3 Text colored in magenta4 Text colored in orchid1 Text colored in orchid2 Text colored in orchid3 Text colored in orchid4 Text colored in plum1 Text colored in plum2 Text colored in plum3 Text colored in plum4 Text colored in mediumorchid1 Text colored in mediumorchid2 Text colored in mediumorchid3 Text colored in mediumorchid4 Text colored in darkorchid1 Text colored in darkorchid2 Text colored in darkorchid3 Text colored in darkorchid4 Text colored in purple1 Text colored in purple2 Text colored in purple3 Text colored in purple4 Text colored in mediumpurple1 Text colored in mediumpurple2 Text colored in mediumpurple3 Text colored in mediumpurple4 Text colored in thistle1 Text colored in thistle2 Text colored in thistle3 Text colored in thistle4 Text colored in gray0 Text colored in grey0 Text colored in gray1 Text colored in grey1 Text colored in gray2 Text colored in grey2 Text colored in gray3 Text colored in grey3 Text colored in gray4 Text colored in grey4 Text colored in gray5 Text colored in grey5 Text colored in gray6 Text colored in grey6 Text colored in gray7 Text colored in grey7 Text colored in gray8 Text colored in grey8 Text colored in gray9 Text colored in grey9 Text colored in gray10 Text colored in grey10 Text colored in gray11 Text colored in grey11 Text colored in gray12 Text colored in grey12 Text colored in gray13 Text colored in grey13 Text colored in gray14 Text colored in grey14 Text colored in gray15 Text colored in grey15 Text colored in gray16 Text colored in grey16 Text colored in gray17 Text colored in grey17 Text colored in gray18 Text colored in grey18 Text colored in gray19 Text colored in grey19 Text colored in gray20 Text colored in grey20 Text colored in gray21 Text colored in grey21 Text colored in gray22 Text colored in grey22 Text colored in gray23 Text colored in grey23 Text colored in gray24 Text colored in grey24 Text colored in gray25 Text colored in grey25 Text colored in gray26 Text colored in grey26 Text colored in gray27 Text colored in grey27 Text colored in gray28 Text colored in grey28 Text colored in gray29 Text colored in grey29 Text colored in gray30 Text colored in grey30 Text colored in gray31 Text colored in grey31 Text colored in gray32 Text colored in grey32 Text colored in gray33 Text colored in grey33 Text colored in gray34 Text colored in grey34 Text colored in gray35 Text colored in grey35 Text colored in gray36 Text colored in grey36 Text colored in gray37 Text colored in grey37 Text colored in gray38 Text colored in grey38 Text colored in gray39 Text colored in grey39 Text colored in gray40 Text colored in grey40 Text colored in gray41 Text colored in grey41 Text colored in gray42 Text colored in grey42 Text colored in gray43 Text colored in grey43 Text colored in gray44 Text colored in grey44 Text colored in gray45 Text colored in grey45 Text colored in gray46 Text colored in grey46 Text colored in gray47 Text colored in grey47 Text colored in gray48 Text colored in grey48 Text colored in gray49 Text colored in grey49 Text colored in gray50 Text colored in grey50 Text colored in gray51 Text colored in grey51 Text colored in gray52 Text colored in grey52 Text colored in gray53 Text colored in grey53 Text colored in gray54 Text colored in grey54 Text colored in gray55 Text colored in grey55 Text colored in gray56 Text colored in grey56 Text colored in gray57 Text colored in grey57 Text colored in gray58 Text colored in grey58 Text colored in gray59 Text colored in grey59 Text colored in gray60 Text colored in grey60 Text colored in gray61 Text colored in grey61 Text colored in gray62 Text colored in grey62 Text colored in gray63 Text colored in grey63 Text colored in gray64 Text colored in grey64 Text colored in gray65 Text colored in grey65 Text colored in gray66 Text colored in grey66 Text colored in gray67 Text colored in grey67 Text colored in gray68 Text colored in grey68 Text colored in gray69 Text colored in grey69 Text colored in gray70 Text colored in grey70 Text colored in gray71 Text colored in grey71 Text colored in gray72 Text colored in grey72 Text colored in gray73 Text colored in grey73 Text colored in gray74 Text colored in grey74 Text colored in gray75 Text colored in grey75 Text colored in gray76 Text colored in grey76 Text colored in gray77 Text colored in grey77 Text colored in gray78 Text colored in grey78 Text colored in gray79 Text colored in grey79 Text colored in gray80 Text colored in grey80 Text colored in gray81 Text colored in grey81 Text colored in gray82 Text colored in grey82 Text colored in gray83 Text colored in grey83 Text colored in gray84 Text colored in grey84 Text colored in gray85 Text colored in grey85 Text colored in gray86 Text colored in grey86 Text colored in gray87 Text colored in grey87 Text colored in gray88 Text colored in grey88 Text colored in gray89 Text colored in grey89 Text colored in gray90 Text colored in grey90 Text colored in gray91 Text colored in grey91 Text colored in gray92 Text colored in grey92 Text colored in gray93 Text colored in grey93 Text colored in gray94 Text colored in grey94 Text colored in gray95 Text colored in grey95 Text colored in gray96 Text colored in grey96 Text colored in gray97 Text colored in grey97 Text colored in gray98 Text colored in grey98 Text colored in gray99 Text colored in grey99 Text colored in gray100 Text colored in grey100 Text colored in darkgrey Text colored in darkgray Text colored in darkblue Text colored in darkcyan Text colored in darkmagenta Text colored in darkred Text colored in lightgreen

1 Code for the color link

(require 's)

(defun color-comp (&optional arg)
  "Completion function for color links."
  (let ((color-data (prog2
                        (save-selected-window
                          (list-colors-display))
                        (with-current-buffer (get-buffer "*Colors*")
                          (mapcar (lambda (line)
                                    (append (list line)
                                            (s-split " " line t)))
                                  (s-split "\n" (buffer-string))))
                      (kill-buffer "*Colors*"))))
    (format "color:%s"
            (s-trim (cadr (assoc (completing-read "Color: " color-data) color-data))))))


(defun color-link-face (path)
  "Face function for color links."
  (or (cdr (assoc path org-link-colors))
      `(:foreground ,path)))


(defun color-link-export (path description backend)
  "Export function for color links."
  (cond
   ((eq backend 'html)
    (let ((rgb (assoc (downcase path) color-name-rgb-alist))
          r g b)
      (setq r (* 255 (/ (nth 1 rgb) 65535.0))
            g (* 255 (/ (nth 2 rgb) 65535.0))
            b (* 255 (/ (nth 3 rgb) 65535.0)))
      (format "<span style=\"color: rgb(%s,%s,%s)\">%s</span>"
              (truncate r) (truncate g) (truncate b)
              (or description path))))))

(org-link-set-parameters "color"         
                         :face 'color-link-face
                         :complete 'color-comp
                         :export 'color-link-export)
:face color-link-face :complete color-comp :export color-link-export

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter

Better equation numbering in LaTeX fragments in org-mode

| categories: latex, orgmode, emacs | tags:

In org-mode we can use LaTeX equations, and toggle an overlay that shows what the rendered equation will look like. One thing that has always bothered me though, is that each fragment is created in isolation. That means numbering is almost always wrong, and typically with each numbered equation starting with (1). Here we look at a way to fix that. Fixing it means we have to find a way to not create each fragment image in isolation; each one needs a context that enables the numbering to be correct. The idea we try here is simple: we just figure out in advance what the numbering for each equation should be, and then figure out how to get that information to the image generation.

See this video of the post in action:

Here are some example equations to see how it works.

This should be numbered (1)

\begin{equation} \int x^2 dx \end{equation}

This is a numbered equation with a custom number. This should have (E1) as the number.

\begin{equation}\tag{E1} \int x^2 dx \end{equation}

But equation* is not numbered

\begin{equation*} \int x^2 dx \end{equation*}

LaTeX align environments are numbered. The first line is (2), the second line is not numbered (because we put \nonumber in the line), and the third line is (3).

\begin{align} a = 5 \\ b=6 \nonumber \\ c = 8 \end{align}

But align* environments are not numbered.

\begin{align*} a = 5 \\ b=6 \end{align*}

This should be numbered (4).

\begin{equation} \int x^3 dx \end{equation}

These should be numbered (5), (6) and (7).

\begin{align} a = 5 \\ b=6 \\ c = 8 \end{align}

This should be numbered with (E2).

\begin{equation}\tag{E2} \int x^2 dx \end{equation}

And this should be numbered (8).

\begin{equation} \int x^2 dx \end{equation}

Note: This will be numbered (1) because it is exactly the same equation as a previous one!

\begin{equation} \int x^2 dx \end{equation}

We can change the numbering of an equation with code like this. After this code, the next equation will be numbered (5).

The only fragments that should be numbered are equation environments, and align environments (these are the main ones that we consider here). The align environment is tricky since there is potentially more than one number in the environment.

So, we get all the fragments, and generate a list of which ones should be numbered, and if they should what the number should be. That means we will need to count the number of numbered equations in an align environment. We will do that by getting the number of line breaks, and subtracting the number of nonumbers.

Here is the code block that does that, using advice again. A downside of this approach is that we generate the list for every fragment, which is not efficient, since it should not change in a synchronous approach to generating them.

(defun org-renumber-environment (orig-func &rest args)
  (let ((results '()) 
        (counter -1)
        (numberp))

    (setq results (loop for (begin .  env) in 
                        (org-element-map (org-element-parse-buffer) 'latex-environment
                          (lambda (env)
                            (cons
                             (org-element-property :begin env)
                             (org-element-property :value env))))
                        collect
                        (cond
                         ((and (string-match "\\\\begin{equation}" env)
                               (not (string-match "\\\\tag{" env)))
                          (incf counter)
                          (cons begin counter))
                         ((string-match "\\\\begin{align}" env)
                          (prog2
                              (incf counter)
                              (cons begin counter)                          
                            (with-temp-buffer
                              (insert env)
                              (goto-char (point-min))
                              ;; \\ is used for a new line. Each one leads to a number
                              (incf counter (count-matches "\\\\$"))
                              ;; unless there are nonumbers.
                              (goto-char (point-min))
                              (decf counter (count-matches "\\nonumber")))))
                         (t
                          (cons begin nil)))))

    (when (setq numberp (cdr (assoc (point) results)))
      (setf (car args)
            (concat
             (format "\\setcounter{equation}{%s}\n" numberp)
             (car args)))))
  
  (apply orig-func args))

(advice-add 'org-create-formula-image :around #'org-renumber-environment)

You can remove the advice like this.

(advice-remove 'org-create-formula-image #'org-renumber-environment)

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter

Justifying LaTeX preview fragments in org-mode

| categories: latex, orgmode, emacs | tags:

A colleague asked if I knew how to center the preview images of LaTeX equations in an org-buffer. This might make org-mode notes look nicer when lecturing, for example. We thought it might be possible to just offset the overlay with a before-string made up of the right number of spaces. I worked out a full solution that lets you "justify" the preview images. You have to add a :justify option to org-format-latex-options, and the option is either 'center or 'right (anything else means left-justified as the default). This will only justify equations that start at the beginning of a line to avoid modifying fragments that are in text. You should see the video to see this in action:

Equation 1: \(e^{i\pi} + 1 = 0\)

An \(x^2 = -1\) equation in the text is not affected.

A display equation with some space after the equation: \[e^{i \cdot \pi} + 1 = 0\]

This is a numbered equation.

\begin{equation} \int x^2 dx \end{equation}

The idea is pretty simple, we get the width of the window, and the width of the image, and compute the offset that approximately centers or right justifies the overlay, and then add the before-string property to the overlay. While we are at it, I will add a tooltip to the image so you can see the LaTeX code that created it, and make it clickable so you can toggle it back to the code. I apply the functions as after advice to the function that creates the overlay, so we do not have to adapt the org code at all. Here is the code that does it.

;; specify the justification you want
(plist-put org-format-latex-options :justify 'center)

(defun org-justify-fragment-overlay (beg end image imagetype)
  "Adjust the justification of a LaTeX fragment.
The justification is set by :justify in
`org-format-latex-options'. Only equations at the beginning of a
line are justified."
  (cond
   ;; Centered justification
   ((and (eq 'center (plist-get org-format-latex-options :justify)) 
         (= beg (line-beginning-position)))
    (let* ((img (create-image image 'imagemagick t))
           (width (car (image-size img)))
           (offset (floor (- (/ (window-text-width) 2) (/ width 2)))))
      (overlay-put (ov-at) 'before-string (make-string offset ? ))))
   ;; Right justification
   ((and (eq 'right (plist-get org-format-latex-options :justify)) 
         (= beg (line-beginning-position)))
    (let* ((img (create-image image 'imagemagick t))
           (width (car (image-display-size (overlay-get (ov-at) 'display))))
           (offset (floor (- (window-text-width) width (- (line-end-position) end)))))
      (overlay-put (ov-at) 'before-string (make-string offset ? ))))))

(defun org-latex-fragment-tooltip (beg end image imagetype)
  "Add the fragment tooltip to the overlay and set click function to toggle it."
  (overlay-put (ov-at) 'help-echo
               (concat (buffer-substring beg end)
                       "mouse-1 to toggle."))
  (overlay-put (ov-at) 'local-map (let ((map (make-sparse-keymap)))
                                    (define-key map [mouse-1]
                                      `(lambda ()
                                         (interactive)
                                         (org-remove-latex-fragment-image-overlays ,beg ,end)))
                                    map)))

;; advise the function to a
(advice-add 'org--format-latex-make-overlay :after 'org-justify-fragment-overlay)
(advice-add 'org--format-latex-make-overlay :after 'org-latex-fragment-tooltip)

That is it. If you get tired of the advice, remove it like this:

(advice-remove 'org--format-latex-make-overlay 'org-justify-fragment-overlay)
(advice-remove 'org--format-latex-make-overlay 'org-latex-fragment-tooltip)

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter

New link features in org 9

| categories: orgmode | tags:

Org 9.0 is finally out, and it comes with a totally overhauled link capability! This post documents some of those capabilities. These new capabilities include support for:

  1. Custom link faces
  2. Custom tooltips on links
  3. More consistent interface for completion
  4. Special keymaps on links
  5. Customized folding of bracketed links

This post will make more sense with this video: https://www.youtube.com/watch?v=5haX95nk02E

1 A colored link with a static tooltip.

All links in elisp are now defined with org-link-set-parameters.

(org-link-set-parameters
 "red"
 :follow (lambda (path) (message "You clicked me."))
 :export (lambda (path desc backend)
           (cond
            ((eq 'html backend)
             (format "<font color=\"red\">%s</font>"
                     (or desc path)))))
 :face '(:foreground "red")
 :help-echo "Click me for a message.")

A link that is colored red. In bracketed form: A link with a description.

You can change a parameter like this.

(org-link-set-parameters
 "red"
 :face '(:foreground "red" :underline t))

A link that is colored red and underlined.

You can use any face for a link. Here we define one and use it.

(defface org-link-green
  '((t (:inherit org-link :foreground "green")))
  "A green link.")

(org-link-set-parameters
   "green"
   :follow (lambda (path) (message "You clicked me."))
   :export (lambda (path desc backend)
     (cond
      ((eq 'html backend)
       (format "<font color=\"green\">%s</font>"
               (or desc path)))))
   :face 'org-link-green
   :help-echo "Click me for a message.")

A link works.

2 A colored link with a static tooltip and no folding.

This link will be shown in full unfolded form even when other links are folded in descriptive format.

(org-link-set-parameters
 "red-full"
 :follow (lambda (path) (message "You clicked me."))
 :export (lambda (path desc backend)
           (cond
            ((eq 'html backend)
             (format "<font color=\"red\">%s</font>"
                     (or desc path)))))
 :face '(:foreground "red")
 :display 'full
 :help-echo "Click me for a message.")

A link that is colored red. In bracketed form: A link with a description. This regular bracketed doi is still folded.

3 A dynamic tooltip

You can make tooltips dynamic. The function must take these arguments (window object position), and construct the tooltip from that information. Here we show what the cursor is on and the point that it is on. \(x^2\)

(defun redd-tip (window object position)
  (save-excursion
    (goto-char position)
    (goto-char (org-element-property :begin (org-element-context)))
    (cond ((looking-at org-plain-link-re)
           (format "Looking at %s with mouse at %s" (match-string 0) position))
          ((looking-at org-bracket-link-regexp)
           (format "Looking at %s in a bracketed link with mouse at %s"
                   (match-string 0) position))
          (t
           "No match"))))

(org-link-set-parameters
 "redd"
 :face '(:underline t)
 :help-echo 'redd-tip)

A link with a dynamic tooltip: link or this one another-link bracketed redd

4 A better file link

Say you want to get a menu of options for file links. For example to find the file, open it in dired, copy the link, etc… We use helm here to make that happen.

(org-link-set-parameters
 "file"
 :follow (lambda (path)
           (funcall
            (helm :sources
                  `((name . "Action")
                    (candidates . ,(append
                                    (loop for f in '(find-file
                                                     org-open-file)
                                          collect (cons (symbol-name f) f))
                                    '(("dired" . (lambda (path)
                                                   (dired (file-name-directory path))
                                                   (re-search-forward (file-name-nondirectory path))))
                                      ("copy org link" . (lambda (path)
                                                           (kill-new (format "[[file:%s]]" path)))))))
                    (action . identity)))
            path)))

5 A link with a new keymap.

To get a special keymap, we have to create a new keymap. We can make a copy of org-mouse-map and add new keys to it that are specific to this link. With this link, you can use arrow-keys with a modifier key to jump between links. We define C-left and C-right to go to the previous and next links, and for fun a C-up and super-mouse-1 bindings that are in effect only on the links.

(defun prev-link ()
  (interactive)
  (re-search-backward "keym:" nil t))

(defun next-link ()
  (interactive)
  (re-search-forward "keym:" nil t))

(org-link-set-parameters
 "keym"
 :follow (lambda (path)
           (interactive)
           (message "You followed me."))
 :keymap (let ((map (copy-keymap org-mouse-map)))
           (define-key map (kbd "C-<left>") 'prev-link)
           (define-key map (kbd "C-<right>") 'next-link)
           (define-key map (kbd "C-<up>")
             (lambda ()
               (interactive)(message-box "special C-up")))
           (define-key map [s-mouse-1]
             (lambda ()
               (interactive)
               (message-box "s-Followed")))
           map))

one then two and finally three

6 A completion example with a dynamic face for validation

This example shows how to add a completion function, and use a dynamic face to show when a bad link has been made (in this case there are 4 allowed fruits, and anything else should be red.

(defun my-comp (&optional arg)
  (format "fruit:%s"
          (completing-read "Choose a fruit: " '("apple" "orange" "grapes" "kiwi"))))

(defun fruit-link-face (path)
  (if (member path '("apple" "orange" "grapes" "kiwi"))
      'org-link
    '(:foreground "red")))

(defun fruit-tooltip (_win _obj position)
  (save-match-data
    (save-excursion
      (goto-char position)
      (let ((path (org-element-property :path (org-element-context))))
        (if (member path '("apple" "orange" "grapes" "kiwi"))
            "A fruit"
          (format "%s: Illegal value. Must be one of apple, orange, grapes or kiwi."
                  path))))))

(org-link-set-parameters "fruit"
                         :help-echo 'fruit-tooltip
                         :face 'fruit-link-face
                         :complete 'my-comp)

apple an orange in brackets

a bad grapefruit. kiwi

kiwi

7 A store link example

A store link example Put your cursor on a headline, and type C-c l. Then move it and type C-c C-l to insert the link.

(defun store-my-headline ()
  (when (and (eq major-mode 'org-mode)
             (org-at-heading-p))
    (org-store-link-props
     :type "head"
     :link (format "head:*%s" (nth 4 (org-heading-components)))
     :description (nth 4 (org-heading-components)))))

(defun follow-head (path)
  (org-open-link-from-string (format "[[%s]]" path)))

(org-link-set-parameters
 "head" :follow 'follow-head :store 'store-my-headline)

8 An activate-func example

You may want to do some additional things when a link is activated. For example, maybe it makes sense for different parts of the link to have different actions, or colors. Here is an example where we make an rgb link of three numbers, and color each number, and make the link color dynamic.

We make a keymap so C-up increments a color, and C-down decrements a color.

(require 'color)

(defun rgb-face (path)
  (let* ((f (split-string path ","))
         (red (/ (string-to-number (nth 0 f)) 255.0))
         (green (/ (string-to-number (nth 1 f)) 255.0))
         (blue (/ (string-to-number (nth 2 f)) 255.0))
         (hex (color-rgb-to-hex red green blue)))
    (list :foreground hex)))


(defun rgb-func (start end path bracketp)
  (save-excursion
    (goto-char start)
    (save-match-data
      (cl-loop for num in (split-string path ",")
               for face in (list '(:foreground "red")
                                 '(:foreground "green")
                                 '(:foreground "blue"))
               do
               (progn
                 (re-search-forward num end t)
                 (add-text-properties
                  (match-beginning 0)
                  (match-end 0)
                  (list 'face face)))))))

(defun ninc ()
  (interactive)
  (skip-chars-backward "0-9")
  (or (looking-at "[0-9]+")
      (error "No number at point"))
  (replace-match (number-to-string (1+ (string-to-number (match-string 0))))))


(defun NINC ()
  (interactive)
  (let* ((link (org-element-context))
         (path (org-element-property :path link))
         (beg (org-element-property :begin link))
         (end (org-element-property :end link))
         (rgb (mapcar 'string-to-number (split-string path ","))))
    (setq rgb (mapcar (lambda (x) (+ x 10)) rgb))
    (setf (buffer-substring beg end)
          (format "rgb:%s" (mapconcat 'identity (mapcar 'number-to-string rgb) ",")))))

(defun NDEC ()
  (interactive)
  (let* ((link (org-element-context))
         (path (org-element-property :path link))
         (beg (org-element-property :begin link))
         (end (org-element-property :end link))
         (rgb (mapcar 'string-to-number (split-string path ","))))
    (setq rgb (mapcar (lambda (x) (- x 10)) rgb))
    (setf (buffer-substring beg end)
          (format "rgb:%s" (mapconcat 'identity (mapcar 'number-to-string rgb) ",")))))


(defun ndec ()
  (interactive)
  (skip-chars-backward "0-9")
  (or (looking-at "[0-9]+")
      (error "No number at point"))
  (replace-match (number-to-string (1- (string-to-number (match-string 0))))))

(org-link-set-parameters "rgb" :face 'rgb-face
                         :activate-func 'rgb-func
                         :keymap (let ((map (copy-keymap org-mouse-map)))
                                   (define-key map (kbd "C-<up>") 'ninc)
                                   (define-key map (kbd "C-<down>") 'ndec)
                                   (define-key map (kbd "s-<up>") 'NINC)
                                   (define-key map (kbd "s-<down>") 'NDEC)
                                   map))

83,29,238 This is a violet color. 112,17,19

This is an rgb link with three comma separated numbers. We color each number accordingly, and set the rgb link to the color represented by the RGB pair.

225,225,225 This is a light gray.

A subtle point in this example is the need to save-match-data. Some functions modify the match-data, and this will mess up the whole font-lock system. I learned that by trial and error.

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter

Sending html emails from org-mode with org-mime

| categories: email, orgmode | tags:

On the org-mode mailing list there was some discussion about sending html mail using orgmode. The support for this in mu4e is deprecated. There is the org-mime library though, and it supports a lot of what is needed for this. As I played around with it though, I came across some limitations:

  1. I found equations were not rendered as images in the html, and files (in links) were not attached out of the box. I fixed that here.
  2. I found it useful to modify the org-mime commands to leave point in the To: field when composing emails from org-buffers.
  3. For use with mu4e, I created a function to open a message in org-mu4e-compose-org-mode, and added a C-cC-c hook to allow me to send it easily (here).

This post documents some work I did figuring out how to send html mails. After some testing, some of these should probably be patched in org-mime.

First, you need to require this library.

(require 'org-mime)

You can send a whole org buffer in html like with this command: org-mime-org-buffer-htmlize. Not all of the internal links work for me (at least in gmail).

The default behavior leaves you at the end of the buffer, which is not too nice. We lightly modify the function here to leave in the To: field.

(defun org-mime-org-buffer-htmlize ()
  "Create an email buffer containing the current org-mode file
  exported to html and encoded in both html and in org formats as
  mime alternatives."
  (interactive)
  (org-mime-send-buffer 'html)
  (message-goto-to))

1 From an org-headline in an org-file

You can compose an email as an org-heading in any org-buffer, and send it as html. In an org-heading, you need to specify a MAIL_FMT property of html, e.g.:

   :PROPERTIES:
   :MAIL_FMT: html
   :END:

Note the following properties can also be set to modify the composed email.

           (subject (or (funcall mp "MAIL_SUBJECT") (nth 4 (org-heading-components))))
           (to (funcall mp "MAIL_TO"))
           (cc (funcall mp "MAIL_CC"))
           (bcc (funcall mp "MAIL_BCC"))

Then, send it with org-mime-subtree

Here I modify this function to leave me in the To: field.

(defun org-mime-subtree ()
  "Create an email buffer containing the current org-mode subtree
  exported to a org format or to the format specified by the
  MAIL_FMT property of the subtree."
  (interactive)
  (org-mime-send-subtree
   (or (org-entry-get nil "MAIL_FMT" org-mime-use-property-inheritance) 'org))
  (message-goto-to))

Here are some sample elements to see if they convert to html reasonably.

1.1 Markup

bold

underlined

italics

strikethrough

code

Subscripts: H2O Superscripts: H+ An entity: To ∞ and beyond

1.2 Equations

\(x^2\)

\[x^4\]

\(e^x\)

1.3 Tables

Table 1: A table for you.
x y
1 2

1.4 Lists

A nested list.

  • one
    • Subentry under one.
  • two

A definition list:

def1
first definition

A checklist:

  • [ ] A checkbox

Here is a numbered list:

  1. number 1
  2. number 2

1.5 Code block

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(0, 10)
x = np.cos(t) * np.exp(-t)
y = np.sin(t) * np.exp(-t)

plt.plot(x, y)
plt.savefig('spiral.png')

1.6 An image from somewhere other than this directory

2 In a mail message

You might prefer to do this directly in an email. Here is how you can do it in mu4e. I use this command to open a message in org-mode. The mode switches if you are in the header, or in the body. If you always do this, you could use a hook instead on message-mode. I do not want default html so I do not do it.

(defun mu4e-compose-org-mail ()
 (interactive)
 (mu4e-compose-new)
 (org-mu4e-compose-org-mode))

For sending, we will use org-mime to htmlize it, and add a C-c C-c hook function to send it. This hook is a little tricky, we want to preserve C-c C-c behavior in org-mode, e.g. in code blocks, but send it if there is no other C-c C-c action that makes sense, so we add it to the end of the hook. Alternatively, you could bind a special key for it, or run the special command. Note the C-c C-c hook only works in the body of the email. From the header, a plain text message is sent.

(defun htmlize-and-send ()
  "When in an org-mu4e-compose-org-mode message, htmlize and send it."
  (interactive)
  (when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook)
    (org-mime-htmlize) 
    (message-send-and-exit)))

(add-hook 'org-ctrl-c-ctrl-c-hook 'htmlize-and-send t)

Here is a way to do this for non-mu4e users. It doesn't have the nice mode switching capability though, so you lose completion in emails, and header specific functions. You can switch back to message-mode to regain those.

(defun compose-html-org ()
  (interactive)
  (compose-mail)
  (message-goto-body)
  (setq *compose-html-org* t)
  (org-mode))

(defun org-htmlize-and-send ()
  "When in an org-mu4e-compose-org-mode message, htmlize and send it."
  (interactive)
  
  (when *compose-html-org*
    (setq *compose-html-org* nil)
    (message-mode)
    (org-mime-htmlize) 
    (message-send-and-exit)))

(add-hook 'org-ctrl-c-ctrl-c-hook 'org-htmlize-and-send t)

3 Equations and file attachments do not seem to work out of the box

\(e^{i\pi} - 1 = 0\)

Out of the box, org-mime does not seem to attach file links to emails or make images for equations..

html-email.org

Here is an adaptation of org-mime-compose that does that for html messages.

(defun org-mime-compose (body fmt file &optional to subject headers)
  (require 'message)
  (let ((bhook
         (lambda (body fmt)
           (let ((hook (intern (concat "org-mime-pre-"
                                       (symbol-name fmt)
                                       "-hook"))))
             (if (> (eval `(length ,hook)) 0)
                 (with-temp-buffer
                   (insert body)
                   (goto-char (point-min))
                   (eval `(run-hooks ',hook))
                   (buffer-string))
               body))))
        (fmt (if (symbolp fmt) fmt (intern fmt)))
        (files (org-element-map (org-element-parse-buffer) 'link
                 (lambda (link)
                   (when (string= (org-element-property :type link) "file")
                     (file-truename (org-element-property :path link)))))))
    (compose-mail to subject headers nil)
    (message-goto-body)
    (cond
     ((eq fmt 'org)
      (require 'ox-org)
      (insert (org-export-string-as
               (org-babel-trim (funcall bhook body 'org)) 'org t)))
     ((eq fmt 'ascii)
      (require 'ox-ascii)
      (insert (org-export-string-as
               (concat "#+Title:\n" (funcall bhook body 'ascii)) 'ascii t)))
     ((or (eq fmt 'html) (eq fmt 'html-ascii))
      (require 'ox-ascii)
      (require 'ox-org)
      (let* ((org-link-file-path-type 'absolute)
             ;; we probably don't want to export a huge style file
             (org-export-htmlize-output-type 'inline-css)
             (org-html-with-latex 'dvipng)
             (html-and-images
              (org-mime-replace-images
               (org-export-string-as (funcall bhook body 'html) 'html t)))
             (images (cdr html-and-images))
             (html (org-mime-apply-html-hook (car html-and-images))))
        (insert (org-mime-multipart
                 (org-export-string-as
                  (org-babel-trim
                   (funcall bhook body (if (eq fmt 'html) 'org 'ascii)))
                  (if (eq fmt 'html) 'org 'ascii) t)
                 html)
                (mapconcat 'identity images "\n")))))
    (mapc #'mml-attach-file files)))

4 Summary

This makes it pretty nice to send rich-formatted html text to people.

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.3.5

Discuss on Twitter
« Previous Page -- Next Page »