Category Archives: helm

Digital index piles with Emacs: Rapid categorization of Org Mode items

Somewhat daunted by the prospect of categorizing more than a hundred sketches and blog posts for my monthly review, I spent some time figuring out how to create the digital equivalent of sorting index cards into various piles.

2015-02-01 Digital piles of index card -- index card #indexing #organization #pkm

2015-02-01 Digital piles of index cards – index card #indexing #organization #pkm

In fact, wouldn’t it be super-cool if the items could automatically guess which category they should probably go in, prompting me only if it wasn’t clear?

I wanted to write a function that could take a list structured like this:

  • Keyword A
    • Previous links
  • Keyword B
    • Previous links
  • Link 1 with Keyword A
  • Link 2 with Keyword B
  • Link 3 with Keyword A
  • Link 4

It should file Link 1 and 3 under Keyword A, Link 2 under Keyword B, and prompt me for the category for Link 4. At that prompt, I should be able to select Keyword A or Keyword B, or specify a new category.

Inspired by John Kitchin’s recent post on defining a Helm source, I wanted to get it to work with Helm.

First step: I needed to figure out the structure of the list, maybe including a sample from the category to make it clearer what’s included. org-list.el seemed to have useful functions for this. org-list-struct gave me the structure of the current list. Let’s say that a category is anything whose text does not match org-bracket-link-regexp.

(defun sacha/org-get-list-categories ()
  "Return a list of (category indent matching-regexp sample).
List categories are items that don't contain links."
  (let ((list (org-list-struct)) last-category results)
    (save-excursion
      (mapc
       (lambda (x)
         (goto-char (car x))
         (let ((current-item
                (buffer-substring-no-properties
                 (+ (point)
                    (elt x 1)
                    (length (elt x 2)))
                 (line-end-position))))
           (if (string-match
                org-bracket-link-regexp
                (buffer-substring-no-properties
                 (point)
                 (line-end-position)))
               ;; Link - update the last category
               (when last-category
                 (if (< (elt x 1) (elt last-category 1))
                     (setq results
                           (cons (append last-category
                                         (list
                                          (match-string-no-properties
                                           3
                                           (buffer-substring-no-properties
                                            (point)
                                            (line-end-position)))))
                                 (cdr results))))
                 (setq last-category nil))
             ;; Category
             (setq results
                     (cons
                      (setq last-category
                            (list
                             current-item
                             (elt x 1)
                             (concat "^"
                                     (make-string (elt x 1) ?\ )
                                     (regexp-quote
                                      (concat (elt x 2)
                                              current-item))
                                     "$")))
                      results)))))
       list))
    results))

The next step was to write a function that guessed the list category based on the item text, and moved the item there.

(defvar sacha/helm-org-list-candidates nil)
(defun sacha/helm-org-list-categories-init-candidates ()
  "Return a list of categories from this list in a form ready for Helm."
  (setq sacha/helm-org-list-candidates
        (mapcar (lambda (x)
                  (cons (if (elt x 3)
                            (format "%s - %s" (car x) (elt x 3))
                          (car x))
                        x))
                (sacha/org-get-list-categories))))

(defun sacha/org-move-current-item-to-category (category)
  (when category
    (let* ((beg (line-beginning-position))
           (end (line-end-position))
           (string (buffer-substring-no-properties beg end)))
      (save-excursion
        (when (re-search-backward (elt category 2) nil t)
          (delete-region beg (min (1+ end) (point-max)))
          (forward-line 1)
          (insert (make-string (+ 2 (elt category 1)) ?\ )
                  string "\n")))) t))

(defun sacha/org-guess-list-category (&optional categories)
  (interactive)
  (require 'cl-lib)
  (unless categories
    (setq categories
          (sacha/helm-org-list-categories-init-candidates)))
  (let* ((beg (line-beginning-position))
         (end (line-end-position))
         (string (buffer-substring-no-properties beg end))
         (found
          (cl-member string
                     categories
                     :test
                     (lambda (string cat-entry)
                       (string-match (regexp-quote (downcase (car cat-entry)))
                                     string)))))
    (when (car found)
      (sacha/org-move-current-item-to-category
       (cdr (car found)))
      t)))

After that, I wrote a function that used Helm to prompt me for a category in case it couldn’t guess the category. It took me a while to figure out that I needed to use :init instead of :candidates because I wanted to read information from the buffer before Helm kicked in.

(setq sacha/helm-org-list-category-source
      (helm-build-sync-source
          "Non-link categories in the current list"
        :init 'sacha/helm-org-list-categories-init-candidates
        :candidates 'sacha/helm-org-list-candidates
        :action 'sacha/org-move-current-item-to-category
        :fuzzy-match t))

(defun sacha/org-guess-uncategorized ()
  (interactive)
  (sacha/helm-org-list-categories-init-candidates)
  (let (done)
    (while (not done)
      (save-excursion
        (unless (sacha/org-guess-list-category sacha/helm-org-list-candidates)
          (unless
              (helm :sources
                    '(sacha/helm-org-list-category-source
                      sacha/helm-org-list-category-create-source))
            (setq done t))))
      (unless done
        (setq done (not (looking-at "^[-+] \\[")))))))

