Friday 17 November 2017

Emacs interface to Recoll

Lately I'm using Recoll (a desktop full-text search tool), https://www.lesbonscomptes.com/recoll/, which comes with a GUI, but it also can be used from a terminal. I wanted to be able to use it from within Emacs and I found two existing ways:


  1. helm-recoll https://github.com/emacs-helm/helm-recoll
  2. counsel-recoll, bundled with swiper https://github.com/abo-abo/swiper
I already use Ivy/Swiper/Counsel, which is very nice, so route 1) didn't appeal much to me: I would have to install Helm just to get Emacs-Recoll interaction and perhaps suffer some problems by having Helm and Ivy together.

Route 2) was not very nice either because counsel-recoll will interactively search the database for matches and it will show in the mini-buffer the names of the files whose contents match the given search so you can quickly open them, but you can not see any context in the files around the search you are trying to do.

So I ended up writing a small functon (ag-recoll), that suits me better. I can give a number of words, which recoll will use to do a search, and then only on those files matching, I will call ag, which will create a buffer with all the places in the files where the given words match. The given buffer is a "compilation" buffer, so the lines are hyperlinks, which can be easily followed to open the file at the place of interest.

The function is as simple as:


(defun ag-recoll (string directory)
  "Search using ag based on the findings of recoll. Search will be done in a given DIRECTORY, and the STRING will be interpreted as concatenated by ANDs for recoll and with ORs for ag.

   The idea is that when I search, for example, for 'openacc mpi', recoll will give me all the files that have those two words somewhere in the file, and ag will find lines that match any of the terms.

   For the moment this is very crude, and most options to recoll and ag are hard-coded in the ag-recoll.sh script, most notably that ag will look for a maximum of 10 matches in each file to avoid huge lists with common searches."

  (interactive (list (ag/read-from-minibuffer "Search string")
                   (read-directory-name "Directory: ")))
  (setq command-string (format "%s %s %s" "/home/angelv/localhacks/ag-recoll.sh" directory string))
  (setq regexp nil)
  (compilation-start
   command-string
   #'ag-mode
   `(lambda (mode-name) ,(ag/buffer-name string directory regexp))))

which uses the ag-recoll.sh script:

#!/bin/bash
dir=$1; shift
ors=$(printf '%s|' address@hidden)
recoll -t -b $@ dir:$dir | sed -e "s/file:\/\///" | xargs -d '\n' ag --max-count 10 --group --line-number column --color --color-match 30\;43 --color-path 1\;32 --smart-case ${ors%|}


So, as an example, when I call ag-recoll with STRING 'openacc pgi' and DIRECTORY /home/angelv/Learning in my workstation, the result is like this:



where each line is a hyperlink to the corresponding match in the file and you can use the common grep-mode commands to open a file, go to the next match C-x `, etc.

This is useful already as it is, but I will try to make it more flexible (being able to pass arguments to recoll and ag, etc. [I'm a total noob with Emacs Lisp, so any suggestions on how to improve the code are very welcome].

2 comments:

Anonymous said...

Hi,
I'm interested in your script.
For now,
ag/read-from-minibuffer and ag/buffer-name string directory regexp

are not defined ;)

Anonymous said...

........... please mail me à xavxxav [at] gmail [dot] com