elisp

Cancel all timers calling some function

This is a bit of a niche post but I recently found I had accidentally started a bunch of duplicate timers all running the same function. I hadn’t recorded the timer objects for them so couldn’t use cancel-timer to kill them. After a bit of searching I discovered the function cancel-function-timers which will cancel all timers that call a particular function.

Not something you’ll be using every day, but might be useful one day!

Advertisement

Quickly move a file to the current directory

Often I’ll download a file in my browser, and then want to move that file to the directory in which I am working in emacs. I wrote a little helper function to streamline this, called bjm/move-file-here, given below or at this github gist. Call the function and it will prompt you with a list of files in your starting directory (defaulting to ~/downloads, but configurable with bjm/move-file-here-start-dir) sorted to have the most recent first. The chosen file will then be moved to the current directory if you are in dired, or else the directory of the current buffer.

The function needs the packages dash.el and swiper installed. Here is the code – comments are welcome.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; move file here                                                         ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(require 'dash)
(require 'swiper)

;; start directory
(defvar bjm/move-file-here-start-dir (expand-file-name "~/downloads"))

(defun bjm/move-file-here ()
  "Move file from somewhere else to here.
The file is taken from a start directory set by `bjm/move-file-here-start-dir' and moved to the current directory if invoked in dired, or else the directory containing current buffer. The user is presented with a list of files in the start directory, from which to select the file to move, sorted by most recent first."
  (interactive)
  (let (file-list target-dir file-list-sorted start-file start-file-full)
    ;; clean directories from list but keep times
    (setq file-list
          (-remove (lambda (x) (nth 1 x))
                   (directory-files-and-attributes bjm/move-file-here-start-dir)))

    ;; get target directory
    ;; http://ergoemacs.org/emacs/emacs_copy_file_path.html
    (setq target-dir
          (if (equal major-mode 'dired-mode)
              (expand-file-name default-directory)
            (if (null (buffer-file-name))
                (user-error "ERROR: current buffer is not associated with a file.")
              (file-name-directory (buffer-file-name)))))

  ;; sort list by most recent
  ;;http://stackoverflow.com/questions/26514437/emacs-sort-list-of-directories-files-by-modification-date
  (setq file-list-sorted
        (mapcar #'car
                (sort file-list
                      #'(lambda (x y) (time-less-p (nth 6 y) (nth 6 x))))))

  ;; use ivy to select start-file
  (setq start-file (ivy-read
                    (concat "Move selected file to " target-dir ":")
                    file-list-sorted
                    :re-builder #'ivy--regex
                    :sort nil
                    :initial-input nil))

  ;; add full path to start file and end-file
  (setq start-file-full
        (expand-file-name start-file bjm/move-file-here-start-dir))
  (setq end-file
        (expand-file-name (file-name-nondirectory start-file) target-dir))
  (rename-file start-file-full end-file)
  (message "moved %s to %s" start-file-full end-file)))

Join line to following line

I wrote a while ago about using M-^ to join the current line to the previous line. In fact, when editing I find that I most often want to join a line to the following line. I used to do this by using C-e to move to the end of the line and then C-d to delete until I got the next line joined to the current one. It is easier to do this with a single key using a simple function, which I set to C-j. Add the following to your emacs config file:

;; join line to next line
(global-set-key (kbd "C-j")
            (lambda ()
                  (interactive)
                  (join-line -1)))

You can keep hitting C-j to keep joining the next line.

Update

I originally used M-j as the keybinding for this, but Kaushal Modi pointed out in the comments that C-j is a better choice.

Transpose characters

You can use C-t to run transpose-chars to switch the character at the cursor position with the previous character. Repeated uses have the effect of dragging the character behind the point to the right.

I have found that I prefer the behaviour to be that the previous two characters before the point are switched, as I usually want to do this if I spot I have just made a typo. This is what transpose-chars does if you are at the end of a line, but not if you are somewhere in the middle of the line. To modify the behaviour in this way, I use

;; adjust transpose-chars to switch previous two characters
(global-set-key (kbd "C-t")
                (lambda () (interactive)
                  (backward-char)
                  (transpose-chars 1)))

As an example, with the modified behaviour, using C-t with the point at the end of the string teh changes it to the, while the original behaviour gives you te h (unless you are at the end of a line, in which case you get the). Repeated use of the modified version simply toggles back and forth.

Fix touch-typing semicolon typo

I’ve started learning to touch type (for the nth time) and I find I make a lot of typos. Most are easily fixed with flyspell’s C-;, but I quite often hit ; instead of l, which flyspell can’t fix as it looks like the end of a word. I wrote a quick bit of code to replace the most recent ; with l

;;fix ; typos
(defun bjm-semicolon-to-l ()
  "Change the most recent semicolon behind the point to an l character. Useful for fixing a common touch-typing error"
  (interactive)
  (let ((bjm-start-pos (point)))
    (search-backward ";")
    (delete-char 1)
    (insert "l")
    (goto-char bjm-start-pos)))

(global-set-key (kbd "C-:") 'bjm-semicolon-to-l)

This is pretty simplistic, but it shows how easy it is to add a very personalised bit of extra functionality to emacs with minimal lisp skills!

Update

Grant Rettke pointed out in the comments that it is simpler to handle moving the point back to where you started by using save-excursion. The updated code is:

;;fix ; typos
(defun bjm-semicolon-to-l ()
  "Change the most recent semicolon behind the point to an l character. Useful for fixing a common touch-typing error"
  (interactive)
  (save-excursion
    (search-backward ";")
    (delete-char 1)
    (insert "l")))

Thanks Grant!

Super spotlight search with counsel

Inspired by abo-abo‘s post on using his excellent tools counsel, ivy and swiper to search the contents of files indexed by the recoll search tool, I tried to make something similar for spotlight on the Mac. My attempt is below.

Using counsel gives us incremental updates of the spotlight search results (accessed using its command line interface mdfind). When a match is selected, it is opened in emacs, and (unless it is a pdf) a swiper search is launched on the search string.

This works really nicely. The only problem I’ve had is that I wanted to sort the results to prioritise .org and .tex files, but my sort function is not being used correctly in ivy, but I can’t tell why. It gets passed the counsel prompt for more characters, but not the set of mdfind matches for sorting. My lisp skills have been exhausted at this point, but maybe someone else can see what I’ve done wrong! UPDATE: this problem was fixed with an update to ivy and counsel, and the sorting command below now works.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; counsel-spotlight                                                      ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Incrementally search the Mac spotlight database and open matching
;; files with a swiper search on the query text.
;; Based on http://oremacs.com/2015/07/27/counsel-recoll/

(require 'counsel)

;; Function to be called by counsel-spotlight
;; The onlyin option limits results to my home directory
;; and directories below that
;; mdfind is the command-line interface to spotlight
(defun counsel-mdfind-function (string &rest _unused)
  "Issue mdfind for STRING."
  (if (< (length string) 4)
      (counsel-more-chars 4)
    (counsel--async-command
     (format "mdfind -onlyin ~/ '%s'" string))
    nil))

;; Main function
(defun counsel-spotlight (&optional initial-input)
  "Search for a string in the mdfind database.
You'll be given a list of files that match.
Selecting a file will launch `swiper' for that file.
INITIAL-INPUT can be given as the initial minibuffer input."
  (interactive)
  (ivy-read "spotlight: " 'counsel-mdfind-function
            :initial-input initial-input
            :dynamic-collection t
            :sort t
            :action (lambda (x)
                      (when (string-match "\\(\/.*\\)\\'" x)
                        (let ((file-name (match-string 1 x)))
                          (find-file file-name)
                          (unless (string-match "pdf$" x)
                            (swiper ivy-text)))))))

;; Define my sort function
(defun bjm-counsel-mdfind-sort-function (x y)
  "Compare two files X and Y. Prioritise org then tex."
  (if (string-match "org$" x)
      t
    (if (string-match "tex$" x)
        (if (string-match "org$" y)
            nil
          t)
      nil)))

;; Add to list of ivy sorting functions
(add-to-list 'ivy-sort-functions-alist
             '(counsel-mdfind-function . bjm-counsel-mdfind-sort-function))

In an ideal world, I’d like to be able to interactively narrow the matches from mdfind with a second counsel on the filenames. The selected file would then open with a swiper search for the first search term. For example, I would like to

  1. Call M-x counsel-spotlight and enter caustic (or enough characters to give me useful results) to get a list of names of files which contain the text caustic somewhere inside them.
  2. Hit some keybinding and get a new counsel prompt and enter chandra to incrementally filter the list of filenames to those containing the string chandra in the filename.
  3. Select the file I want and hit RET to have emacs open that with a swiper search of my original query caustic.

Given the state of my lisp skills, this might take me a while, but it is nice to dream!

Update: A cheat to sort/filter results

Given that my perl skills are much better than my lisp skills, I cheated and wrote a perl wrapper for mdfind that filters and sorts the results for me. The code is below – feel free to use and modify and share. To use this, save the code to a file called bjm-mdfind in your $PATH, make sure it has executable permissions set, and modify the lisp code above to use it:

;; Function to be called by counsel-spotlight
;;
;; mdfind is the command-line interface to spotlight
;;
;; The onlyin option limits results to my home directory
;; and directories below that
;;
;; N.B. below this is replaced with a custom perl wrapper to sort
;; and filter the mdfind results
(defun counsel-mdfind-function (string &rest _unused)
  "Issue mdfind for STRING."
  (if (< (length string) 4)
      (counsel-more-chars 4)
    (counsel--async-command
     (format "bjm-mdfind '%s'" string))
     ;;(format "mdfind -onlyin ~/ '%s'" string))
    nil))

Here is the perl code:

#!/usr/bin/perl -w

###############################################################################
# bjm-mdfind
#
# wrapper for mdfind to filter and sort results
# written by Ben Maughan http://pragmaticemacs.com/
#
# $Id: bjm-mdfind,v 1.2 2015/08/06 11:21:37 bjm Exp $
###############################################################################

use strict;
use File::Basename;
use Getopt::Long;

my $scriptname = basename($0); # Strip away the leading directory names
my $runtime = localtime;

##########################################################################
# customise these options                                                #
##########################################################################
# limit search to this dir (recursively)
my $dir="~/";
# preferred order of file extensions
# others will appear later
my @order=(".org",".tex",".el",".txt",".dat",".pdf");
# exclude files matching these strings
my @exclude=("Library/Caches","Application Support");

###############################################################################
# Handle command line arguments

my $help;
my $man;
my $v=0; #set default verbosity
my $version = defined ((split / /, q/$Revision: 1.2 $/)[1]) ? (split / /, q/$Revision: 1.2 $/)[1] : 0;
my @args=@ARGV;
my $nargs=1; #number of required command line args

&Getopt::Long::Configure( 'bundling' );
GetOptions(
           'help|h' => \$help,
           'man|m'  => \$man,
           'verbose|v=i' => \$v,
) or die "ERROR: Invalid command line option $!";

if ($help||$man||$#ARGV<$nargs-1) { #print help
  # Load Pod::Usage only if needed.
  require "Pod/Usage.pm";
  import Pod::Usage;
  pod2usage(VERBOSE => 1) if $help;
  pod2usage(VERBOSE => 2) if $man;
  pod2usage(VERBOSE => 0, -message => "ERROR: not enough arguments use --help or --man for more help") if $#ARGV<0;
  pod2usage(VERBOSE => 0, -message => "ERROR: not enough arguments - your input was:\n    $scriptname @args") if $#ARGV<$nargs-1;
}

#check input
my $string=$ARGV[0];

###############################################################################
# Main part of program

if ($v > 0) {
  print <<EOF

------------------------$scriptname version $version------------------

Invocation was:
$scriptname @args

Runtime $runtime

EOF
}

chomp(my @out=`mdfind -onlyin $dir $string`);

## sort and filter
##print "$order[2]\n\n";

##filter
my $exc=join "|", @exclude;
@out = grep !/$exc/, @out;

##sort
my @out2;
foreach my $ext (@order) {
    my @tmp = grep /$ext$/, @out;
    print "###$ext\n###@tmp\n\n" if $v>1;
    push @out2, @tmp;
}
##everything else
my $inv=join "\$|", @order;
print "###$inv\n" if $v>1;
my @rest = grep !/$inv$/, @out;
print "###@rest\n" if $v>1;
push @out2, @rest;

##join
my $out=join "\n", @out2;

##print
print "$out\n";

#report successful completion
print "$scriptname completed successfully\n\n" if $v>0;

###############################################################################
# POD documentation

=head1 NAME

bjm-mdfind

=head1 SYNOPSIS

B<bjm-mdfind> [options] string

=head1 DESCRIPTION

wrapper for mdfind to filter and sort results

  string        - search query

=head1 OPTIONS

=over 4

=item B<-h, --help>

Prints out a brief help message.

=item B<-m, --man>

Prints out detailed help.

=item B<-v, --verbosity>

Control the amount of output B<(Default = 1)>

=back

=head1 VERSION

This is $RCSfile: bjm-mdfind,v $ $Revision: 1.2 $

=head1 AUTHOR

Ben Maughan <benmaughan@gmail.com>

=cut

###############################################################################
# $Log: bjm-mdfind,v $
# Revision 1.2  2015/08/06 11:21:37  bjm
# Summary: added URL
#
# Revision 1.1  2015/08/06 11:17:37  bjm
# Initial revision
#

Tweaking deft: no spaces in file names

Building on my earlier posts on deft, I have another minor improvement I wanted to make. I really like the way deft creates files from the search string, generating the file name from the string. The only problem is that I don’t want spaces in my file names, which I may well have in my deft search string.

My solution was to add the following advice to the function in deft that creates new files, telling it to replace spaces in the search string with hyphens before creating the file.

;;advise deft-new-file-named to replace spaces in file names with -
(defun bjm-deft-strip-spaces (orig-fun &rest file)
  ;;this probably doesn't need to be done in three steps!
  (setq name (pop file))
  (setq name (replace-regexp-in-string " " "-" name))
  (push name file)
  (apply orig-fun file)
  )

(advice-add 'deft-new-file-named :around #'bjm-deft-strip-spaces)

Maybe someone can help me improve my novice lisp programming here, as I am sure there must be a more direct way to use replace-regexp-in-string here!

I think this is a nice illustration of how the user can relatively easily adjust the behaviour of a function which is quite buried inside a package. You have to be a bit careful when advising functions that you don’t introduce unwanted behaviour if that function is called in a different context, but in this case it should be safe as the function deft-new-file-named is not used elsewhere.

In a comment on my earlier post Kaushal Modi linked to his solution to this, which also removes any upper cases.

Update

The wise commenters below pointed out that my function above is a bit dangerous, since the variable name that I use has global scope so could modify another variable called name elsewhere. This can be solved by using (let ...) to give the variable local scope, as Kaushal Modi points out, but I ended up going with the compact version suggested by Noam Postavsky:

;;advise deft-new-file-named to replace spaces in file names with -
(defun bjm-deft-strip-spaces (args)
  "Replace spaces with - in the string contained in the first element of the list args. Used to advise deft's file naming function."
  (list (replace-regexp-in-string " " "-" (car args)))
  )
(advice-add 'deft-new-file-named :filter-args #'bjm-deft-strip-spaces)

Which uses filter-args in the advice function to specify that my bjm-quit-strip-spaces function is applied to the arguments to deft-new-file-named before they are passed to the latter function.

Tweaking deft: improving navigation

Following up on my earlier posts on deft, I had another small tweak I wanted to make, that also illustrates a useful technique for customised Emacs.

In the deft buffer, each entry takes two lines so I have to hit the up/down arrow key twice to move between them. To simplify this, I tweaked the keybindings in deft-mode to make the arrow keys jump 2 lines instead of 1, but only when deft-mode is active:

;;override normal settings to jump 2 lines as deft has time stamps on second line
(define-key deft-mode-map (kbd "<down>")
  (lambda () (interactive) (next-line 2)))
(define-key deft-mode-map (kbd "<up>")
  (lambda () (interactive) (next-line -2)))

Here, lambda is the name used for anonymous functions in emacs lisp, which are useful in cases like this where I want to make a simple function that I won’t need to use anywhere else. The function is very simple, it just calls the normal line movement command next-line with an argument of 2 or -2 to skip up or down by 2 lines. We then define the arrow keys to call this function, but only in deft-mode-map, which contains the keybindings for deft-mode.

Touch-typists (which, to my shame I am not) generally prefer to use C-n and C-p to move up and down lines, so those could be used instead here.

Update

Commenter Kaushal Modi pointed out that I should not have been seeing the entries in the deft buffer taking up more than one line. It turns out the problem was that I had global-visual-line-mode switched on on my emacs config file which caused the problem.

This means the above changes are redundant, but I’ll leave them up as an example of adding keybindings to a specific mode.

Tweaking deft: quicker notes

I posted recently about using deft to make quick notes, and after using it for a bit I like it a lot, but wanted to make a few tweaks to the way it works. This gave me an excuse to learn a few lisp techniques, which other lisp novices might find useful.

I really like the way that org-capture lets me quickly make a note and return me seamlessly to where I was before, and so I wanted deft to be a bit more like that. By default, if I launch deft and make a note, I have to:

  • save the buffer
  • kill the buffer which takes me back to the deft menu
  • quit deft

This is too much work. Okay, I could save the note buffer and then switch back to my original buffer using e.g. winner-undo but that is still too much work!

Instead I’ve dabbled in a bit of lisp coding which I think illustrates a few nice ways you can customise your emacs with minimal lisp skills (like mine).

To start with I made my first attempt to advise a function. This is a way to make a function built into emacs or a package behave differently. Here I advise deft to save my window configuration before it launches:

;;advise deft to save window config
(defun bjm-deft-save-windows (orig-fun &rest args)
  (setq bjm-pre-deft-window-config (current-window-configuration))
  (apply orig-fun args)
  )

(advice-add 'deft :around #'bjm-deft-save-windows)

Side note: in principal, I think something similar could be done using hooks, but my reading of the deft code suggested that the hooks would run after the window configuration had been changed, which is not what I wanted.

I then make a function to save the current buffer, kill the current buffer, kill the deft buffer, and then restore the pre-deft configuration. I then set up a shortcut for this function.

;;function to quit a deft edit cleanly back to pre deft window
(defun bjm-quit-deft ()
  "Save buffer, kill buffer, kill deft buffer, and restore window config to the way it was before deft was invoked"
  (interactive)
  (save-buffer)
  (kill-this-buffer)
  (switch-to-buffer "*Deft*")
  (kill-this-buffer)
  (when (window-configuration-p bjm-pre-deft-window-config)
    (set-window-configuration bjm-pre-deft-window-config)
    )
  )

(global-set-key (kbd "C-c q") 'bjm-quit-deft)

So now, I can launch deft with C-c d make a quick note and then quit with C-c q to get back to where I was. This is pleasingly close to the experience of org-capture.

Note that bjm-quit-deft is not bullet proof; there is nothing to stop you running it in a buffer that is not a note opened from deft, but if you do, nothing terrible will happen. If I was a better lisp programmer I could probably come up with a way to test if the current buffer was opened from deft and issue a warning from bjm-quit-deft if not, but I am not much of a lisp programmer!

More to follow on tweaking deft…