The :action above refers to this function, which creates a category if it doesn’t exist yet.

(setq sacha/helm-org-list-category-create-source
      (helm-build-dummy-source
          "Create category"
        :action (helm-make-actions
                 "Create category"
                 (lambda (candidate)
                   (save-excursion
                     (let* ((beg (line-beginning-position))
                            (end (line-end-position))
                            (string (buffer-substring beg end)))
                       (delete-region beg (min (1+ end) (point-max)))
                       (org-beginning-of-item-list)
                       (insert "- " candidate "\n  " string "\n")))
                   (sacha/helm-org-list-categories-init-candidates)))))

I’m new to fiddling with Helm, so this implementation is not the best it could be. But it’s nifty and it works the way I want it to, hooray! Now I can generate a list of blog posts and unblogged sketches, categorize them quickly, and then tweak the categorizations afterwards.

2015-02-01 Index card sketches and monthly reviews -- index card #organization #pkm #indexing

2015-02-01 Index card sketches and monthly reviews – index card #organization #pkm #indexing

You can see the results in my January 2015 review, or check my config to see if the code has changed.

My next step for learning more about Helm sources is probably to write a Helm command that creates a montage of selected images. John Kitchin has a post about handling multiple selection in Helm, so I just need to combine that with my code for using Imagemagick to create a montage of images. Whee!

Emacs kaizen: helm-swoop and editing

This entry is part 2 of 4 in the series Emacs Kaizen

Continuing on this quest to focus on one tiny little workflow change at a time, so that I can get even better at using Emacs…

One of those packages I installed but never got around to trying out was all, which lets you interactively edit all lines matching a given regular expression. It’s like an editable occur, sorta.

It turns out that helm-swoop lets you use C-c C-e to edit matching lines interactively (so you can use keyboard macros or replace-regexp or whatever). You can type C-x C-s to save it back to the buffer.

On a related note, I’m still tickled pink every time I use dired-toggle-read-only (C-x C-q) to make a Dired buffer editable so that I can batch-rename filenames.

Emacs kaizen: ace-isearch combines ace-jump-mode and helm-swoop

This entry is part 3 of 4 in the series Emacs Kaizen

I’m a fan of clever little things that change their behaviour depending on what you’re doing while letting you mentally think of it as just one function.

ace-isearch looks like it’ll be useful for collapsing three different functions into one mental thing in my head:

  • jumping to a specific character that I can see (ace-jump-mode)
  • searching through a buffer for a few characters (isearch)
  • doing helm-swoop to quickly preview and navigate through matching lines in a buffer

If you install the ace-isearch package and turn on global-ace-isearch-mode, then searching for a single character triggers ace-jump-mode, searching for less than 5 characters triggers isearch, and anything longer triggers helm-swoop-from-isearch. You can customize those thresholds, of course.

Neat!

Emacs: M-y as helm-show-kill-ring

This entry is part 1 of 4 in the series Emacs Kaizen

After realizing that I barely scratched the surface of Helm’s awesomeness (really, I basically use it as an ido-vertical-mode), I made a concerted effort to explore more of the interesting things in the Helm toolkit. helm-show-kill-ring is one such thing. I’ve bound it to M-y, which I had previously configured to be browse-kill-ring, but helm-show-kill-ring is much cooler because it makes it easy to dynamically filter your kill ring. Also, Kcode>M-y works better for me than C-y does because I know when I want the last thing I killed, but going beyond that is a little annoying.

That said, browse-kill-ring does make it easy to edit a kill ring entry. Maybe I should learn how to modify Helm’s behaviour so that I can add an edit action. There’s already a delete action. Besides, I haven’t used that feature in browse-kill-ring yet, so I can probably get by even without it.

ido fans: you can use helm-show-kill-ring without activating helm-mode, if you want.

On a related note, I like how rebinding M-x (execute-extended-comand) to helm-M-x shows me keybindings as I search for commands. You do have to get used to the quirk of typing C-u and other prefixes after M-x instead of before, but I haven’t had a problem with this yet. This is mostly because I haven’t dug into just how many commands do awesome things when given a prefix argument. I know about using C-u C-c C-w (org-refile) to jump to places instead of refiling notes, but that’s about it. I haven’t gone anywhere close to C-u C-u. Does anyone have a favourite command they use that does really smart things when given that prefix? =)

