an alternative commandline interface to notmuch

Peter John Hartman peterjohnhartman at gmail.com
Sat Oct 1 07:29:06 PDT 2011


Hi folks,

Rafa released slmenu, a dmenu-clone that runs on the commandline, and I
contributed a couple of patches (verticality and tokens) to make it work
nice with notmuch.  So far, I'm pretty happy.  To get slmenu:

hg clone http://hg.suckless.org/slmenu

You'll need to apply the patch at the bottom of this e-mail[1].
You'll also want to use the shell script (nmsl) at the bottom of this e-mail[2].

How it works:

Basically, it pipes output (after being mangled a bit) from notmuch search
into slmenu/dmenu (so you can select the thread) and then sends that output 
to mutt, either via mbox (if notmuch is hosted remote) or maildir (if local).
It also allows you to prepend an entry with v (tag viewed), t +tag (tag
tag), x (tag x-del).

All this will be at:

http://durandus.trilidun.org/durandus/code/notmuch-slmenu/
Best,
Peter



[1] token.patch to slmenu

diff -r 7896c4e3bf21 slmenu.c
--- a/slmenu.c	Thu Sep 29 12:45:34 2011 +0200
+++ b/slmenu.c	Sat Oct 01 10:11:29 2011 -0400
@@ -36,7 +36,7 @@
 static void   drawmenu(void);
 static char  *fstrstr(const char*, const char*);
 static void   insert(const char*, ssize_t);
-static void   match(int);
+static void   match(void); 
 static size_t nextrune(int);
 static void   readstdin(void);
 static int    run(void);
@@ -60,10 +60,10 @@
 
 void
 appenditem(Item *item, Item **list, Item **last) {
-	if(!*last)
+	if(*last)
+		(*last)->right = item;
+	else
 		*list = item;
-	else
-		(*last)->right = item;
 	item->left = *last;
 	item->right = NULL;
 	*last = item;
@@ -132,13 +132,17 @@
 	Item *item;
 	int rw;
 
+	fprintf(stderr, "\033[H"); 
+	fprintf(stderr, "\033[2J"); 
+
 	/* use default colors */
 	fprintf(stderr, "\033[0m");
+/*	setupterm(); 
+	putp(clear_screen);  */
 
 	/* place cursor in first column, clear it */
 	fprintf(stderr, "\033[0G");
 	fprintf(stderr, "\033[K");
-
 	if(prompt)
 		drawtext(prompt, promptw-4, C_Reverse);
 
@@ -188,9 +192,10 @@
 	if(n > 0)
 		memcpy(&text[cursor], str, n);
 	cursor += n;
-	match(n > 0 && text[cursor] == '\0');
+	match();
 }
 
+/*
 void
 match(int sub) {
 	size_t len = strlen(text);
@@ -231,6 +236,60 @@
 	curr = sel = matches;
 	calcoffsets();
 }
+*/
+
+void
+match(void) {
+	static char **tokv = NULL;
+	static int tokn = 0;
+
+	char buf[sizeof text], *s;
+	int i, tokc = 0;
+	size_t len;
+	Item *item, *lprefix, *lsubstr, *prefixend, *substrend;
+	
+	strcpy(buf, text);
+	for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " "))
+		if(++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
+			die("Can't realloc.");
+/*			fprintf("cannot realloc %u bytes\n", tokn * sizeof *tokv); */
+	len = tokc ? strlen(tokv[0]) : 0;
+	
+	matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
+	for(item = items; item && item->text; item++) {
+		for(i = 0; i < tokc; i++)
+			if(!fstrstr(item->text, tokv[i]))
+				break;
+		if (i != tokc)
+			continue;
+		if(!tokc || !fstrncmp(tokv[0], item->text, len+1))
+			appenditem(item, &matches, &matchend);
+		else if(!strncmp(tokv[0], item->text, len))
+			appenditem(item, &lprefix, &prefixend);
+		else
+			appenditem(item, &lsubstr, &substrend);
+	}
+	if(lprefix) {
+		if(matches) {
+			matchend->right = lprefix;
+			lprefix->left = matchend;
+		}
+		else
+			matches = lprefix;
+		matchend = prefixend;
+	}
+	if(lsubstr) {
+		if(matches) {
+			matchend->right = lsubstr;
+			lsubstr->left = matchend;
+		}
+		else
+			matches = lsubstr;
+		matchend = substrend;
+	}
+	curr = sel = matches;
+	calcoffsets();
+}
 
 size_t
 nextrune(int inc) {
@@ -371,7 +430,7 @@
 		case CONTROL('J'):
 			if(sel) strncpy(text, sel->text, sizeof text); /* Complete the input first, when hitting return */
 			cursor = strlen(text);
-			match(TRUE);
+			match();
 			drawmenu();
 			/* fallthrough */
 		case CONTROL(']'):
@@ -441,11 +500,11 @@
 				break;
 			strncpy(text, sel->text, sizeof text);
 			cursor = strlen(text);
-			match(TRUE);
+			match(); 
 			break;
 		case CONTROL('K'):
 			text[cursor] = '\0';
-			match(FALSE);
+			match(); 
 			break;
 		case CONTROL('U'):
 			insert(NULL, 0 - cursor);
@@ -514,7 +573,7 @@
 	lines=MIN(MAX(lines, 0), mh);
 	promptw=(prompt?textw(prompt):0);
 	inputw=MIN(inputw, mw/3);
-	match(FALSE);
+	match();
 	if(barpos!=0) resetline();
 	drawmenu();
 }
