changeset 85805:1d1d43e36948

Use expand-file-name rather than concat. (doc-view-cache-directory): Add the UID so multiple users won't clash. (doc-view-current-overlay, doc-view-pending-cache-flush): New vars. (doc-view-goto-page, doc-view-insert-image, doc-view-buffer-message) (doc-view-toggle-display): Use an overlay over the whole buffer so as not to have to touch the buffer's content. (doc-view-initiate-display): New function, extracted from doc-view-mode. (doc-view-mode): Use it. Don't mark as a special mode. Put the page numbers in the modeline. Set up the overlay. Hide the cursor. Run the mode hook. Use after-revert-hook rather than revert-buffer-function. (doc-view-search-internal): Fix typo. (doc-view-convert-current-doc, doc-view-insert-image): Delay the image-cache flush. (doc-view-reconvert-doc): Don't reset the whole mode. (doc-view-make-safe-dir): New function. (doc-view-current-cache-dir): Use it.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Tue, 30 Oct 2007 21:53:05 +0000
parents b9d04b479b22
children 2a9d361b3986
files lisp/ChangeLog lisp/doc-view.el
diffstat 2 files changed, 154 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Tue Oct 30 21:25:32 2007 +0000
+++ b/lisp/ChangeLog	Tue Oct 30 21:53:05 2007 +0000
@@ -1,3 +1,23 @@
+2007-10-30  Stefan Monnier  <monnier@iro.umontreal.ca>
+
+	* doc-view.el: Use expand-file-name rather than concat.
+	(doc-view-cache-directory): Add the UID so multiple users won't clash.
+	(doc-view-current-overlay, doc-view-pending-cache-flush): New vars.
+	(doc-view-goto-page, doc-view-insert-image, doc-view-buffer-message)
+	(doc-view-toggle-display): Use an overlay over the whole buffer so as
+	not to have to touch the buffer's content.
+	(doc-view-initiate-display): New function, extracted from doc-view-mode.
+	(doc-view-mode): Use it.  Don't mark as a special mode.
+	Put the page numbers in the modeline.
+	Set up the overlay.  Hide the cursor.  Run the mode hook.
+	Use after-revert-hook rather than revert-buffer-function.
+	(doc-view-search-internal): Fix typo.
+	(doc-view-convert-current-doc, doc-view-insert-image): Delay the
+	image-cache flush.
+	(doc-view-reconvert-doc): Don't reset the whole mode.
+	(doc-view-make-safe-dir): New function.
+	(doc-view-current-cache-dir): Use it.
+
 2007-10-30  Jason Rumney  <jasonr@gnu.org>
 
 	* time.el (display-time-world-list): Test for zoneinfo support.
--- a/lisp/doc-view.el	Tue Oct 30 21:25:32 2007 +0000
+++ b/lisp/doc-view.el	Tue Oct 30 21:53:05 2007 +0000
@@ -103,9 +103,10 @@
 ;; Todo:
 ;; - better menu.
 ;; - don't use `find-file'.
