]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: introduce notmuch-command-to-string, replace use of shell-command-to-string
[notmuch] / emacs / notmuch-lib.el
index 77a591d89ede485c0fbd3318511fbf71b028d338..85950da14f49ab40d0508c628cd6aa6d3358e2a7 100644 (file)
@@ -97,13 +97,40 @@ For example, if you wanted to remove an \"inbox\" tag and add an
   :group 'notmuch-search
   :group 'notmuch-show)
 
+;; By default clicking on a button does not select the window
+;; containing the button (as opposed to clicking on a widget which
+;; does). This means that the button action is then executed in the
+;; current selected window which can cause problems if the button
+;; changes the buffer (e.g., id: links) or moves point.
+;;
+;; This provides a button type which overrides mouse-action so that
+;; the button's window is selected before the action is run. Other
+;; notmuch buttons can get the same behaviour by inheriting from this
+;; button type.
+(define-button-type 'notmuch-button-type
+  'mouse-action (lambda (button)
+                 (select-window (posn-window (event-start last-input-event)))
+                 (button-activate button)))
+
+(defun notmuch-command-to-string (&rest args)
+  "Synchronously invoke \"notmuch\" with the given list of arguments.
+
+If notmuch exits with a non-zero status, output from the process
+will appear in a buffer named \"*Notmuch errors*\" and an error
+will be signaled.
+
+Otherwise the output will be returned"
+  (with-temp-buffer
+    (let* ((status (apply #'call-process notmuch-command nil t nil args))
+          (output (buffer-string)))
+      (notmuch-check-exit-status status (cons notmuch-command args) output)
+      output)))
+
 (defun notmuch-version ()
   "Return a string with the notmuch version number."
   (let ((long-string
         ;; Trim off the trailing newline.
-        (substring (shell-command-to-string
-                    (concat notmuch-command " --version"))
-                   0 -1)))
+        (substring (notmuch-command-to-string "--version") 0 -1)))
     (if (string-match "^notmuch\\( version\\)? \\(.*\\)$"
                      long-string)
        (match-string 2 long-string)
@@ -112,9 +139,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
 (defun notmuch-config-get (item)
   "Return a value from the notmuch configuration."
   ;; Trim off the trailing newline
-  (substring (shell-command-to-string
-             (concat notmuch-command " config get " item))
-             0 -1))
+  (substring (notmuch-command-to-string "config" "get" item) 0 -1))
 
 (defun notmuch-database-path ()
   "Return the database.path value from the notmuch configuration."
@@ -301,38 +326,75 @@ current buffer, if possible."
   (loop for (key value . rest) on plist by #'cddr
        collect (cons (intern (substring (symbol-name key) 1)) value)))
 
-(defun notmuch-combine-face-text-property (start end face)
+(defun notmuch-face-ensure-list-form (face)
+  "Return FACE in face list form.
+
+If FACE is already a face list, it will be returned as-is.  If
+FACE is a face name or face plist, it will be returned as a
+single element face list."
+  (if (and (listp face) (not (keywordp (car face))))
+      face
+    (list face)))
+
+(defun notmuch-combine-face-text-property (start end face &optional below object)
   "Combine FACE into the 'face text property between START and END.
 
 This function combines FACE with any existing faces between START
-and END.  Attributes specified by FACE take precedence over
-existing attributes.  FACE must be a face name (a symbol or
-string), a property list of face attributes, or a list of these."
-
-  (let ((pos start))
+and END in OBJECT (which defaults to the current buffer).
+Attributes specified by FACE take precedence over existing
+attributes unless BELOW is non-nil.  FACE must be a face name (a
+symbol or string), a property list of face attributes, or a list
+of these.  For convenience when applied to strings, this returns
+OBJECT."
+
+  ;; A face property can have three forms: a face name (a string or
+  ;; symbol), a property list, or a list of these two forms.  In the
+  ;; list case, the faces will be combined, with the earlier faces
+  ;; taking precedent.  Here we canonicalize everything to list form
+  ;; to make it easy to combine.
+  (let ((pos start)
+       (face-list (notmuch-face-ensure-list-form face)))
     (while (< pos end)
-      (let ((cur (get-text-property pos 'face))
-           (next (next-single-property-change pos 'face nil end)))
-       (put-text-property pos next 'face (cons face cur))
-       (setq pos next)))))
-
-(defun notmuch-pop-up-error (msg)
-  "Pop up an error buffer displaying MSG.
-
-This will accumulate error messages in the errors buffer until
-the user dismisses it."
-
-  (let ((buf (get-buffer-create "*Notmuch errors*")))
-    (with-current-buffer buf
-      (view-mode-enter nil #'kill-buffer)
-      (let ((inhibit-read-only t))
-       (goto-char (point-max))
-       (unless (bobp)
-         (insert "\n"))
-       (insert msg)
+      (let* ((cur (get-text-property pos 'face object))
+            (cur-list (notmuch-face-ensure-list-form cur))
+            (new (cond ((null cur-list) face)
+                       (below (append cur-list face-list))
+                       (t (append face-list cur-list))))
+            (next (next-single-property-change pos 'face object end)))
+       (put-text-property pos next 'face new object)
+       (setq pos next))))
+  object)
+
+(defun notmuch-combine-face-text-property-string (string face &optional below)
+  (notmuch-combine-face-text-property
+   0
+   (length string)
+   face
+   below
+   string))
+
+(defun notmuch-logged-error (msg &optional extra)
+  "Log MSG and EXTRA to *Notmuch errors* and signal MSG.
+
+This logs MSG and EXTRA to the *Notmuch errors* buffer and
+signals MSG as an error.  If EXTRA is non-nil, text referring the
+user to the *Notmuch errors* buffer will be appended to the
+signaled error.  This function does not return."
+
+  (with-current-buffer (get-buffer-create "*Notmuch errors*")
+    (goto-char (point-max))
+    (unless (bobp)
+      (newline))
+    (save-excursion
+      (insert "[" (current-time-string) "]\n" msg)
+      (unless (bolp)
+       (newline))
+      (when extra
+       (insert extra)
        (unless (bolp)
-         (insert "\n"))))
-    (pop-to-buffer buf)))
+         (newline)))))
+  (error "%s" (concat msg (when extra
+                           " (see *Notmuch errors* for more details)"))))
 
 (defun notmuch-check-async-exit-status (proc msg)
   "If PROC exited abnormally, pop up an error buffer and signal an error.
@@ -363,35 +425,40 @@ contents of ERR-FILE will be included in the error message."
   (cond
    ((eq exit-status 0) t)
    ((eq exit-status 20)
-    (notmuch-pop-up-error "Error: Version mismatch.
+    (notmuch-logged-error "notmuch CLI version mismatch
 Emacs requested an older output format than supported by the notmuch CLI.
-You may need to restart Emacs or upgrade your notmuch Emacs package.")
-    (error "notmuch CLI version mismatch"))
+You may need to restart Emacs or upgrade your notmuch Emacs package."))
    ((eq exit-status 21)
-    (notmuch-pop-up-error "Error: Version mismatch.
+    (notmuch-logged-error "notmuch CLI version mismatch
 Emacs requested a newer output format than supported by the notmuch CLI.
-You may need to restart Emacs or upgrade your notmuch package.")
-    (error "notmuch CLI version mismatch"))
+You may need to restart Emacs or upgrade your notmuch package."))
    (t
-    (notmuch-pop-up-error
-     (concat
-      (format "Error invoking notmuch.  %s exited with %s%s.\n"
-             (mapconcat #'identity command " ")
-             ;; Signal strings look like "Terminated", hence the
-             ;; colon.
-             (if (integerp exit-status) "status " "signal: ")
-             exit-status)
-      (when err-file
-       (concat "Error:\n"
-               (with-temp-buffer
-                 (insert-file-contents err-file)
-                 (if (eobp)
-                     "(no error output)\n"
-                   (buffer-string)))))
-      (when (and output (not (equal output "")))
-       (format "Output:\n%s" output))))
-    ;; Mimic `process-lines'
-    (error "%s exited with status %s" (car command) exit-status))))
+    (let* ((err (when err-file
+                 (with-temp-buffer
+                   (insert-file-contents err-file)
+                   (unless (eobp)
+                     (buffer-string)))))
+          (extra
+           (concat
+            "command: " (mapconcat #'shell-quote-argument command " ") "\n"
+            (if (integerp exit-status)
+                (format "exit status: %s\n" exit-status)
+              (format "exit signal: %s\n" exit-status))
+            (when err
+              (concat "stderr:\n" err))
+            (when output
+              (concat "stdout:\n" output)))))
+       (if err
+           ;; We have an error message straight from the CLI.
+           (notmuch-logged-error
+            (replace-regexp-in-string "\\s $" "" err) extra)
+         ;; We only have combined output from the CLI; don't inundate
+         ;; the user with it.  Mimic `process-lines'.
+         (notmuch-logged-error (format "%s exited with status %s"
+                                       (car command) exit-status)
+                               extra))
+       ;; `notmuch-logged-error' does not return.
+       ))))
 
 (defun notmuch-call-notmuch-json (&rest args)
   "Invoke `notmuch-command' with `args' and return the parsed JSON output.