<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/"
     >
  <channel>
    <atom:link href="http://kitchingroup.cheme.cmu.edu/blog/feed/index.xml" rel="self" type="application/rss+xml" />
    <title>The Kitchin Research Group</title>
    <link>https://kitchingroup.cheme.cmu.edu/blog</link>
    <description>Chemical Engineering at Carnegie Mellon University</description>
    <pubDate>Sat, 01 Nov 2025 13:47:46 GMT</pubDate>
    <generator>Blogofile</generator>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    
    <item>
      <title>Clickable org-contacts in text files</title>
      <link>https://kitchingroup.cheme.cmu.edu/blog/2015/06/22/Clickable-org-contacts-in-text-files</link>
      <pubDate>Mon, 22 Jun 2015 13:07:03 EDT</pubDate>
      <category><![CDATA[orgmode]]></category>
      <category><![CDATA[contacts]]></category>
      <guid isPermaLink="false">eWJRQWpiR0RtBwWGt_v3x7v5i9o=</guid>
      <description>Clickable org-contacts in text files</description>
      <content:encoded><![CDATA[



&lt;p&gt;
Continuing my adventures with clickable text (See &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2015/06/21/Clickable-email-addresses-in-emacs/"&gt;clickable email addresses&lt;/a&gt; and &lt;a href="http://kitchingroup.cheme.cmu.edu/blog/2015/03/18/Clickable-links-for-Twitter-handles-in-Emacs/"&gt;clickable twitter handles&lt;/a&gt; ), here we consider how to get clickable names that are also in my org-contacts database. The goal is to have these names highlighted and clickable so that when I click on them I get a hydra menu of actions, e.g. to open the contact, email them, etc&amp;#x2026; We will again use button-lock to do the action. We will construct a fairly large regexp to match all the names in the org-contacts database. This turns out to be very easy using the regexp-opt function.
&lt;/p&gt;

&lt;p&gt;
First, I formalize the code I used last time to get text around the point that has a text-property. We will use that to get the text that has been highlighted by button-lock.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-emacs-lisp"&gt;(&lt;span style="color: #0000FF;"&gt;defun&lt;/span&gt; &lt;span style="color: #006699;"&gt;get-surrounding-text-with-property&lt;/span&gt; (property)
  &lt;span style="color: #036A07;"&gt;"Return text surrounding point with the text-property PROPERTY."&lt;/span&gt;
  (&lt;span style="color: #0000FF;"&gt;let&lt;/span&gt; ((start) (end))
    (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; (get-text-property (point) property)
      (&lt;span style="color: #0000FF;"&gt;save-excursion&lt;/span&gt;
        (&lt;span style="color: #0000FF;"&gt;while&lt;/span&gt; (get-text-property (point) property)
          (backward-char))
        (forward-char)
        (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; start (point))
        (&lt;span style="color: #0000FF;"&gt;while&lt;/span&gt; (get-text-property (point) property)
          (forward-char))
        (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; end (point)))
      (buffer-substring start end))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
get-surrounding-text-with-property
&lt;/pre&gt;

&lt;p&gt;
I want to use nicknames that are defined in my org-contacts database. We first try to return an assoc lookup, then the slower approach of looping through the entries to find a matching nickname.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-emacs-lisp"&gt;(&lt;span style="color: #0000FF;"&gt;defun&lt;/span&gt; &lt;span style="color: #006699;"&gt;get-contact-from-name-or-nickname&lt;/span&gt; (name-or-nickname)
  &lt;span style="color: #036A07;"&gt;"Return a contact from the org-contacts database for NAME-OR-NICKNAME."&lt;/span&gt;
  (&lt;span style="color: #0000FF;"&gt;or&lt;/span&gt;
   (assoc name-or-nickname (org-contacts-db))
   &lt;span style="color: #8D8D84;"&gt;;; &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;no assoc, so now we have to find a nickname&lt;/span&gt;
   (&lt;span style="color: #0000FF;"&gt;catch&lt;/span&gt; '&lt;span style="color: #D0372D;"&gt;contact&lt;/span&gt;
     (&lt;span style="color: #0000FF;"&gt;dolist&lt;/span&gt; (contact (org-contacts-db))
       (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; (-contains? (s-split &lt;span style="color: #008000;"&gt;","&lt;/span&gt; (&lt;span style="color: #0000FF;"&gt;or&lt;/span&gt; (cdr (assoc &lt;span style="color: #008000;"&gt;"NICKNAMES"&lt;/span&gt; (caddr contact))) &lt;span style="color: #008000;"&gt;" "&lt;/span&gt;)) name-or-nickname)
         (&lt;span style="color: #0000FF;"&gt;throw&lt;/span&gt; '&lt;span style="color: #D0372D;"&gt;contact&lt;/span&gt; contact))))))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
get-contact-from-name-or-nickname
&lt;/pre&gt;

&lt;p&gt;
Now, let us write a hydra function that will be our menu of actions. For some reason, when you click on a highlighted text the mouse moves to the end of the text, so in our hydra function we move back a char, and then get the info. Basically, we get the name, then get the contact, and extract what we need from there. Here we provide functionality to open a contact, email a contact or open the url of the contact (if it exists). I also want a conditional hydra, which doesn't seem to be an option yet, so we we roll our own here. Basically, we construct the code for a hydra, and only add a menu option to open the url if we find one in the contact. We will have to eval the code returned from this function to get the hydra body, and then call the body function in the action function for the highlighted text.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-emacs-lisp"&gt;(&lt;span style="color: #0000FF;"&gt;defun&lt;/span&gt; &lt;span style="color: #006699;"&gt;conditional-hydra-actions&lt;/span&gt; ()
  &lt;span style="color: #036A07;"&gt;"Construct code to create a hydra with conditional options."&lt;/span&gt;
  (&lt;span style="color: #0000FF;"&gt;let&lt;/span&gt; ((code  '(defhydra org-contacts (&lt;span style="color: #006FE0;"&gt;:color&lt;/span&gt; blue)
                  &lt;span style="color: #008000;"&gt;"Org contacts"&lt;/span&gt;)))
    (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; code
          (append
           code
           '((&lt;span style="color: #008000;"&gt;"o"&lt;/span&gt; (&lt;span style="color: #0000FF;"&gt;progn&lt;/span&gt;
                    (backward-char)
                    (&lt;span style="color: #0000FF;"&gt;let*&lt;/span&gt; ((name (get-surrounding-text-with-property 'org-contact))
                           (contact (get-contact-from-name-or-nickname name))
                           (contact-marker (nth 1 contact)))
                      (switch-to-buffer (marker-buffer contact-marker))
                      (goto-char (marker-position contact-marker))
                      (show-subtree)))
              &lt;span style="color: #008000;"&gt;"Open contact"&lt;/span&gt;))))

    (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; code
          (append
           code '((&lt;span style="color: #008000;"&gt;"e"&lt;/span&gt; (&lt;span style="color: #0000FF;"&gt;progn&lt;/span&gt;
                         (backward-char)
                         (&lt;span style="color: #0000FF;"&gt;let*&lt;/span&gt; ((name (get-surrounding-text-with-property 'org-contact))
                                (contact (get-contact-from-name-or-nickname name))
                                (email (cdr (assoc &lt;span style="color: #008000;"&gt;"EMAIL"&lt;/span&gt; (caddr contact))))))
                         (mu4e~compose-mail email))
                   &lt;span style="color: #008000;"&gt;"Email contact"&lt;/span&gt;))))

    &lt;span style="color: #8D8D84;"&gt;;; &lt;/span&gt;&lt;span style="color: #8D8D84; font-style: italic;"&gt;conditional menu for opening a URL&lt;/span&gt;
    (&lt;span style="color: #0000FF;"&gt;let*&lt;/span&gt; ((name (get-surrounding-text-with-property 'org-contact))
           (contact (assoc name (org-contacts-db)))
           (url (cdr (assoc &lt;span style="color: #008000;"&gt;"URL"&lt;/span&gt; (caddr contact)))))
      (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; url
        (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; code
              (append
               code '((&lt;span style="color: #008000;"&gt;"w"&lt;/span&gt; (&lt;span style="color: #0000FF;"&gt;progn&lt;/span&gt;
                             (backward-char)
                             (&lt;span style="color: #0000FF;"&gt;let*&lt;/span&gt; ((name (get-surrounding-text-with-property 'org-contact))
                                    (contact (get-contact-from-name-or-nickname name))
                                    (url (cdr (assoc &lt;span style="color: #008000;"&gt;"URL"&lt;/span&gt; (caddr contact)))))
                               (&lt;span style="color: #0000FF;"&gt;if&lt;/span&gt; url
                                   (browse-url url)
                                 (message &lt;span style="color: #008000;"&gt;"No url found."&lt;/span&gt;))))
                       &lt;span style="color: #008000;"&gt;"Open in browser"&lt;/span&gt;))))))
    code))
&lt;/pre&gt;
&lt;/div&gt;

&lt;pre class="example"&gt;
conditional-hydra-actions
&lt;/pre&gt;

&lt;p&gt;
I also want to have nicknames in this list, because sometimes I don't use the full names in my contact database. These are stored in a comma-separated property called NICKNAMES in entries that have them. A subtle point here is that it complicates looking up the contact in the database. Normally, I can get this by a simple assoc lookup. For the nicknames, that will fail, so we need a back up method. Now, the highlighting code. You can make the regexp by passing a list of strings to match to regexp-opt. We get our list of strings from:
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-emacs-lisp"&gt;(append
 (mapcar 'car (org-contacts-db))
 (&lt;span style="color: #0000FF;"&gt;let&lt;/span&gt; ((nicknames '()))
   (&lt;span style="color: #0000FF;"&gt;dolist&lt;/span&gt; (contact (org-contacts-db))
     (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; (assoc &lt;span style="color: #008000;"&gt;"NICKNAMES"&lt;/span&gt; (caddr contact))
       (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; nicknames
             (append nicknames (s-split &lt;span style="color: #008000;"&gt;","&lt;/span&gt; (cdr (assoc &lt;span style="color: #008000;"&gt;"NICKNAMES"&lt;/span&gt; (caddr contact))))))))
   nicknames))
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
I am not going to show them here to protect my contacts ;). Now, we create the function that highlights the contacts. and add it as a hook function to text-mode-hook.
&lt;/p&gt;

&lt;div class="org-src-container"&gt;

&lt;pre class="src src-emacs-lisp"&gt;(&lt;span style="color: #0000FF;"&gt;defun&lt;/span&gt; &lt;span style="color: #006699;"&gt;highlight-org-contacts&lt;/span&gt; ()
  (button-lock-set-button
   (regexp-opt
    (append
     (mapcar 'car (org-contacts-db))
     (&lt;span style="color: #0000FF;"&gt;let&lt;/span&gt; ((nicknames '()))
       (&lt;span style="color: #0000FF;"&gt;dolist&lt;/span&gt; (contact (org-contacts-db))
         (&lt;span style="color: #0000FF;"&gt;when&lt;/span&gt; (assoc &lt;span style="color: #008000;"&gt;"NICKNAMES"&lt;/span&gt; (caddr contact))
           (&lt;span style="color: #0000FF;"&gt;setq&lt;/span&gt; nicknames
                 (append
                  nicknames
                  (s-split &lt;span style="color: #008000;"&gt;","&lt;/span&gt; (cdr (assoc &lt;span style="color: #008000;"&gt;"NICKNAMES"&lt;/span&gt; (caddr contact))))))))
       nicknames)))
   (&lt;span style="color: #0000FF;"&gt;lambda&lt;/span&gt; ()
     (&lt;span style="color: #0000FF;"&gt;interactive&lt;/span&gt;)
     (eval (conditional-hydra-actions))
     (org-contacts/body))
   &lt;span style="color: #006FE0;"&gt;:face&lt;/span&gt; '((&lt;span style="color: #006FE0;"&gt;:background&lt;/span&gt; &lt;span style="color: #008000;"&gt;"MistyRose1"&lt;/span&gt;)
           (&lt;span style="color: #006FE0;"&gt;:underline&lt;/span&gt; t))
   &lt;span style="color: #006FE0;"&gt;:help-echo&lt;/span&gt; (format &lt;span style="color: #008000;"&gt;"An org contact"&lt;/span&gt;)
   &lt;span style="color: #006FE0;"&gt;:keyboard-binding&lt;/span&gt; (kbd &lt;span style="color: #008000;"&gt;"RET"&lt;/span&gt;)
   &lt;span style="color: #006FE0;"&gt;:additional-property&lt;/span&gt; 'org-contact))

(add-hook 'text-mode-hook 'highlight-org-contacts)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;
That does it. Now, whenever I open a text-based file, the names that are in my contacts are highlighted and actionable. This should be useful for meeting notes, etc&amp;#x2026;
&lt;/p&gt;
&lt;p&gt;Copyright (C) 2015 by John Kitchin. See the &lt;a href="/copying.html"&gt;License&lt;/a&gt; for information about copying.&lt;p&gt;&lt;p&gt;&lt;a href="/org/2015/06/22/Clickable-org-contacts-in-text-files.org"&gt;org-mode source&lt;/a&gt;&lt;p&gt;&lt;p&gt;Org-mode version = 8.2.10&lt;/p&gt;]]></content:encoded>
    </item>
  </channel>
</rss>
