image-file.el 7.08 KB
Newer Older
Pavel Janík's avatar
Pavel Janík committed
1
;;; image-file.el --- support for visiting image files
Miles Bader's avatar
Miles Bader committed
2
;;
Paul Eggert's avatar
Paul Eggert committed
3
;; Copyright (C) 2000-2019 Free Software Foundation, Inc.
Miles Bader's avatar
Miles Bader committed
4 5 6 7 8 9
;;
;; Author: Miles Bader <miles@gnu.org>
;; Keywords: multimedia

;; This file is part of GNU Emacs.

10
;; GNU Emacs is free software: you can redistribute it and/or modify
Miles Bader's avatar
Miles Bader committed
11
;; it under the terms of the GNU General Public License as published by
12 13
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Miles Bader's avatar
Miles Bader committed
14 15 16 17 18 19 20

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
21
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
Miles Bader's avatar
Miles Bader committed
22 23 24 25

;;; Commentary:

;; Defines a file-name-handler hook that transforms visited (or
Pavel Janík's avatar
Pavel Janík committed
26
;; inserted) image files so that they are displayed by Emacs as
Miles Bader's avatar
Miles Bader committed
27 28 29 30 31 32 33 34 35
;; images.  This is done by putting a `display' text-property on the
;; image data, with the image-data still present underneath; if the
;; resulting buffer file is saved to another name it will correctly save
;; the image data to the new file.

;;; Code:

(require 'image)

36

37
;;;###autoload
38
(defcustom image-file-name-extensions
39
  (purecopy '("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm" "svg"))
Lute Kamstra's avatar
Lute Kamstra committed
40
  "A list of image-file filename extensions.
41 42 43 44 45
Filenames having one of these extensions are considered image files,
in addition to those matching `image-file-name-regexps'.

See `auto-image-file-mode'; if `auto-image-file-mode' is enabled,
setting this variable directly does not take effect unless
46 47
`auto-image-file-mode' is re-enabled; this happens automatically when
the variable is set using \\[customize]."
48 49 50 51 52 53
  :type '(repeat string)
  :set (lambda (sym val)
	 (set-default sym val)
	 (when auto-image-file-mode
	   ;; Re-initialize the image-file handler
	   (auto-image-file-mode t)))
Miles Bader's avatar
Miles Bader committed
54 55 56
  :initialize 'custom-initialize-default
  :group 'image)

57
;;;###autoload
58
(defcustom image-file-name-regexps nil
Lute Kamstra's avatar
Lute Kamstra committed
59
  "List of regexps matching image-file filenames.
60 61
Filenames matching one of these regexps are considered image files,
in addition to those with an extension in `image-file-name-extensions'.
Miles Bader's avatar
Miles Bader committed
62

