;;; my.el --- Personal library  -*- lexical-binding: t -*-

;;; Commentary:
;; Org:
;;  Colored text in Org buffer and export.
;;    [[color:gray][text]]
;;    [[color:#cccccc][text]]

;;; Code:
;; ELisp:
;; (equal (symbol-name 'tmp) "tmp")  ;; get symbol as string and compare with string
;; (equal (intern "tmp") 'tmp)       ;; get string as symbol and compare with symbol
;; (regexp-quote "/foo/baz/*")       ;; => "/foo/baz/\\*"
;; (add-hook 'help-mode-hook 'virtual-auto-fill-mode)  ;; add a mode-hook
;; (add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook 'a-test-save-hook nil t)))  ;; add local hook to a mode-hook
;; Org:
;; https://orgmode.org/worg/dev/org-element-api.html
;; https://orgmode.org/worg/dev/org-syntax.html
;; Over an element, like a table.  The key must start with attr_.
;; The lower line shows the plist elements inside the org element context.

(require 'my-view)

(defgroup my nil
  "My concept mapping"
  :prefix "my-"
  :group 'emacs)

(defun my-help ()
  "Display help"
  (interactive)
  (let ((buffer-name "*Quick Help*"))
    (with-current-buffer (get-buffer-create buffer-name)
      (define-derived-mode my-org-help-mode org-mode "Org help mode"
        "Major mode for help buffers using Org mode")
      (insert "\

*General Commands*                     /S: shift, C: control, M: alt or ⌘/

  [[help:save-buffer][Save]] ................... =C-x= =C-s=    [[help:help-for-help][Help]] ................... =C-h= =C-h=
  [[help:write-file][Save as]] ................ =C-x= =C-w=    [[help:keyboard-quit][Cancel]] ..................... =C-g=
  [[help:find-file][Open file]] .............. =C-x= =C-f=    [[help:undo][Undo]] ....................... =C-/=
  [[help:counsel-recentf][Open recent]] ............ =C-x= =C-r=    [[help:kill-buffer][Close buffer]] ............. =C-x= =k=
  [[help:dired][Browse directory]] ......... =C-x= =d=    [[help:counsel-switch-buffer][Switch buffer]] ............ =C-x= =b=
  [[help:previous-buffer][Previous buffer]] ....... =C-x= =left=    [[help:next-buffer][Next buffer]] .......... =C-x= =right=
  [[help:save-buffers-kill-terminal][Quit]] ................... =C-x= =C-c=    [[toggle-window-split][Toggle 2 window split]]  ... =C-x= =|=

  [[help:swiper][Search]] ..................... =C-s=    [[help:goto-line][Go to line]] ............. =M-g= =M-g=
  [[help:query-replace][Replace]] .................... =M-%=    [[help:counsel-M-x][Execute]] .................... =M-x=
  [[help:begin-of-buffer][Begin of buffer]] ............ =M-<=    [[help:beginning-of-line][Begin of line]] .............. =C-a=
  [[help:end-of-buffer][End of buffer]] .............. =M->=    [[help:end-of-line][End of line]] ................ =C-e=

  [[help:set-mark-command][Mark]] ..................... =C-SPC=    [[help:kill-ring-save][Copy]] ....................... =M-w=
  [[help:rectangle-mark-mode][Mark rectangular]] ....... =C-x SPC=    [[help:yank][Paste]] ...................... =C-y=
  [[help:mark-whole-buffer][Mark whole buffer]] ........ =C-x= =h=    [[help:counsel-yank-pop][Paste older]] ................ =M-y=
  [[help:kill-region][Cut]] ........................ =C-w=    [[help:move-line-up][Move line up]] .............. =M-up=
  [[help:kill-line][Cut rest of line]] ........... =C-k=    [[move-line-down][Move line down]] .......... =M-down=
  [[help:indent-rigidly][Indent lines]] ........... =C-x= =TAB=

  *Multiple cursors*                    [[help:mc/mark-all-dwim][Mark all at point]] .... =M-m= =s= =m= =a=
  , Jump to next cursor ...... =C-v=    , (un)hide other lines ..... =C-'=
  , Jump to previous cursor .. =M-v=
                                                    /[[info:emacs#Key%2520Bindings][Other key bindings]]/
  *Mouse*
  [[help:mwheel-scroll][Scroll right]] ........... =mouse-6=    [[help:previous-buffer][Previous buffer]] ........ =mouse-8=
  [[help:mwheel-scroll][Scroll left]] ............ =mouse-7=    [[help:next-buffer][Next buffer]] ............ =mouse-9=
  [[help:mouse-wheel-text-scale][Zoom in]] ............. =C-wheel-up=    [[help:mouse-wheel-global-text-scale][Zoom in global]] .... =C-M-wheel-up=
  [[help:mouse-wheel-text-scale][Zoom out]] .......... =C-wheel-down=    [[help:mouse-wheel-global-text-scale][Zoom out global]] . =C-M-wheel-down=

  *Spelling*                                           (flyspell ispell)
  Spell check whole buffer ........................... [[help:flyspell-buffer][flyspell-buffer]]
  On-the-fly spell checking ............................ [[help:flyspell-mode][flyspell-mode]]
  , [[help:flyspell-correct-wrapper][Correct word]] ............. =C-;=    , [[help:flyspell-goto-next-error][Next error]] ............... =C-,=
  , [[help:flyspell-correct-word][Correct word]] ......... =mouse-2=


*Org*                      see also [[elisp:(my-org-article-help)][Article help]] and YASnippet templates

  [[help:org-toggle-pretty-entities][Pretty entities]] (\\xyz to UTF8 char) ...................... =C-c= =C-x= =\\=
  [[help:org-redisplay-images][Redisplay inline images]] .............................. =C-c= =C-x= =C-M-v=

  *Table*
  [[help:org-shiftleft][Move cell left]] .......... =S-left=    [[help:org-shiftup][Move cell up]] .............. =S-up=
  [[help:org-shiftright][Move cell right]] ........ =S-right=    [[help:org-shiftdown][Move cell down]] .......... =S-down=
  [[help:org-table-blank-field][Clear cell]] ................. =SPC=

  [[help:org-table-create-or-convert-from-region][Convert seperator (1. tab, 2. comma, 3. space)]] ............... =C-c= =|=
  , comma .............. =C-u= =C-c= =|=    , tab separator .. =C-u= =C-u= =C-c= =|=
  , spaces ..... =C-u= =INTEGER= =C-c= =|=    , regex ........ =C-u= =REGEX= =C-c= =|=

  *SRC*
  [[help:org-babel-tangle][write src block to file]] (=:tangle filename=) ............... =C-c= =C-v= =t=

  *SRC figures generating tex output, e.g. gnuplot*
  Do not use =%= but =\\%= or in a format command =\\%%=
  - =%= starts a comment in LaTeX and therefore stops further interpre-
    tations like a closing bracket =}= etc.
    - Gnuplot graphics are imported as .tex (text, axis, legend) and
      .eps (figure) files.
  - =\\%= and =\\%%= can also be used for HTML SVG export

  *Export*
  [[help:org-html-export-to-html][HTML]] ............... =C-c= =C-e= =h= =h=    [[help:org-latex-export-to-pdf][PDF (LaTeX)]] ........ =C-c= =C-e= =l= =l=
  [[help:my-org-article-html-export-to-html][HTML article]] ....... =C-c= =C-e= =h= =a=    [[help:my-org-article-latex-export-to-pdf][PDF article (LaTeX)]]  =C-c= =C-e= =l= =a=
  [[help:my-org-article-html-export-to-html-notify-async][HTML article (async)]] ........ =F5=    [[help:my-org-article-latex-export-to-pdf-notify-async][PDF article (LaTeX, async)]] .. =F6=
  [[help:my-org-letter-latex-export-to-pdf][PDF letter (LaTeX)]] . =C-c= =C-e= =l= =t=

  *Export LaTeX* [[help:org-latex-remove-logfiles][Logfiles are removed]] (deactivate for debugging)


*Elisp*

  [[help:eval-expression][Evaluate expression]] ........ =M-:=    [[help:forward-sexp][Jump to next expression]] .. =C-M-f=
  - [[help:eval-last-sexp][last to echo]] ......... =C-x= =C-e=    [[help:backward-sexp][Jump tp prev. expression]] . =C-M-b=
  - [[help:eval-print-last-sexp][last to buffer]]                    [[help:kill-sexp][Cut expression]] ........... =C-M-k=
  [[help:mark-sexp][Mark expression]] .......... =C-M-@=    [[help:backward-kill-sexp][Cut last expression]] . =C-M-backsp=

")
      (my-org-help-mode)
      (goto-char (point-min))
      (local-set-key (kbd "q") 'kill-buffer-and-window)
      (local-set-key (kbd "C-g") 'kill-buffer-and-window)
      (not-modified)
      (read-only-mode))
    (pop-to-buffer buffer-name '((display-buffer-below-selected)
                                 (window-parameters . ((no-other-window . nil)))
                                 (window-height . fit-window-to-buffer)))
    (message "C-g - Quit, q - Quit")))

(defun my-eval-string (string)
  "Evaluate elisp code stored in a string."
  (eval (car (read-from-string (format "(progn %s)" string)))))

(defun my-list-delete (element list)
  "Destructive version of `delete'.
LIST will be nil if the last ELEMENT was deleted.
Example:
  (setq my-list '(\"a\"))
  (my-list-delete \"a\" 'my-list)
  (setq my-list '(a))
  (my-list-delete 'a 'my-list)
  (add-to-list 'my-list '(\"a\"))
  (my-list-delete '(\"a\") 'my-list)"
  (set list (delete element (symbol-value list))))

(defun my-list-to-org-table (lst)
  "Convert list into an Org table."
  (let ((lines lst)
        (rows)
        (tbl))
    (while lines
      (setq rows (car lines))
      (setq tbl (concat tbl "|"))
      (while rows
        (setq tbl (concat tbl (format "%s|" (car rows))))
        (setq rows (cdr rows)))
      (setq tbl (concat tbl "\n"))
      (setq lines (cdr lines)))
    tbl))

(defmacro my-plist-put (plist &rest args)
  "Example usage:
  (my-plist-put my-org-table-colored-cells 'table-name '(\"@23$3\" \"blue\"))
  (my-plist-put my-org-table-colored-cells
                'table-name-1 '(\"@13$3\" \"red\") 'table-name-2 '(\"@33$3\" \"green\"))"
  (let ((list nil))
    (while args
      (push `(setq ,plist (plist-put ,plist ,(pop args) ,(pop args))) list))
    (cons 'progn (nreverse list))))

(defun my-interpolate (low high r rlow rhigh)
  "Return the point between LOW and HIGH that corresponds to where R is \
between RLOW and RHIGH.

Linear interpolate of R in the interval RLOW RHIGH.

RESULT - LOW    HIGH - LOW
------------ = ------------
  R - RLOW     RHIGH - RLOW

                             HIGH - LOW
RESULT = LOW + (R - RLOW) * ------------
                            RHIGH - RLOW

Example:
  (my-interpolate 0 100 12 0 10) => 120"
  (+ low (/ (* (- high low) (- r rlow)) (- rhigh rlow))))

(defun my-color-luminance (R G B)
  "Luminosity, relative luminance.

L = 0.2126*R' + 0.7152*G' + 0.0722*B' with
[R',G',B'] = [R,G,B] / 12.92 if [R,G,B] <= 0.03928 else (([R,G,B]+0.055)/1.055)^2.4
earlier
L = 0.2126*R^2.2 + 0.7152*G^2.2 + 0.0722*B^2.2

R,G,B,L = [0, 1]
See also `my-color-contrast'"
  ;; https://www.w3.org/Graphics/Color/sRGB.html
  (let ((R (if (<= R 0.03928) (/ R 12.92) (expt (/ (+ R 0.055) 1.055) 2.4)))
        (G (if (<= G 0.03928) (/ G 12.92) (expt (/ (+ G 0.055) 1.055) 2.4)))
        (B (if (<= B 0.03928) (/ B 12.92) (expt (/ (+ B 0.055) 1.055) 2.4))))
    (+ (* 0.2126 R) (* 0.7152 G) (* 0.0722 B)))
  ;; earlier
  ;;(+ (* 0.2126 (expt R 2.2)) (* 0.7152 (expt G 2.2)) (* 0.0722 (expt B 2.2)))
  )

(defun my-color-contrast (R1 G1 B1 &optional R2 G2 B2)
  "Luminosity contrast ratio.
Calculate the difference between the given colors R1, G1, B1 and R2,
G2, B2.  The returned value should be greater than or equal to 4.5
\(earlier greater than 5) for best readability.  Using
`my-color-luminance'.  R2, G2, B2 defaults to black.  See also
`color-dark-p'."
  ;; https://www.w3.org/TR/WCAG20/#contrast-ratiodef
  ;; https://www.w3.org/TR/2016/NOTE-WCAG20-TECHS-20161007/G18
  (let* ((L1 (my-color-luminance R1 G1 B1))
         (R2 (if R2 R2 0)) (G2 (if G2 G2 0)) (B2 (if B2 B2 0))
         (L2 (my-color-luminance R2 G2 B2)))
    (if (> L1 L2)  ;; normally L1 defined as the lighter color and L2 as the darker color
        (/ (+ L1 0.05) (+ L2 0.05))
      (/ (+ L2 0.05) (+ L1 0.05)))))

(defun my-color-rgb-gradient (rgbsteps position)
  "RGBSTEPS is a list of four element lists.
The list consists
- a start position value for the color d
- and the three color parameters r g b
- example
  '((d1 r1 g1 b1)
    (d2 r2 g2 b2)
    (d3 r3 g3 b3)
    (d4 r4 g4 b4))
  with d1 < d2 < d3 < d4
if POSITION <= d1 then return (r1 g1 b1)
else remove the rgbstep_i where POSITION > di+1 from RGBSTEPS
if there is only one rgbstep left in RGBSTEPS return the (rn gn bn) values
otherwise interpolate of the first two rgbstep elements of the remaining
RGBSTEPS list.

Examples:
  (my-rgb-gradient '((1 1 1 1) (2 2 2 2) (3 3 3 3)) 2)
  (my-rgb-gradient '((1 1 1 1) (2 2 2 2)) 2)"
  ;; if position <= first element of first element (d1)
  ;; then return other elements of first element (r1 g1 b1)
  (if (<= position (caar rgbsteps))
      (cdar rgbsteps)
    ;; if there are other elements  and  if position > d1(,new) of the first other element
    ;; then remove first element
    (while (and (cdr rgbsteps) (> position (caadr rgbsteps)))
      (setq rgbsteps (cdr rgbsteps)))
    ;; if there is no other element, return other elements (rn gn bn) of the element in list
    (if (null (cdr rgbsteps))
        (cdar rgbsteps)
      ;; else there are at least two elements left.
      ;; return interpolation of the first two elements
      (list
       ;;                     r1 g1 b1                  r2 g2 b2                   d1               d2
       (my-interpolate (nth 1 (car rgbsteps)) (nth 1 (cadr rgbsteps)) position (caar rgbsteps) (caadr rgbsteps))
       (my-interpolate (nth 2 (car rgbsteps)) (nth 2 (cadr rgbsteps)) position (caar rgbsteps) (caadr rgbsteps))
       (my-interpolate (nth 3 (car rgbsteps)) (nth 3 (cadr rgbsteps)) position (caar rgbsteps) (caadr rgbsteps))))))

(with-eval-after-load 'org

  ;;; colored table cells
  ;; https://emacs.stackexchange.com/questions/7375/can-i-format-cells-in-an-org-mode-table-differently-depending-on-a-formula
  (require 'ov)

  (defun my-org-keywords ()
    "Parse the buffer and return a cons list of (key . value)
from lines like:
#+KEY: value"
    (org-element-map (org-element-parse-buffer 'greater-element) 'keyword
      (lambda (keyword) (cons (org-element-property :key keyword)
                              (org-element-property :value keyword)))))

  (defun my-org-keyword (keyword)
    "Get the value of a KEYWORD in the form of #+KEYWORD: value

Using `my-org-keywords' to find all keywords."
    (cdr (assoc keyword (my-org-keywords))))

  (defun my-org-keyword-re (KEYWORD)
    "Get the value from a line like this
#+KEYWORD: value
in a buffer.

Using a case-insensitive regular expressions search in the buffer to grab the value."
    (interactive)
    (let ((case-fold-search t)
          (re (format "^#\\+%s:[ \t]+\\([^\t\n]+\\)" KEYWORD)))
      (if (not (save-excursion
                 (or (re-search-forward re nil t)
                     (re-search-backward re nil t))))
          (error (format "No line containing #+%s: value found" KEYWORD)))
      (match-string 1)))
  
  (defun my-org-attr-to-list (attr)
    "
ATTR is the for example (plist-get table :attr_color)

#+ATTR_MY_KEY: this and that
:attr_my_key (\"this and that\")

#+ATTR_MY_KEY: this and that
#+ATTR_MY_KEY: foo baz
:attr_my_key (\"this and that\" \"foo baz\")"
    ;;(split-string (car attr))                             ;; this was only the first string, meaning only one (the last) attr_color line.
    ;;(split-string (string-join attr " "))                 ;; splits on space but also inside quotes
    (split-string-and-unquote (string-join attr " ")))

  (defun my-org-table-get ()
    "Check if cursor is inside an Org table or on #+TBLFM lines \
then return the table element otherwise return nil.
`org-at-table-p' is nil if cursor on #+TBLFM"
    (let ((element (org-element-at-point)))                 ;; get org element
      (while (and element (not (eq (car element) 'table)))  ;; check if it is table
        (setq element (plist-get (cadr element) :parent)))  ;; if not check if parent element is table
      (cond
       ((equal (car element) 'table)                        ;; only if table found
        (cadr element)))))                                  ;; return element

  (defun my-org-table-range-to-list (desc &optional val)
    "
Example usage:
\(my-org-table-range-to-list \"@3$1\") -> (@3$1)
\(my-org-table-range-to-list \"@3$1\" \"red\") -> (@3$1 red)
\(my-org-table-range-to-list \"@3$1..@3$3\") -> (@3$1 @3$2 @3$3)
\(my-org-table-range-to-list \"@3$1..@3$3\" \"red\") -> (@3$1 red @3$2 red @3$3 red)

Used in `my-org-table-list-of-range-to-list'"
    (if (string-match-p (regexp-quote "..") desc)
        (let (from-row from-column to-row to-column result)
          (string-match "@\\([0-9]+\\)\$\\([0-9]+\\)\\.\\.@\\([0-9]+\\)\$\\([0-9]+\\)" desc)
          (setq from-row (string-to-number (match-string 1 desc)))  ;; 1st parentheses match from string-match
          (setq from-column (string-to-number (match-string 2 desc)))  ;; 2nd parentheses match from string-match
          (setq to-row (string-to-number (match-string 3 desc)))  ;; 3rd parentheses match from string-match
          (setq to-column (string-to-number (match-string 4 desc)))  ;; 4th parentheses match from string-match
          (loop for i upfrom to-row downto from-row   ;; push prepends
                do
                (cl-loop for j upfrom to-column downto from-column
                         do
                         (when val (push val result))    ;; push prepends
                         (push (concat "@" (number-to-string i) "$" (number-to-string j)) result)
                         ))
          result)
      (if val (list desc val) (list desc))))
  
  (defun my-org-table-list-of-range-to-list (seq)
    "
@3$1..@3$3 red @1$3 #0055aa -> (@3$1 red @3$2 red @3$3 red @1$3 #0055aa)

Used in `my-org-table-cell-color-attr'
uses `my-org-table-range-to-list'"
    (when seq
      (let (result)
        ;;(message "%s" seq)
        (while seq
          (setq result
                (append result
                        (my-org-table-range-to-list (car seq) (cadr seq))))
          (setq seq (cddr seq)))
        result)))

  (defun my-org-table-cell-color (beg end seq)
    "BEG and END are the beginning and the end of the table.
SEQ is a list of cell name and color name pairs."
    (save-excursion                                         ;; save cursor and go back to it after, important for other features
      (goto-char beg)                                       ;; go inside the table, required for org-table-analyse
      (org-table-analyze)                                   ;; required for org-table-goto-field
      (ov-clear beg end)
      (while seq                                            ;; run as long elements are in list
        (let* ((cell (car seq))                             ;; get first "key"
               (color-name (cadr seq))                      ;; get first "value"
               (color-rgb (color-name-to-rgb color-name))
               (bg (apply #'color-rgb-to-hex color-rgb))
               ;;(fg (if (>= (apply #'my-color-contrast color-rgb) 4.5) "#000000" "#ffffff"))
               (fg (if (>= (apply #'my-color-contrast (append color-rgb (color-name-to-rgb "gray10"))) 4.5) "gray10" "gray80"))
               ;;(fg (if (>= (apply #'my-color-contrast color-rgb) 4.5) "gray10" 'default))
               (beg (progn (org-table-goto-field cell) (backward-char) (point)))  ;; beginning of the cell
               ;;(end (progn (org-table-end-of-field 1) (forward-char) (point)))  ;; for left aligned cells end is end of content not of cell
               (end (1- (plist-get (cadr (org-element-context)) :end)))
               )
          (ov beg end 'face (list :background bg
                                  :foreground fg))
          (setq seq (cddr seq))))))                         ;; remove first element from list

  (defvar-local my-org-table-cell-color-list
    nil
    "Plist of table names with list of cells to color.
It is used for the function `my-org-table-cell-color-var'.
Example usage:
  (my-plist-put my-org-table-cell-color-list 'table-name '(\"@23$3\" \"blue\"))
  (setq my-org-table-cell-color-list '(
    table-name-1 (
      \"@33$3\" \"blue\"
      \"@34$2\" \"red\"
      \"@34$3\" \"green\"
    )
    table-name-2 (\"@13$3\" \"blue\" \"@14$2\" \"red\" \"@14$3\" \"green\")
  ))")
  
  (defun my-org-table-cell-color-var ()
    "Function to color cells.
It uses the variable `my-org-table-cell-color-list'.
Example usage to add a (normal, global) hook:
  (add-hook 'org-ctrl-c-ctrl-c-hook 'my-org-table-cell-color-var)
Example usage to add a local hook:
  (add-hook 'org-ctrl-c-ctrl-c-hook 'my-org-table-cell-color-var nil t)"
    (let* ((table (my-org-table-get))                       ;; get table element
           (table-name (plist-get table :name)))            ;; get table name (string)
      (cond
       (table-name                                          ;; only if table found
        (let ((begcont (plist-get table :contents-begin))   ;; :begin at the beginning of #+NAME:, #+ATTR_...
              (endcont (plist-get table :contents-end))     ;; :end at the end of #+TBLFM: ...
              (tmp-list (plist-get my-org-table-cell-color-list (intern table-name))))  ;; get value of key (string to symbol)
          (my-org-table-cell-color begcont endcont tmp-list))))))

  (defun my-org-table-cell-color-attr ()
    "Function to color cells.
It uses the Org keyword #+ATTR_COLOR: CELL COLOR ...
COLOR is either a color name (see `list-colors-display') or a 
Multiple #+ATTR_COLOR are possible. They are joint together.
Example usage to add a (normal, global) hook:
  (add-hook 'org-ctrl-c-ctrl-c-hook 'my-org-table-cell-color-attr)
Example usage to add a local hook:
  (add-hook 'org-ctrl-c-ctrl-c-hook 'my-org-table-cell-color-attr nil t)
Example usage
#+ATTR_COLOR: @1$3 #0055aa @1$1 #887744 @1$2 #008822
#+ATTR_COLOR: @2$3 blue @2$1 yellow @2$2 green
#+ATTR_COLOR: @3$1..@4$3 #cc0000 @5$3 red
"
    (let* ((table (my-org-table-get))                       ;; get table element
           (table-attr (plist-get table :attr_color)))      ;; nil if attr not set, table can be nil
      (cond
       (table-attr                                          ;; only if table attr found
        (let ((begcont (plist-get table :contents-begin))   ;; :begin at the beginning of #+NAME:, #+ATTR_...
              (endcont (plist-get table :contents-end))     ;; :end at the end of #+TBLFM: ...
              (color-list
               (my-org-table-list-of-range-to-list
                (my-org-attr-to-list table-attr))))
          (my-org-table-cell-color begcont endcont color-list))))))

  ;; colored text in org-mode using links
  ;; http://kitchingroup.cheme.cmu.edu/blog/2016/01/16/Colored-text-in-org-mode-with-export-to-HTML/
  ;; https://en.wikibooks.org/wiki/LaTeX/Colors
  ;; this will be evaluated during export
  (require 'ol)
  (require 'color)
  (require 'ov)
  (org-link-set-parameters
   "color"
   :follow
  ;;(org-add-link-type
  ;; "color"
   '(lambda (path)
     "No follow action.")
   :export
   '(lambda (color description backend)
     "if link description is empty use color as description.
[[color:COLOR][DESCRIPTION]]"
     (cond
      ((eq backend 'html)
       (let ((rgb (color-name-to-rgb color))
             r g b)
         (if rgb
             (progn
               (setq r (truncate (* 255 (nth 0 rgb))))
               (setq g (truncate (* 255 (nth 1 rgb))))
               (setq b (truncate (* 255 (nth 2 rgb))))
               (format "<span style=\"color: rgb(%s,%s,%s)\">%s</span>"
                       r g b
                       (or description color)))
           (format "No Color RGB for %s" color))))
      ((eq backend 'latex)
       (let ((rgb (color-name-to-rgb color)))
         (if rgb
             (progn
               (format "\\textcolor[rgb]{%s,%s,%s}{%s}"
                       (nth 0 rgb) (nth 1 rgb) (nth 2 rgb)
                       (or description color)))
           (format "No Color RGB for %s" color))))
      )))
  (defun my-org-link-color (limit)
    "Helper function for colored text in buffer.
Usage:
  [[color:gray][text]]
  [[color:#cccccc][text]]"
    (when (re-search-forward
           "color:[#0-9a-zA-Z]\\{2,\\}" limit t)
      (forward-char -2)
      (let ((link (org-element-context))
            color beg end post-blanks)
        (if link
            (progn
              (setq color (org-element-property :path link)
                    beg (org-element-property :begin link)
                    end (org-element-property :end link)
                    post-blanks (org-element-property :post-blank link))
              (set-match-data
               (list beg
                     (- end post-blanks)))
              (ov-clear beg end 'color)
              (ov beg
                  (- end post-blanks)
                  'color t
                  'face
                  `((:foreground ,color)))
              (goto-char end))
          (goto-char limit)
          nil))))
  (defun my-org-link-color-hook ()
    "activate with e.g. (add-hook 'org-mode-hook 'my-org-link-color-hook)"
    (font-lock-add-keywords
     nil
     '((my-org-link-color (0 'org-link t)))
     t)
    )

  )  ;; with-eval-after-load 'org

(defun my-magit-repo-status (directory branch &optional with-update as-text)
  (when (featurep 'magit)
    (let* ((default-directory directory)
           (remotes (magit-list-remotes))
           (diff))
      (when (= (length remotes) 1)
        (when with-update (magit-git-string "fetch" (car remotes) branch))
        (let* ((remote-branch-name (concat (car remotes) "/" branch))  ;; @{u} may not configured, TODO: (if (magit-rev-parse "@{u}") ...)
               (diff-count (magit-rev-diff-count "@" remote-branch-name)))  ;; i.e. git rev-list @...origin/master --count --left-right
          (setq diff (- (cadr diff-count) (car diff-count)))))
      (if as-text
          (if (= diff 0) "Up-to-date"
            (if (> diff 0) "Need to pull"
              "Need to push"))
        diff))))

(provide 'my)
;;; my.el ends here