@@ -542,7 +601,7 @@
 			puts("slmenu, © 2011 slmenu engineers, see LICENSE for details");
 			exit(EXIT_SUCCESS);
 		}
-		else if(!strcmp(argv[i], "-i"))
+		else if(!strcmp(argv[i], "-i")) 
 			fstrncmp = strncasecmp;
 		else if(!strcmp(argv[i], "-t"))
 			barpos=1;

[2] nmsl shell script
#!/bin/sh
# Copyright (c) Peter John Hartman (peterjohnhartman at gmail dot com)
#
# * Permission to use, copy, modify, and distribute this software for any
# * purpose with or without fee is hereby granted, provided that the above
# * copyright notice and this permission notice appear in all copies.
# *
# * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
# * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
# * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

maildir="/tmp/maildir"
slmenu="slmenu -i -l 30 -t -p [vtx]:"
#slmenu="dmenu -i -l 30 -p [vtx]:"
notmuch="/usr/local/bin/notmuch"
use_maildir=1
usage() {
  cat << EOF
  usage: $0 options

  Example: nmsl `nmdate 3 days ago`..`nmdate yesterday` tag:inbox
  Example: nmsl tag:inbox and tag:unread

  nmdate = echo $(date +%s -d "$*")

  OPTIONS:
     -h      Show this message
     -s      Use ssh
EOF
}

while getopts "hsd:" OPTION
do
  case $OPTION in
    h) 
      usage
      exit 1
      ;;
    s)
      notmuch="ssh trillap /usr/local/bin/notmuch"
      use_maildir=0
      ;;
  esac
done

shift $((OPTIND-1))
search=$@

if [[ $search == "" ]]; then
  search="tag:inbox and tag:unread"
fi

res=$($notmuch search $search |\
  sed -r 's/([^ ]*) ([^[]*)([^]]*\]) ([^;]*); (.*) (\([^)]*\))/\2\5; \4 \3 \6 \1/' |\
  $slmenu)

if [[ $res != "" ]]; then
	option=$(echo $res | cut -d' ' -f1)

	case "$option" in
	  x) 
	  # delete
                thread=$(echo $res | awk -F' ' '{print $NF}')
	        notmuch tag -unread +x-del $thread
	        nmsl $*
	  ;;
	  t)
	  # tag;
                thread=$(echo $res | awk -F' ' '{print $NF}')
		notmuch tag $(echo $res | cut -d' ' -f2) -inbox $thread
		nmsl $*
	  ;;
	  v)
	  # quick -unread
                thread=$(echo $res | awk -F' ' '{print $NF}')
	        notmuch tag -unread $thread
	        nmsl $*
	  ;;
	  *)
                thread=$(echo $res | awk -F' ' '{print $NF}')
                clear
		if [[ $use_maildir -eq 1 ]]; then
	                rm -rf $maildir
        	        mkdir $maildir
                	mkdir $maildir/cur
	                mkdir $maildir/new
        	        mkdir $maildir/tmp
                	$notmuch search --output=files $thread | sed -e 's: :\\ :g' |\
                        	xargs --no-run-if-empty ln -s -t $maildir/cur/
	                mutt -f $maildir
		else
			$notmuch show --format=mbox $thread > /tmp/nmsearch.$$
			mutt -f /tmp/nmsearch.$$
			rm -f -- /tmp/nmsearch.$$
		fi
		nmsl $*
	  ;;
	esac
fi

clear







-- 
sic dicit magister P
University of Toronto / Fordham University
Collins Hall B06; Office Hours TF10-12
http://individual.utoronto.ca/peterjh
gpg  1024D/ED6EF59B  (7D1A 522F D08E 30F6 FA42  B269 B860 352B ED6E F59B)
gpg --keyserver pgp.mit.edu --recv-keys ED6EF59B


More information about the notmuch mailing list