63 64
See function `auto-image-file-mode'; if `auto-image-file-mode' is
enabled, setting this variable directly does not take effect unless
65 66
`auto-image-file-mode' is re-enabled; this happens automatically when
the variable is set using \\[customize]."
Miles Bader's avatar
Miles Bader committed
67 68 69
  :type '(repeat regexp)
  :set (lambda (sym val)
	 (set-default sym val)
70
	 (when auto-image-file-mode
Miles Bader's avatar
Miles Bader committed
71
	   ;; Re-initialize the image-file handler
72
	   (auto-image-file-mode t)))
Miles Bader's avatar
Miles Bader committed
73 74 75
  :initialize 'custom-initialize-default
  :group 'image)

76 77 78

;;;###autoload
(defun image-file-name-regexp ()
Stefan Monnier's avatar
Stefan Monnier committed
79
  "Return a regular expression matching image-file filenames."
80 81 82
  (let ((exts-regexp
	 (and image-file-name-extensions
	      (concat "\\."
83 84 85 86
		      (regexp-opt (nconc (mapcar #'upcase
						 image-file-name-extensions)
					 image-file-name-extensions)
				  t)
87 88 89 90
		      "\\'"))))
    (if image-file-name-regexps
	(mapconcat 'identity
		   (if exts-regexp
91 92
		       (cons exts-regexp image-file-name-regexps)
		     image-file-name-regexps)
93 94 95 96
		   "\\|")
      exts-regexp)))


Miles Bader's avatar
Miles Bader committed
97 98 99
;;;###autoload
(defun insert-image-file (file &optional visit beg end replace)
  "Insert the image file FILE into the current buffer.
100 101 102
Optional arguments VISIT, BEG, END, and REPLACE are interpreted
as for the command `insert-file-contents'.  Return list of
absolute file name and number of characters inserted."
Miles Bader's avatar
Miles Bader committed
103 104 105 106
  (let ((rval
	 (image-file-call-underlying #'insert-file-contents-literally
				     'insert-file-contents
				     file visit beg end replace)))
107 108 109
    ;; Turn the image data into a real image, but only if the whole file
    ;; was inserted
    (when (and (or (null beg) (zerop beg)) (null end))
Miles Bader's avatar
Miles Bader committed
110 111
      (let* ((ibeg (point))
	     (iend (+ (point) (cadr rval)))
112
	     (visitingp (and visit (= ibeg (point-min)) (= iend (point-max))))
113 114
             (image (create-image (encode-coding-region ibeg iend 'binary t)
                                  nil t))
Miles Bader's avatar
Miles Bader committed
115 116
	     (props
	      `(display ,image
117 118
			yank-handler
			(image-file-yank-handler nil t)
Miles Bader's avatar
Miles Bader committed
119
			intangible ,image
120
			rear-nonsticky (display intangible)
Miles Bader's avatar
Miles Bader committed
121
			;; This a cheap attempt to make the whole buffer
122 123
			;; read-only when we're visiting the file (as
			;; opposed to just inserting it).
124
			,@(and visitingp
Miles Bader's avatar
Miles Bader committed
125
			       '(read-only t front-sticky (read-only))))))
126 127 128 129
	(add-text-properties ibeg iend props)
	(when visitingp
	  ;; Inhibit the cursor when the buffer contains only an image,
	  ;; because cursors look very strange on top of images.
130 131 132 133
	  (setq cursor-type nil)
	  ;; This just makes the arrow displayed in the right fringe
	  ;; area look correct when the image is wider than the window.
	  (setq truncate-lines t))))
Miles Bader's avatar
Miles Bader committed
134 135
    rval))

136 137 138 139 140
;; We use a yank-handler to make yanked images unique, so that
;; yanking two copies of the same image next to each other are
;; recognized as two different images.
(defun image-file-yank-handler (string)
  "Yank handler for inserting an image into a buffer."
141 142 143
  (let ((len (length string))
	(image (get-text-property 0 'display string)))
    (remove-text-properties 0 len yank-excluded-properties string)
144
    (if (consp image)
145 146 147 148 149 150 151 152
	(add-text-properties 0
			     (or (next-single-property-change 0 'image-counter string)
				 (length string))
			     `(display
			       ,(cons (car image) (cdr image))
			       yank-handler
			       ,(cons 'image-file-yank-handler '(nil t)))
			     string))
153 154
    (insert string)))

155
(put 'image-file-handler 'safe-magic t)
Miles Bader's avatar
Miles Bader committed
156
(defun image-file-handler (operation &rest args)
Stefan Monnier's avatar
Stefan Monnier committed
157
  "Filename handler for inserting image files.
Miles Bader's avatar
Miles Bader committed
158 159
OPERATION is the operation to perform, on ARGS.
See `file-name-handler-alist' for details."
160 161
  (if (and (eq operation 'insert-file-contents)
	   auto-image-file-mode)
Miles Bader's avatar
Miles Bader committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      (apply #'insert-image-file args)
    ;; We don't handle OPERATION, use another handler or the default
    (apply #'image-file-call-underlying operation operation args)))

(defun image-file-call-underlying (function operation &rest args)
  "Call FUNCTION with `image-file-handler' and OPERATION inhibited.
Optional argument ARGS are the arguments to call FUNCTION with."
  (let ((inhibit-file-name-handlers
	 (cons 'image-file-handler
	       (and (eq inhibit-file-name-operation operation)
		    inhibit-file-name-handlers)))
	(inhibit-file-name-operation operation))
    (apply function args)))


177 178
;;;###autoload
(define-minor-mode auto-image-file-mode
179
  "Toggle visiting of image files as images (Auto Image File mode).
180

181
An image file is one whose name has an extension in
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
`image-file-name-extensions', or matches a regexp in
`image-file-name-regexps'."
  :global t
  :group 'image
  ;; Remove existing handler
  (let ((existing-entry
	 (rassq 'image-file-handler file-name-handler-alist)))
    (when existing-entry
      (setq file-name-handler-alist
	    (delq existing-entry file-name-handler-alist))))
  ;; Add new handler, if enabled
  (when auto-image-file-mode
    (push (cons (image-file-name-regexp) 'image-file-handler)
	  file-name-handler-alist)))


Miles Bader's avatar
Miles Bader committed
198 199 200
(provide 'image-file)

;;; image-file.el ends here