-;; - `reload' without changing the slicing.
 ;; - Bind slicing to a drag event.
-;; - zoom
+;; - zoom (the whole document and/or just the region around the cursor).
+;; - get rid of the silly arrow in the fringe.
+;; - improve anti-aliasing (pdf-utils gets it better).
 
 (require 'dired)
 (require 'image-mode)
@@ -156,8 +157,8 @@
   :type 'file
   :group 'doc-view)
 
-(defcustom doc-view-cache-directory (concat temporary-file-directory
-					    "doc-view")
+(defcustom doc-view-cache-directory
+  (expand-file-name (concat "docview" (user-uid)) temporary-file-directory)
   "The base directory, where the PNG images will be saved."
   :type 'directory
   :group 'doc-view)
@@ -201,6 +202,8 @@
 
 (defvar doc-view-current-image nil
   "Only used internally.")
+(defvar doc-view-current-overlay)
+(defvar doc-view-pending-cache-flush nil)
 
 (defvar doc-view-current-info nil
   "Only used internally.")
@@ -303,16 +306,14 @@
 			 (setq contexts (concat contexts "  - \"" m "\"\n")))
 		       contexts)))))
     ;; Update the buffer
-    (let ((inhibit-read-only t))
-      (erase-buffer)
-      (let ((beg (point)))
-	(doc-view-insert-image (nth (1- page) doc-view-current-files)
-			       :pointer 'arrow)
-	(put-text-property beg (point) 'help-echo doc-view-current-info))
-      (insert "\n" doc-view-current-info)
-      (goto-char (point-min))
-      (forward-char))
-    (set-buffer-modified-p nil)))
+    (doc-view-insert-image (nth (1- page) doc-view-current-files)
+                           :pointer 'arrow)
+    (overlay-put doc-view-current-overlay 'help-echo doc-view-current-info)
+    (goto-char (point-min))
+    ;; This seems to be needed for set-window-hscroll (in
+    ;; image-forward-hscroll) to do something useful, I don't have time to
+    ;; debug this now.  :-(  --Stef
+    (forward-char)))
 
 (defun doc-view-next-page (&optional arg)
   "Browse ARG pages forward."
@@ -369,20 +370,49 @@
   (when (eq major-mode 'doc-view-mode)
     (kill-buffer (current-buffer))))
 
+(defun doc-view-make-safe-dir (dir)
+  (condition-case nil
+      (let ((umask (default-file-modes)))
+        (unwind-protect
+            (progn
+              ;; Create temp files with strict access rights.  It's easy to
+              ;; loosen them later, whereas it's impossible to close the
+              ;; time-window of loose permissions otherwise.
+              (set-default-file-modes #o0700)
+              (make-directory dir))
+          ;; Reset the umask.
+          (set-default-file-modes umask)))
+    (file-already-exists
+     (if (file-symlink-p dir)
+         (error "Danger: %s points to a symbolic link" dir))
+     ;; In case it was created earlier with looser rights.
+     ;; We could check the mode info returned by file-attributes, but it's
+     ;; a pain to parse and it may not tell you what we want under
+     ;; non-standard file-systems.  So let's just say what we want and let
+     ;; the underlying C code and file-system figure it out.
+     ;; This also ends up checking a bunch of useful conditions: it makes
+     ;; sure we have write-access to the directory and that we own it, thus
+     ;; closing a bunch of security holes.
+     (set-file-modes dir #o0700))))
+
 (defun doc-view-current-cache-dir ()
   "Return the directory where the png files of the current doc should be saved.
 It's a subdirectory of `doc-view-cache-directory'."
   (if doc-view-current-cache-dir
       doc-view-current-cache-dir
+    ;; Try and make sure doc-view-cache-directory exists and is safe.
+    (doc-view-make-safe-dir doc-view-cache-directory)
+    ;; Now compute the subdirectory to use.
     (setq doc-view-current-cache-dir
 	  (file-name-as-directory
-	   (concat (file-name-as-directory doc-view-cache-directory)
-		   (let ((doc buffer-file-name))
-		     (concat (file-name-nondirectory doc)
-			     "-"
-			     (with-temp-buffer
-			       (insert-file-contents-literally doc)
-			       (md5 (current-buffer))))))))))
+	   (expand-file-name
+            (let ((doc buffer-file-name))
+              (concat (file-name-nondirectory doc)
+                      "-"
+                      (with-temp-buffer
+                        (insert-file-contents-literally doc)
+                        (md5 (current-buffer)))))
+            doc-view-cache-directory)))))
 
 (defun doc-view-remove-if (predicate list)
   "Return LIST with all items removed that satisfy PREDICATE."
@@ -393,7 +423,7 @@
 
 ;;;; Conversion Functions
 
-(defun doc-view-reconvert-doc (&rest args)
+(defun doc-view-reconvert-doc ()
   "Reconvert the current document.
 Should be invoked when the cached images aren't up-to-date."
   (interactive)
@@ -401,7 +431,7 @@
   ;; Clear the old cached files
   (when (file-exists-p (doc-view-current-cache-dir))
     (dired-delete-file (doc-view-current-cache-dir) 'always))
-  (doc-view-mode))
+  (doc-view-initiate-display))
 
 (defun doc-view-dvi->pdf-sentinel (proc event)
   "If DVI->PDF conversion was successful, convert the PDF to PNG now."
@@ -412,8 +442,8 @@
 	  mode-line-process nil)
     ;; Now go on converting this PDF to a set of PNG files.
     (let* ((pdf (process-get proc 'pdf-file))
-	   (png (concat (doc-view-current-cache-dir)
-			"page-%d.png")))
+	   (png (expand-file-name "page-%d.png"
+                                  (doc-view-current-cache-dir))))
       (doc-view-pdf/ps->png pdf png))))
 
 (defun doc-view-dvi->pdf (dvi pdf)
@@ -493,8 +523,8 @@
 	  mode-line-process nil)
     ;; Now we can transform to plain text.
     (doc-view-pdf->txt (process-get proc 'pdf-file)
-		       (concat (doc-view-current-cache-dir)
-			       "doc.txt"))))
+		       (expand-file-name "doc.txt"
+                                         (doc-view-current-cache-dir)))))
 
 (defun doc-view-ps->pdf (ps pdf)
   "Convert PS to PDF asynchronously."
@@ -516,18 +546,23 @@
   "Convert `buffer-file-name' to a set of png files, one file per page.
 Those files are saved in the directory given by the function
 `doc-view-current-cache-dir'."
-  (clear-image-cache)
-  (let ((png-file (concat (doc-view-current-cache-dir)
-			  "page-%d.png")))
-    (make-directory (doc-view-current-cache-dir) t)
+  ;; Let stale files still display while we recompute the new ones, so only
+  ;; flush the cache when the conversion is over.  One of the reasons why it
+  ;; is important to keep displaying the stale page is so that revert-buffer
+  ;; preserves the horizontal/vertical scroll settings (which are otherwise
+  ;; resets during the redisplay).
+  (setq doc-view-pending-cache-flush t)
+  (let ((png-file (expand-file-name "page-%d.png"
+                                    (doc-view-current-cache-dir))))
+    (make-directory (doc-view-current-cache-dir))
     (if (not (string= (file-name-extension buffer-file-name) "dvi"))
 	;; Convert to PNG images.
 	(doc-view-pdf/ps->png buffer-file-name png-file)
       ;; DVI files have to be converted to PDF before Ghostscript can process
       ;; it.
       (doc-view-dvi->pdf buffer-file-name
-			 (concat (file-name-as-directory doc-view-current-cache-dir)
-				 "doc.pdf")))))
+			 (expand-file-name "doc.pdf"
+                                           doc-view-current-cache-dir)))))
 
 ;;;; Slicing
 
@@ -583,9 +618,16 @@
 (defun doc-view-insert-image (file &rest args)
   "Insert the given png FILE.
 ARGS is a list of image descriptors."
+  (when doc-view-pending-cache-flush
+    (clear-image-cache)
+    (setq doc-view-pending-cache-flush nil))
   (let ((image (apply 'create-image file 'png nil args)))
     (setq doc-view-current-image image)
-    (insert-image image (concat "[" file "]") nil doc-view-current-slice)))
+    (move-overlay doc-view-current-overlay (point-min) (point-max))
+    (overlay-put doc-view-current-overlay 'display
+                 (if doc-view-current-slice
+                     (list (cons 'slice doc-view-current-slice) image)
+                   image))))
 
 (defun doc-view-sort (a b)
   "Return non-nil if A should be sorted before B.
@@ -605,9 +647,14 @@
     (doc-view-goto-page doc-view-current-page)))
 
 (defun doc-view-buffer-message ()
-  (insert (propertize "Welcome to DocView!" 'face 'bold)
-          "\n"
-          "
+  ;; Only show this message initially, not when refreshing the buffer (in which
+  ;; case it's better to keep displaying the "stale" page while computing
+  ;; the fresh new ones).
+  (unless (overlay-get doc-view-current-overlay 'display)
+    (overlay-put doc-view-current-overlay 'display
+                 (concat (propertize "Welcome to DocView!" 'face 'bold)
+                         "\n"
+                         "
 If you see this buffer it means  that the document you want to view is being
 converted to PNG and the conversion of the first page hasn't finished yet or
 `doc-view-conversion-refresh-interval' is set to nil.
@@ -616,7 +663,7 @@
 
 `q' : Bury this buffer.  Conversion will go on in background.
 `k' : Kill the conversion process and this buffer.
-`K' : Kill the conversion process.\n"))
+`K' : Kill the conversion process.\n"))))
 
 (defun doc-view-show-tooltip ()
   (interactive)
@@ -632,20 +679,17 @@
       (progn
 	(doc-view-kill-proc)
 	(setq buffer-read-only nil)
-	(erase-buffer)
-	(insert-file-contents buffer-file-name)
+        (delete-overlay doc-view-current-overlay)
 	;; Switch to the previously used major mode or fall back to fundamental
 	;; mode.
 	(if doc-view-previous-major-mode
 	    (funcall doc-view-previous-major-mode)
 	  (fundamental-mode))
-	(doc-view-minor-mode 1)
-	(set-buffer-modified-p nil))
+	(doc-view-minor-mode 1))
     ;; Switch to doc-view-mode
     (when (and (buffer-modified-p)
 	       (y-or-n-p "The buffer has been modified.  Save the changes? "))
       (save-buffer))
-    (erase-buffer)
     (doc-view-mode)))
 
 ;;;; Searching
