emacs: show: implement lazy hidden part handling

This adds the actual code to do the lazy insertion of hidden parts.

We use a memory inefficient but simple method: when we come to insert
the part if it is hidden we just store all of the arguments to the
part insertion function as a button property. This means when we want
to show the part we can just resume where we left off.

One thing is that we can't tell if a lazy part will produce text until
we try to render it so when unhiding a part we check to see if it
rendered; if not we invoke the default part handler (e.g. an external
viewer).

Also, we would like to insert the lazy part at the start of the line
after the part button. But if this line has some text properties
(e.g. the colours for a following message header) then the lazy part
gets these properties. Thus we start at the end of the part button
line, insert a newline, insert the lazy part, and then delete the
extra newline at the end of the part.
This commit is contained in:
Mark Walters 2013-06-10 05:57:13 +01:00 committed by David Bremner
parent 055f7621d6
commit d0bd88f06d

View file

@ -488,12 +488,14 @@ message at DEPTH in the current thread."
;; return button ;; return button
button)) button))
;; This is taken from notmuch-wash: maybe it should be unified?
(defun notmuch-show-toggle-part-invisibility (&optional button) (defun notmuch-show-toggle-part-invisibility (&optional button)
(interactive) (interactive)
(let* ((button (or button (button-at (point)))) (let* ((button (or button (button-at (point))))
(overlay (button-get button 'overlay))) (overlay (button-get button 'overlay))
(when overlay (lazy-part (button-get button :notmuch-lazy-part)))
;; We have a part to toggle if there is an overlay or if there is a lazy part.
;; If neither is present we cannot toggle the part so we just return nil.
(when (or overlay lazy-part)
(let* ((show (button-get button :notmuch-part-hidden)) (let* ((show (button-get button :notmuch-part-hidden))
(new-start (button-start button)) (new-start (button-start button))
(button-label (button-get button :base-label)) (button-label (button-get button :base-label))
@ -509,7 +511,15 @@ message at DEPTH in the current thread."
(move-overlay button new-start (point)) (move-overlay button new-start (point))
(delete-region (point) old-end)) (delete-region (point) old-end))
(goto-char (min old-point (1- (button-end button)))) (goto-char (min old-point (1- (button-end button))))
(overlay-put overlay 'invisible (not show)))))) ;; Return nil if there is a lazy-part, it is empty, and we are
;; trying to show it. In all other cases return t.
(if lazy-part
(when show
(button-put button :notmuch-lazy-part nil)
(notmuch-show-lazy-part lazy-part button))
;; else there must be an overlay.
(overlay-put overlay 'invisible (not show))
t)))))
;; MIME part renderers ;; MIME part renderers
@ -828,6 +838,39 @@ message at DEPTH in the current thread."
(pushnew :notmuch-part v) (pushnew :notmuch-part v)
v)))) v))))
(defun notmuch-show-lazy-part (part-args button)
;; Insert the lazy part after the button for the part. We would just
;; move to the start of the new line following the button and insert
;; the part but that point might have text properties (eg colours
;; from a message header etc) so instead we start from the last
;; character of the button by adding a newline and finish by
;; removing the extra newline from the end of the part.
(save-excursion
(goto-char (button-end button))
(insert "\n")
(let* ((inhibit-read-only t)
;; We need to use markers for the start and end of the part
;; because the part insertion functions do not guarantee
;; to leave point at the end of the part.
(part-beg (copy-marker (point) nil))
(part-end (copy-marker (point) t))
;; We have to save the depth as we can't find the depth
;; when narrowed.
(depth (notmuch-show-get-depth)))
(save-restriction
(narrow-to-region part-beg part-end)
(delete-region part-beg part-end)
(apply #'notmuch-show-insert-bodypart-internal part-args)
(indent-rigidly part-beg part-end depth))
(goto-char part-end)
(delete-char 1)
(notmuch-show-record-part-information (second part-args)
(button-start button)
part-end)
;; Create the overlay. If the lazy-part turned out to be empty/not
;; showable this returns nil.
(notmuch-show-create-part-overlays button part-beg part-end))))
(defun notmuch-show-insert-bodypart (msg part depth &optional hide) (defun notmuch-show-insert-bodypart (msg part depth &optional hide)
"Insert the body part PART at depth DEPTH in the current thread. "Insert the body part PART at depth DEPTH in the current thread.
@ -846,15 +889,21 @@ If HIDE is non-nil then initially hide this part."
(notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename)))) (notmuch-show-insert-part-header nth mime-type content-type (plist-get part :filename))))
(content-beg (point))) (content-beg (point)))
(notmuch-show-insert-bodypart-internal msg part mime-type nth depth button) (if (not hide)
(notmuch-show-insert-bodypart-internal msg part mime-type nth depth button)
(button-put button :notmuch-lazy-part
(list msg part mime-type nth depth button)))
;; Some of the body part handlers leave point somewhere up in the ;; Some of the body part handlers leave point somewhere up in the
;; part, so we make sure that we're down at the end. ;; part, so we make sure that we're down at the end.
(goto-char (point-max)) (goto-char (point-max))
;; Ensure that the part ends with a carriage return. ;; Ensure that the part ends with a carriage return.
(unless (bolp) (unless (bolp)
(insert "\n")) (insert "\n"))
(notmuch-show-create-part-overlays button content-beg (point)) ;; We do not create the overlay for hidden (lazy) parts until
(when hide ;; they are inserted.
(if (not hide)
(notmuch-show-create-part-overlays button content-beg (point))
(save-excursion (save-excursion
(notmuch-show-toggle-part-invisibility button))) (notmuch-show-toggle-part-invisibility button)))
(notmuch-show-record-part-information part beg (point)))) (notmuch-show-record-part-information part beg (point))))
@ -2019,8 +2068,10 @@ is destroyed when FN returns."
(defun notmuch-show-part-button-default (&optional button) (defun notmuch-show-part-button-default (&optional button)
(interactive) (interactive)
(let ((button (or button (button-at (point))))) (let ((button (or button (button-at (point)))))
(if (button-get button 'overlay) ;; Try to toggle the part, if that fails then call the default
(notmuch-show-toggle-part-invisibility button) ;; action. The toggle fails if the part has no emacs renderable
;; content.
(unless (notmuch-show-toggle-part-invisibility button)
(call-interactively notmuch-show-part-button-default-action)))) (call-interactively notmuch-show-part-button-default-action))))
(defun notmuch-show-save-part () (defun notmuch-show-save-part ()