This Helm intro has animated GIFs and a few other useful commands. Check it out!

Emacs Basics: Call commands by name with M-x (with tips for better completion using ido or helm)

This entry is part 2 of 3 in the series Emacs Basics

Emacs has way too many keyboard shortcuts to memorize. Fortunately, you can call commands by name by typing M-x and the name of the command. M- stands for the Meta key. If your keyboard does not have a Meta key (and most don’t, these days), use Alt or Option. For example, on a PC keyboard, you can type Alt-x. Alternatively, you can replace Meta with ESC. M-x then becomes ESC x.

If you know the name of the command to execute, you can type it after M-x, and then press RET (the Return key, which is the same as the Enter key). For example, M-x find-file opens a file. M-x save-buffer saves the current file. You can use TAB to complete words. Use <up> and <down> to go through your command history.

What if you don’t know the name of the command to execute? You can use M-x apropos-command to search for the command using keywords. If you know the keyboard shortcut or you can find the command on a menu, you can also use M-x describe-key and then do the keyboard shortcut or select it from the menu.

If a command you execute has a keyboard shortcut, it will flash briefly at the bottom of your screen. For example:

You can run the command `find-file' with C-x C-f

Using TAB for completion can be a little slow. Here are two ways to make that and a whole lot of other things faster: ido and helm. To explore these approaches, you will need to add the MELPA package repository to your configuration. To set that up, add the following to the beginning of your ~/.emacs.d/init.el file.

(package-initialize)
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t)

Then use M-x eval-buffer to load the changes into your current Emacs, and use M-x package-refresh-contents to reload the list of packages.

Helm mode

This is what completion with Helm looks like:

2014-03-17 13_06_54-c__sacha_personal_organizer.org.png

Figure 2: Helm

Use M-x package-install to install the helm package. Then you can try it out with M-x helm-mode . After you start Helm mode, try M-x again. You can type in multiple words to search for a command, and you can use <up> and <down> to go through completions. Use M-p and M-n to go through your command history.

If you like it, here’s some code that you can add to your ~/.emacs.d/init.el file to load it automatically next time, and to tweak it for more convenience.

(require 'helm-config) 
(helm-mode 1)

Use M-x eval-buffer to load your changes.

If you change your mind and want to disable helm-mode, you can toggle it off with M-x helm-mode .

If you like how that works, you may want to (global-set-key (kbd "M-x") 'helm-M-x). If you do, you’ll be able to see keybindings when you call commands with M-x. Note that if you want to use a prefix argument (ex: C-u), you will need to do that after calling M-x instead of before.

Ido, ido-hacks, smex, ido-vertical-mode, and flx-ido

Ido is like Helm, but it takes a different approach. Here’s what this combination will get you:

2014-03-17 12_40_40-MELPA.png

Figure 1: ido, smex, ido-vertical-mode, and flx-ido

If you want to give this a try, remove or comment out (helm-mode 1) from your ~/.emacs.d/init.el (if you added it), and disable helm-mode if you still have it active from the previous section.

To set Ido up, use M-x package-install to install ido, smex, ido-vertical-mode, ido-hacks, and flx-ido.

After the packages are installed, add the following code to your ~/.emacs.d/init.el .

(ido-mode 1)
(require 'ido-hacks nil t)
(if (commandp 'ido-vertical-mode) 
    (progn
      (ido-vertical-mode 1)
      (setq ido-vertical-define-keys 'C-n-C-p-up-down-left-right)))
(if (commandp 'smex)
    (global-set-key (kbd "M-x") 'smex))
(if (commandp 'flx-ido-mode)
    (flx-ido-mode 1))

Use M-x eval-buffer to load your changes, then try M-x again. You should now have much better completion. You’ll be able to call commands by typing in part of their names. Use <up> and <down> to go through the completion options, and use <left> and <right> to go through your history.

Try it for a week. If you like it, keep it. If you don’t like it, try the Helm approach.

Other tips

When you learn keyboard shortcuts, try to remember the names of the commands as well. You can do that with C-h k (describe-key). For example, M-x calls the command execute-extended-command. That way, even if you forget the keyboard shortcut, you can call the command by name.

If you forget the name of the command and you don’t know the keyboard shortcut for it, you can look for it in the menus or in the help file. You can open the help file with C-h i (info). You can also use M-x apropos-command to search through the commands that you can call with M-x.

Make your own cheat sheet with frequently-used keyboard shortcuts and commands to help you learn more about Emacs. Good luck!

Emacs Basics: M-x

Emacs Basics: M-x

You can download the MP3 from archive.org.