@@ -664,11 +708,11 @@
 	(when (match-string 1) (incf page))
 	(when (match-string 2)
 	  (if (/= page lastpage)
-	      (setq matches (push (cons page
-					(list (buffer-substring
-					       (line-beginning-position)
-					       (line-end-position))))
-				  matches))
+	      (push (cons page
+                          (list (buffer-substring
+                                 (line-beginning-position)
+                                 (line-end-position))))
+                    matches)
 	    (setq matches (cons
 			   (append
 			    (or
@@ -698,8 +742,8 @@
   (interactive)
   ;; New search, so forget the old results.
   (setq doc-view-current-search-matches nil)
-  (let ((txt (concat (doc-view-current-cache-dir)
-		     "doc.txt")))
+  (let ((txt (expand-file-name "doc.txt"
+                               (doc-view-current-cache-dir))))
     (if (file-readable-p txt)
 	(progn
 	  (setq doc-view-current-search-matches
@@ -721,13 +765,13 @@
 	    ;; Doc is a PS, so convert it to PDF (which will be converted to
 	    ;; TXT thereafter).
 	    (doc-view-ps->pdf buffer-file-name
-			      (concat (doc-view-current-cache-dir)
-				      "doc.pdf")))
+			      (expand-file-name "doc.pdf"
+                                                (doc-view-current-cache-dir))))
 	   ((string= ext "dvi")
 	    ;; Doc is a DVI.  This means that a doc.pdf already exists in its
 	    ;; cache subdirectory.
-	    (doc-view-pdf->txt (concat (doc-view-current-cache-dir)
-				       "doc.pdf")
+	    (doc-view-pdf->txt (expand-file-name "doc.pdf"
+                                                 (doc-view-current-cache-dir))
 			       txt))
 	   (t (error "DocView doesn't know what to do"))))))))
 
@@ -761,7 +805,30 @@
 
 ;;;; User interface commands and the mode
 
-(put 'doc-view-mode 'mode-class 'special)
+;; (put 'doc-view-mode 'mode-class 'special)
+
+(defun doc-view-initiate-display ()
+  ;; Switch to image display if possible
+  (if (and (display-images-p)
+	   (image-type-available-p 'png))
+      (progn
+	(doc-view-buffer-message)
+	(setq doc-view-current-page (or doc-view-current-page 1))
+	(if (file-exists-p (doc-view-current-cache-dir))
+	    (progn
+	      (message "DocView: using cached files!")
+	      (doc-view-display buffer-file-name))
+	  (doc-view-convert-current-doc))
+	(message
+	 "%s"
+	 (substitute-command-keys
+	  (concat "Type \\[doc-view-toggle-display] to toggle between "
+		  "editing or viewing the document."))))
+    (message
+     "%s"
+     (substitute-command-keys
+      (concat "No image (png) support available.  Type \\[doc-view-toggle-display] "
+	      "to switch to an editing mode.")))))
 
 ;;;###autoload
 (defun doc-view-mode ()
@@ -783,37 +850,22 @@
   (make-local-variable 'doc-view-current-cache-dir)
   (make-local-variable 'doc-view-current-info)
   (make-local-variable 'doc-view-current-search-matches)
-  ;; The file should already be in the current buffer.  --Stef
-  ;; (insert-file-contents buffer-file-name)
+  (set (make-local-variable 'doc-view-current-overlay)
+       (make-overlay (point-min) (point-max) nil t))
+  (add-hook 'change-major-mode-hook
+            (lambda () (delete-overlay doc-view-current-overlay))
+            nil t)
+  (set (make-local-variable 'mode-line-position)
+       '(" P" (:eval (number-to-string doc-view-current-page))
+         "/" (:eval (number-to-string (length doc-view-current-files)))))
+  (set (make-local-variable 'cursor-type) nil)
   (use-local-map doc-view-mode-map)
-  (set (make-local-variable 'revert-buffer-function) 'doc-view-reconvert-doc)
+  (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
   (setq mode-name "DocView"
 	buffer-read-only t
 	major-mode 'doc-view-mode)
-  ;; Switch to image display if possible
-  (if (and (display-images-p)
-	   (image-type-available-p 'png))
-      (let ((inhibit-read-only t))
-	(erase-buffer)
-	(doc-view-buffer-message)
-        (set-buffer-modified-p nil)
-	(setq doc-view-current-page (or doc-view-current-page 1))
-	(if (file-exists-p (doc-view-current-cache-dir))
-	    (progn
-	      (message "DocView: using cached files!")
-	      (doc-view-display buffer-file-name))
-	  (doc-view-convert-current-doc))
-	(use-local-map doc-view-mode-map)
-	(message
-	 "%s"
-	 (substitute-command-keys
-	  (concat "Type \\[doc-view-toggle-display] to toggle between "
-		  "editing or viewing the document."))))
-    (message
-     "%s"
-     (substitute-command-keys
-      (concat "No image (png) support available.  Type \\[doc-view-toggle-display] "
-	      "to switch to an editing mode.")))))
+  (doc-view-initiate-display)
+  (run-mode-hooks 'doc-view-mode-hook))
 
 ;;;###autoload
 (define-minor-mode doc-view-minor-mode