unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
From: Austin Clements <amdragon@MIT.EDU>
To: notmuch@notmuchmail.org
Cc: tomi.ollila@iki.fi
Subject: [PATCH v3 8/9] emacs: Switch from text to JSON format for search results
Date: Mon,  9 Jul 2012 17:42:41 -0400	[thread overview]
Message-ID: <1341870162-17782-9-git-send-email-amdragon@mit.edu> (raw)
In-Reply-To: <1341870162-17782-1-git-send-email-amdragon@mit.edu>

The JSON format eliminates the complex escaping issues that have
plagued the text search format.  This uses the incremental JSON parser
so that, like the text parser, it can output search results
incrementally.

This slows down the parser by about ~4X, but puts us in a good
position to optimize either by improving the JSON parser (evidence
suggests this can reduce the overhead to ~40% over the text format) or
by switching to S-expressions (evidence suggests this will more than
double performance over the text parser).  [1]

This also fixes the incremental search parsing test.

This has one minor side-effect on search result formatting.
Previously, the date field was always padded to a fixed width of 12
characters because of how the text parser's regexp was written.  The
JSON format doesn't do this.  We could pad it out in Emacs before
formatting it, but, since all of the other fields are variable width,
we instead fix notmuch-search-result-format to take the variable-width
field and pad it out.  For users who have customized this variable,
we'll mention in the NEWS how to fix this slight format change.

[1] id:"20110720205007.GB21316@mit.edu"
---
 emacs/notmuch.el |  110 +++++++++++++++++++++++++++++++-----------------------
 test/emacs       |    1 -
 2 files changed, 64 insertions(+), 47 deletions(-)

diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index dfeaf35..fabb7c0 100644
--- a/emacs/notmuch.el
+++ b/emacs/notmuch.el
@@ -60,7 +60,7 @@
 (require 'notmuch-message)
 
 (defcustom notmuch-search-result-format
-  `(("date" . "%s ")
+  `(("date" . "%12s ")
     ("count" . "%-7s ")
     ("authors" . "%-20s ")
     ("subject" . "%s ")
@@ -557,17 +557,14 @@ This function advances the next thread when finished."
   (notmuch-search-tag '("-inbox"))
   (notmuch-search-next-thread))
 
-(defvar notmuch-search-process-filter-data nil
-  "Data that has not yet been processed.")
-(make-variable-buffer-local 'notmuch-search-process-filter-data)
-
 (defun notmuch-search-process-sentinel (proc msg)
   "Add a message to let user know when \"notmuch search\" exits"
   (let ((buffer (process-buffer proc))
 	(status (process-status proc))
 	(exit-status (process-exit-status proc))
 	(never-found-target-thread nil))
-    (if (memq status '(exit signal))
+    (when (memq status '(exit signal))
+	(kill-buffer (process-get proc 'parse-buf))
 	(if (buffer-live-p buffer)
 	    (with-current-buffer buffer
 	      (save-excursion
@@ -577,8 +574,6 @@ This function advances the next thread when finished."
 		  (if (eq status 'signal)
 		      (insert "Incomplete search results (search process was killed).\n"))
 		  (when (eq status 'exit)
-		    (if notmuch-search-process-filter-data
-			(insert (concat "Error: Unexpected output from notmuch search:\n" notmuch-search-process-filter-data)))
 		    (insert "End of search results.")
 		    (unless (= exit-status 0)
 		      (insert (format " (process returned %d)" exit-status)))
@@ -758,45 +753,59 @@ non-authors is found, assume that all of the authors match."
     (insert (apply #'format string objects))
     (insert "\n")))
 
+(defvar notmuch-search-process-state nil
+  "Parsing state of the search process filter.")
+
+(defvar notmuch-search-json-parser nil
+  "Incremental JSON parser for the search process filter.")
+
 (defun notmuch-search-process-filter (proc string)
   "Process and filter the output of \"notmuch search\""
-  (let ((buffer (process-buffer proc)))
-    (if (buffer-live-p buffer)
-	(with-current-buffer buffer
-	    (let ((line 0)
-		  (more t)
-		  (inhibit-read-only t)
-		  (string (concat notmuch-search-process-filter-data string)))
-	      (setq notmuch-search-process-filter-data nil)
-	      (while more
-		(while (and (< line (length string)) (= (elt string line) ?\n))
-		  (setq line (1+ line)))
-		(if (string-match "^thread:\\([0-9A-Fa-f]*\\) \\([^][]*\\) \\[\\([0-9]*\\)/\\([0-9]*\\)\\] \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line)
-		    (let* ((thread-id (match-string 1 string))
-			   (tags-str (match-string 7 string))
-			   (result (list :thread thread-id
-					 :date_relative (match-string 2 string)
-					 :matched (string-to-number
-						   (match-string 3 string))
-					 :total (string-to-number
-						 (match-string 4 string))
-					 :authors (match-string 5 string)
-					 :subject (match-string 6 string)
-					 :tags (if tags-str
-						   (save-match-data
-						     (split-string tags-str))))))
-		      (if (/= (match-beginning 0) line)
-			  (notmuch-search-show-error
-			   (substring string line (match-beginning 0))))
-		      (notmuch-search-show-result result)
-		      (set 'line (match-end 0)))
-		  (set 'more nil)
-		  (while (and (< line (length string)) (= (elt string line) ?\n))
-		    (setq line (1+ line)))
-		  (if (< line (length string))
-		      (setq notmuch-search-process-filter-data (substring string line)))
-		  ))))
-      (delete-process proc))))
+  (let ((results-buf (process-buffer proc))
+	(parse-buf (process-get proc 'parse-buf))
+	(inhibit-read-only t)
+	done)
+    (if (not (buffer-live-p results-buf))
+	(delete-process proc)
+      (with-current-buffer parse-buf
+	;; Insert new data
+	(save-excursion
+	  (goto-char (point-max))
+	  (insert string)))
+      (with-current-buffer results-buf
+	(while (not done)
+	  (condition-case nil
+	      (case notmuch-search-process-state
+		((begin)
+		 ;; Enter the results list
+		 (if (eq (notmuch-json-begin-compound
+			  notmuch-search-json-parser) 'retry)
+		     (setq done t)
+		   (setq notmuch-search-process-state 'result)))
+		((result)
+		 ;; Parse a result
+		 (let ((result (notmuch-json-read notmuch-search-json-parser)))
+		   (case result
+		     ((retry) (setq done t))
+		     ((end) (setq notmuch-search-process-state 'end))
+		     (otherwise (notmuch-search-show-result result)))))
+		((end)
+		 ;; Any trailing data is unexpected
+		 (notmuch-json-eof notmuch-search-json-parser)
+		 (setq done t)))
+	    (json-error
+	     ;; Do our best to resynchronize and ensure forward
+	     ;; progress
+	     (notmuch-search-show-error
+	      "%s"
+	      (with-current-buffer parse-buf
+		(let ((bad (buffer-substring (line-beginning-position)
+					     (line-end-position))))
+		  (forward-line)
+		  bad))))))
+	;; Clear out what we've parsed
+	(with-current-buffer parse-buf
+	  (delete-region (point-min) (point)))))))
 
 (defun notmuch-search-tag-all (&optional tag-changes)
   "Add/remove tags from all messages in current search buffer.
@@ -899,10 +908,19 @@ Other optional parameters are used as follows:
 	(let ((proc (start-process
 		     "notmuch-search" buffer
 		     notmuch-command "search"
+		     "--format=json"
 		     (if oldest-first
 			 "--sort=oldest-first"
 		       "--sort=newest-first")
-		     query)))
+		     query))
+	      ;; Use a scratch buffer to accumulate partial output.
+	      ;; This buffer will be killed by the sentinel, which
+	      ;; should be called no matter how the process dies.
+	      (parse-buf (generate-new-buffer " *notmuch search parse*")))
+	  (set (make-local-variable 'notmuch-search-process-state) 'begin)
+	  (set (make-local-variable 'notmuch-search-json-parser)
+	       (notmuch-json-create-parser parse-buf))
+	  (process-put proc 'parse-buf parse-buf)
 	  (set-process-sentinel proc 'notmuch-search-process-sentinel)
 	  (set-process-filter proc 'notmuch-search-process-filter)
 	  (set-process-query-on-exit-flag proc nil))))
diff --git a/test/emacs b/test/emacs
index 293b12a..afe35ba 100755
--- a/test/emacs
+++ b/test/emacs
@@ -36,7 +36,6 @@ test_emacs '(notmuch-search "tag:inbox")
 test_expect_equal_file OUTPUT $EXPECTED/notmuch-search-tag-inbox
 
 test_begin_subtest "Incremental parsing of search results"
-test_subtest_known_broken
 test_emacs "(ad-enable-advice 'notmuch-search-process-filter 'around 'pessimal)
 	    (ad-activate 'notmuch-search-process-filter)
 	    (notmuch-search \"tag:inbox\")
-- 
1.7.10

  parent reply	other threads:[~2012-07-09 21:42 UTC|newest]

Thread overview: 66+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-07-03 22:20 [PATCH 0/8] JSON-based search-mode Austin Clements
2012-07-03 22:20 ` [PATCH 1/8] emacs: Clean up notmuch-search-show-result Austin Clements
2012-07-04  7:53   ` Mark Walters
2012-07-04 16:22     ` Austin Clements
2012-07-04 16:31       ` Tomi Ollila
2012-07-04 20:47   ` Mark Walters
2012-07-04 21:00     ` Austin Clements
2012-07-03 22:20 ` [PATCH 2/8] emacs: Separate search line parsing and display Austin Clements
2012-07-03 22:20 ` [PATCH 3/8] emacs: Move search-target logic to `notmuch-search-show-result' Austin Clements
2012-07-04  8:34   ` Mark Walters
2012-07-04 16:17     ` Austin Clements
2012-07-03 22:20 ` [PATCH 4/8] emacs: Helper for reporting search parsing errors Austin Clements
2012-07-04  8:41   ` Mark Walters
2012-07-03 22:20 ` [PATCH 5/8] emacs: Pass plist to `notmuch-search-show-result' Austin Clements
2012-07-03 22:20 ` [PATCH 6/8] test: New test for incremental search output parsing Austin Clements
2012-07-03 22:20 ` [PATCH 7/8] emacs: Implement an incremental JSON parser Austin Clements
2012-07-05  8:30   ` Mark Walters
2012-07-05 18:36     ` Austin Clements
2012-07-03 22:20 ` [PATCH 8/8] emacs: Switch from text to JSON format for search results Austin Clements
2012-07-05  8:37   ` Mark Walters
2012-07-05 18:58     ` Austin Clements
2012-07-04 16:37 ` [PATCH 0/8] JSON-based search-mode Tomi Ollila
2012-07-05 20:52 ` [PATCH v2 0/9] " Austin Clements
2012-07-05 20:52   ` [PATCH v2 1/9] emacs: Clean up notmuch-search-show-result Austin Clements
2012-07-05 20:52   ` [PATCH v2 2/9] emacs: Separate search line parsing and display Austin Clements
2012-07-05 20:52   ` [PATCH v2 3/9] emacs: Helper for reporting search parsing errors Austin Clements
2012-07-05 20:52   ` [PATCH v2 4/9] emacs: Move search-target logic to `notmuch-search-show-result' Austin Clements
2012-07-05 20:52   ` [PATCH v2 5/9] emacs: Pass plist " Austin Clements
2012-07-05 20:52   ` [PATCH v2 6/9] test: New test for incremental search output parsing Austin Clements
2012-07-05 20:52   ` [PATCH v2 7/9] emacs: Implement an incremental JSON parser Austin Clements
2012-07-05 20:52   ` [PATCH v2 8/9] emacs: Switch from text to JSON format for search results Austin Clements
2012-07-05 20:52   ` [PATCH v2 9/9] News for JSON-based search Austin Clements
2012-07-05 21:44   ` [PATCH v2 0/9] JSON-based search-mode Mark Walters
2012-07-06  0:29     ` Austin Clements
2012-07-07 16:27       ` Mark Walters
2012-07-09 21:42 ` [PATCH v3 " Austin Clements
2012-07-09 21:42   ` [PATCH v3 1/9] emacs: Clean up notmuch-search-show-result Austin Clements
2012-07-13  3:14     ` David Bremner
2012-07-09 21:42   ` [PATCH v3 2/9] emacs: Separate search line parsing and display Austin Clements
2012-07-09 21:42   ` [PATCH v3 3/9] emacs: Helper for reporting search parsing errors Austin Clements
2012-07-09 21:42   ` [PATCH v3 4/9] emacs: Move search-target logic to `notmuch-search-show-result' Austin Clements
2012-07-09 21:42   ` [PATCH v3 5/9] emacs: Pass plist " Austin Clements
2012-07-09 21:42   ` [PATCH v3 6/9] test: New test for incremental search output parsing Austin Clements
2012-07-09 21:42   ` [PATCH v3 7/9] emacs: Implement an incremental JSON parser Austin Clements
2012-07-09 21:42   ` Austin Clements [this message]
2012-07-09 21:42   ` [PATCH v3 9/9] News for JSON-based search Austin Clements
2012-07-11  6:55   ` [PATCH v3 0/9] JSON-based search-mode Mark Walters
2012-07-11  8:48   ` Tomi Ollila
2012-07-21 17:37 ` [PATCH v4 0/8] emacs: JSON-based search cleanups Austin Clements
2012-07-21 17:37   ` [PATCH v4 1/8] emacs: Record thread search result object in a text property Austin Clements
2012-07-21 17:37   ` [PATCH v4 2/8] emacs: Use text properties instead of overlays for tag coloring Austin Clements
2012-07-21 17:37   ` [PATCH v4 3/8] emacs: Update tags by rewriting the search result line in place Austin Clements
2012-07-21 17:37   ` [PATCH v4 4/8] emacs: Use result text properties for search result iteration Austin Clements
2012-07-21 17:37   ` [PATCH v4 5/8] emacs: Replace other search text properties with result property Austin Clements
2012-07-21 17:37   ` [PATCH v4 6/8] emacs: Allow custom tags formatting Austin Clements
2012-07-21 17:37   ` [PATCH v4 7/8] emacs: Fix navigation of multi-line search result formats Austin Clements
2012-08-02  6:51     ` Jani Nikula
2012-08-02  7:19       ` [PATCH] emacs: fix a bug introduced by the recent search cleanups Mark Walters
2012-08-02  7:59         ` Jani Nikula
2012-08-02 14:22         ` Austin Clements
2012-08-03  1:00         ` David Bremner
2012-07-21 17:37   ` [PATCH v4 8/8] News for " Austin Clements
2012-07-21 17:56   ` [PATCH v4 0/8] emacs: JSON-based " Austin Clements
2012-07-22 15:27   ` Mark Walters
2012-07-22 18:45   ` Jameson Graef Rollins
2012-07-24 12:34   ` David Bremner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://notmuchmail.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1341870162-17782-9-git-send-email-amdragon@mit.edu \
    --to=amdragon@mit.edu \
    --cc=notmuch@notmuchmail.org \
    --cc=tomi.ollila@iki.fi \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).