Month: September 2016

A better shortcut for delete-frame

Following on from my post on using C-x k to kill the current buffer, why not bind C-x w to delete-frame in order close the current emacs “window” (remember, in Emacs-speak, a frame is what most other apps would call a window). This is way better than the default C-x 5 0

Just add the following to your emacs config file

(global-set-key (kbd "C-x w") 'delete-frame)

Don’t kill-buffer, kill-this-buffer instead

Here’s a tiny tip. By default C-x k runs the command kill-buffer which prompts you for which buffer you want to kill, defaulting to the current active buffer. I don’t know about you, but I rarely want to kill a different buffer than the one I am looking at, so I rebind C-x k to kill-this-buffer which just kills the current buffer without prompting (unless there are unsaved changes).

Just add this to your emacs config file

(global-set-key (kbd "C-x k") 'kill-this-buffer)

Update 2017-04-24

Commenter thbit points out that using kill-this-buffer in this way is unstable (see discussion here). Here is a safer alternative.

(defun bjm/kill-this-buffer ()
  "Kill the current buffer."
  (interactive)
  (kill-buffer (current-buffer)))

(global-set-key (kbd "C-x k") 'bjm/kill-this-buffer)

Star and unstar articles in elfeed

Following on from a couple of previous posts on elfeed, the excellent feed reader for Emacs, I wanted to share some code to enable the starring and unstarring of articles for future reference.

This code is closely based on this article, so all credit should go in that direction. My only addition is to add the ability to unstar an article.

;; code to add and remove a starred tag to elfeed article
;; based on http://matt.hackinghistory.ca/2015/11/22/elfeed/

;; add a star
(defun bjm/elfeed-star ()
  "Apply starred to all selected entries."
  (interactive )
  (let* ((entries (elfeed-search-selected))
         (tag (intern "starred")))

    (cl-loop for entry in entries do (elfeed-tag entry tag))
    (mapc #'elfeed-search-update-entry entries)
    (unless (use-region-p) (forward-line))))

;; remove a start
(defun bjm/elfeed-unstar ()
  "Remove starred tag from all selected entries."
  (interactive )
  (let* ((entries (elfeed-search-selected))
         (tag (intern "starred")))

    (cl-loop for entry in entries do (elfeed-untag entry tag))
    (mapc #'elfeed-search-update-entry entries)
    (unless (use-region-p) (forward-line))))

;; face for starred articles
(defface elfeed-search-starred-title-face
  '((t :foreground "#f77"))
  "Marks a starred Elfeed entry.")

(push '(starred elfeed-search-starred-title-face) elfeed-search-face-alist)

This code sets up the required functions to add and remove the “starred” tag from articles, and sets a face colour to indicate articles that have been starred.

I bind these to the keys “*” to add a star and “8” to remove the star (easy to remember):

;; add keybindings
(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map (kbd "*") 'bjm/elfeed-star))
(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map (kbd "8") 'bjm/elfeed-unstar))

Now, entering the search filter “+starred” in elfeed will show the starred articles, and as described previously you can save a bookmark to that filter (I called mine “elfeed-starred”) and assign a key like “S” to jump directly to the starred articles:

;;shortcut to jump to starred bookmark
(defun bjm/elfeed-show-starred ()
  (interactive)
  (bookmark-jump "elfeed-starred"))
(define-key elfeed-search-mode-map (kbd "S") 'bjm/elfeed-show-starred)

Update 2016-09-27

Commenter Galrog has a much simpler implementation. I’ve copied it here so the formatting is nicer:

(defalias 'elfeed-toggle-star
  (elfeed-expose #'elfeed-search-toggle-all 'star))

(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map (kbd "m") 'elfeed-toggle-star))

Counsel-yank-pop with a tweak

Abo Abo’s counsel package (part of swiper/ivy) puts together a set of replacements for Emacs commands that leverages the power of the ivy completion library.

One of my favourites is counsel-yank-pop which replaces the standard clipboard history (kill-ring in Emacs terminology) with an ivy-powered version. You can then type search strings to filter your clipboard history dynamically.

I use the following code to configure counsel-yank-pop to replace the standard yank-pop on M-y. The only thing I missed about the vanilla yank-pop was that repeatedly pressing M-y cycled through the entries. The counsel version doesn’t do this by default but this is easy to add by binding M-y to ivy-next-line in the ivy-minibuffer-map.

(use-package counsel
  :bind
  (("M-y" . counsel-yank-pop)
   :map ivy-minibuffer-map
   ("M-y" . ivy-next-line)))

Automatically copy text selected with the mouse

I’m not much of a mouse user, but a colleague using emacs for OS X wanted to replicate the normal X11 behaviour that linux users are familiar with – text selected with the mouse is automatically copied to the system clipboard. It turns out to be as easy as adding one line to your emacs config file:

(setq mouse-drag-copy-region t)

From the help for the variable (C-h v mouse-drag-copy-region)

If non-nil, copy to kill-ring upon mouse adjustments of the region.

In other words, highlight a block of text and it is copied to the kill-ring (Emacs’ internal clipboard). This also copies to the system clipboard on the Mac so you can then paste to other apps.

Even better email contact completion in mu4e

I have written previously about my tweaks to improve contact completion when composing emails with mu4e. Thanks to some help from abo-abo, the author of the fantastic ivy completion library, with the code below you can hit a comma to complete the current choice of email address and start searching for the next one. This matches the behaviour of many other email clients like Gmail or Thunderbird.

This won’t change anybody’s world, but gives you a nice little thrill of efficiency when entering several recipients to an email!

Here is the updated code (see my previous post for more details):

;;need this for hash access
(require 'subr-x)

;;my favourite contacts - these will be put at front of list
(setq bjm/contact-file "/homeb/bjm/docs/fave-contacts.txt")

(defun bjm/read-contact-list ()
  "Return a list of email addresses"
  (with-temp-buffer
    (insert-file-contents bjm/contact-file)
    (split-string (buffer-string) "\n" t)))

;; code from https://github.com/abo-abo/swiper/issues/596
(defun bjm/counsel-email-action (contact)
  (with-ivy-window
    (insert contact)))

;; bind comma to launch new search
(defvar bjm/counsel-email-map
  (let ((map (make-sparse-keymap)))
    (define-key map "," 'bjm/counsel-email-more)
    map))

(defun bjm/counsel-email-more ()
  "Insert email address and prompt for another."
  (interactive)
  (ivy-call)
  (with-ivy-window
    (insert ", "))
  (delete-minibuffer-contents)
  (setq ivy-text ""))

;; ivy contacts
;; based on http://kitchingroup.cheme.cmu.edu/blog/2015/03/14/A-helm-mu4e-contact-selector/
(defun bjm/ivy-select-and-insert-contact (&optional start)
  (interactive)
  ;; make sure mu4e contacts list is updated - I was having
  ;; intermittent problems that this was empty but couldn't see why
  (mu4e~request-contacts)
  (let ((eoh ;; end-of-headers
         (save-excursion
           (goto-char (point-min))
           (search-forward-regexp mail-header-separator nil t)))
        ;; append full sorted contacts list to favourites and delete duplicates
        (contacts-list
         (delq nil (delete-dups (append (bjm/read-contact-list) (mu4e~sort-contacts-for-completion (hash-table-keys mu4e~contacts)))))))

    ;; only run if we are in the headers section
    (when (and eoh (> eoh (point)) (mail-abbrev-in-expansion-header-p))
      (let* ((end (point))
           (start
            (or start
                (save-excursion
                  (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
                  (goto-char (match-end 0))
                  (point))))
           (initial-input (buffer-substring-no-properties start end)))

      (delete-region start end)

      (ivy-read "Contact: "
                contacts-list
                :re-builder #'ivy--regex
                :sort nil
                :initial-input initial-input
                :action 'bjm/counsel-email-action
                :keymap bjm/counsel-email-map)
      ))))