rfn-eshadow.el 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. ;;; rfn-eshadow.el --- Highlight `shadowed' part of read-file-name input text
  2. ;;
  3. ;; Copyright (C) 2000-2012 Free Software Foundation, Inc.
  4. ;;
  5. ;; Author: Miles Bader <miles@gnu.org>
  6. ;; Keywords: convenience minibuffer
  7. ;; Package: emacs
  8. ;; This file is part of GNU Emacs.
  9. ;; GNU Emacs is free software: you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or
  12. ;; (at your option) any later version.
  13. ;; GNU Emacs is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  19. ;;; Commentary:
  20. ;;
  21. ;; Defines the mode `file-name-shadow-mode'.
  22. ;;
  23. ;; The `read-file-name' function passes its result through
  24. ;; `substitute-in-file-name', so any part of the string preceding
  25. ;; multiple slashes (or a drive indicator on MS-DOS/MS-Windows) is
  26. ;; ignored.
  27. ;;
  28. ;; If `file-name-shadow-mode' is active, any part of the
  29. ;; minibuffer text that would be ignored because of this is given the
  30. ;; properties in `file-name-shadow-properties', which may
  31. ;; be used to make the ignored text invisible, dim, etc.
  32. ;;
  33. ;;; Code:
  34. ;;; Customization
  35. (defconst file-name-shadow-properties-custom-type
  36. '(list
  37. (checklist :inline t
  38. (const :tag "Invisible"
  39. :doc "Make shadowed part of filename invisible"
  40. :format "%t%n%h"
  41. :inline t
  42. (invisible t intangible t))
  43. (list :inline t
  44. :format "%v"
  45. :tag "Face"
  46. :doc "Display shadowed part of filename using a different face"
  47. (const :format "" face)
  48. (face :value file-name-shadow))
  49. (list :inline t
  50. :format "%t: %v%h"
  51. :tag "Brackets"
  52. ;; Note the 4 leading spaces in the doc string;
  53. ;; this is hack to get around the fact that the
  54. ;; newline after the second string widget comes
  55. ;; from the string widget, and doesn't indent
  56. ;; correctly. We could use a :size attribute to
  57. ;; make the second string widget not have a
  58. ;; terminating newline, but this makes it impossible
  59. ;; to enter trailing whitespace, and it's desirable
  60. ;; that it be possible.
  61. :doc " Surround shadowed part of filename with brackets"
  62. (const :format "" before-string)
  63. (string :format "%v" :size 4 :value "{")
  64. (const :format "" after-string)
  65. ;; see above about why the 2nd string doesn't use :size
  66. (string :format " and: %v" :value "} "))
  67. (list :inline t
  68. :format "%t: %v%n%h"
  69. :tag "String"
  70. :doc "Display a string instead of the shadowed part of filename"
  71. (const :format "" display)
  72. (string :format "%v" :size 15 :value "<...ignored...>"))
  73. (const :tag "Avoid"
  74. :doc "Try to keep cursor out of shadowed part of filename"
  75. :format "%t%n%h"
  76. :inline t
  77. (field shadow)))
  78. (repeat :inline t
  79. :tag "Other Properties"
  80. (list :inline t
  81. :format "%v"
  82. (symbol :tag "Property")
  83. (sexp :tag "Value")))))
  84. (defcustom file-name-shadow-properties
  85. ;; FIXME: should we purecopy this?
  86. '(face file-name-shadow field shadow)
  87. "Properties given to the `shadowed' part of a filename in the minibuffer.
  88. Only used when `file-name-shadow-mode' is active.
  89. If Emacs is not running under a window system,
  90. `file-name-shadow-tty-properties' is used instead."
  91. :type file-name-shadow-properties-custom-type
  92. :group 'minibuffer
  93. :version "22.1")
  94. (defcustom file-name-shadow-tty-properties
  95. (purecopy '(before-string "{" after-string "} " field shadow))
  96. "Properties given to the `shadowed' part of a filename in the minibuffer.
  97. Only used when `file-name-shadow-mode' is active and Emacs
  98. is not running under a window-system; if Emacs is running under a window
  99. system, `file-name-shadow-properties' is used instead."
  100. :type file-name-shadow-properties-custom-type
  101. :group 'minibuffer
  102. :version "22.1")
  103. (defface file-name-shadow
  104. '((t :inherit shadow))
  105. "Face used by `file-name-shadow-mode' for the shadow."
  106. :group 'minibuffer
  107. :version "22.1")
  108. (defvar rfn-eshadow-setup-minibuffer-hook nil
  109. "Minibuffer setup functions from other packages.")
  110. (defvar rfn-eshadow-update-overlay-hook nil
  111. "Customer overlay functions from other packages")
  112. ;;; Internal variables
  113. ;; A list of minibuffers to which we've added a post-command-hook.
  114. (defvar rfn-eshadow-frobbed-minibufs nil)
  115. ;; An overlay covering the shadowed part of the filename (local to the
  116. ;; minibuffer).
  117. (defvar rfn-eshadow-overlay)
  118. (make-variable-buffer-local 'rfn-eshadow-overlay)
  119. ;;; Hook functions
  120. ;; This function goes on minibuffer-setup-hook
  121. (defun rfn-eshadow-setup-minibuffer ()
  122. "Set up a minibuffer for `file-name-shadow-mode'.
  123. The prompt and initial input should already have been inserted."
  124. (when minibuffer-completing-file-name
  125. (setq rfn-eshadow-overlay
  126. (make-overlay (minibuffer-prompt-end) (minibuffer-prompt-end)))
  127. ;; Give rfn-eshadow-overlay the user's props.
  128. (let ((props
  129. (if window-system
  130. file-name-shadow-properties
  131. file-name-shadow-tty-properties)))
  132. (while props
  133. (overlay-put rfn-eshadow-overlay (pop props) (pop props))))
  134. ;; Turn on overlay evaporation so that we don't have to worry about
  135. ;; odd effects when the overlay sits empty at the beginning of the
  136. ;; minibuffer.
  137. (overlay-put rfn-eshadow-overlay 'evaporate t)
  138. ;; Add our post-command hook, and make sure can remove it later.
  139. (add-to-list 'rfn-eshadow-frobbed-minibufs (current-buffer))
  140. (add-hook 'post-command-hook #'rfn-eshadow-update-overlay nil t)
  141. ;; Run custom hook
  142. (run-hooks 'rfn-eshadow-setup-minibuffer-hook)))
  143. (defsubst rfn-eshadow-sifn-equal (goal pos)
  144. (equal goal (condition-case nil
  145. (substitute-in-file-name
  146. (buffer-substring-no-properties pos (point-max)))
  147. ;; `substitute-in-file-name' can fail on partial input.
  148. (error nil))))
  149. ;; post-command-hook to update overlay
  150. (defun rfn-eshadow-update-overlay ()
  151. "Update `rfn-eshadow-overlay' to cover shadowed part of minibuffer input.
  152. This is intended to be used as a minibuffer `post-command-hook' for
  153. `file-name-shadow-mode'; the minibuffer should have already
  154. been set up by `rfn-eshadow-setup-minibuffer'."
  155. (condition-case nil
  156. (let ((goal (substitute-in-file-name (minibuffer-contents)))
  157. (mid (overlay-end rfn-eshadow-overlay))
  158. (start (minibuffer-prompt-end))
  159. (end (point-max))
  160. (non-essential t))
  161. (unless
  162. ;; Catch the common case where the shadow does not need to move.
  163. (and mid
  164. (or (eq mid end)
  165. (not (rfn-eshadow-sifn-equal goal (1+ mid))))
  166. (or (eq mid start)
  167. (rfn-eshadow-sifn-equal goal mid)))
  168. ;; Binary search for the greatest position still equivalent to
  169. ;; the whole.
  170. (while (or (< (1+ start) end)
  171. (if (and (< (1+ end) (point-max))
  172. (rfn-eshadow-sifn-equal goal (1+ end)))
  173. ;; (SIFN end) != goal, but (SIFN (1+end)) == goal,
  174. ;; We've reached a discontinuity: this can happen
  175. ;; e.g. if `end' point to "/:...".
  176. (setq start (1+ end) end (point-max))))
  177. (setq mid (/ (+ start end) 2))
  178. (if (rfn-eshadow-sifn-equal goal mid)
  179. (setq start mid)
  180. (setq end mid)))
  181. (move-overlay rfn-eshadow-overlay (minibuffer-prompt-end) start))
  182. ;; Run custom hook
  183. (run-hooks 'rfn-eshadow-update-overlay-hook))
  184. ;; `substitute-in-file-name' can fail on partial input.
  185. (error nil)))
  186. (define-minor-mode file-name-shadow-mode
  187. "Toggle file-name shadowing in minibuffers (File-Name Shadow mode).
  188. With a prefix argument ARG, enable File-Name Shadow mode if ARG
  189. is positive, and disable it otherwise. If called from Lisp,
  190. enable the mode if ARG is omitted or nil.
  191. File-Name Shadow mode is a global minor mode. When enabled, any
  192. part of a filename being read in the minibuffer that would be
  193. ignored (because the result is passed through
  194. `substitute-in-file-name') is given the properties in
  195. `file-name-shadow-properties', which can be used to make that
  196. portion dim, invisible, or otherwise less visually noticeable."
  197. :global t
  198. ;; We'd like to use custom-initialize-set here so the setup is done
  199. ;; before dumping, but at the point where the defcustom is evaluated,
  200. ;; the corresponding function isn't defined yet, so
  201. ;; custom-initialize-set signals an error.
  202. :initialize 'custom-initialize-delay
  203. :init-value t
  204. :group 'minibuffer
  205. :version "22.1"
  206. (if file-name-shadow-mode
  207. ;; Enable the mode
  208. (add-hook 'minibuffer-setup-hook 'rfn-eshadow-setup-minibuffer)
  209. ;; Disable the mode
  210. (remove-hook 'minibuffer-setup-hook 'rfn-eshadow-setup-minibuffer)
  211. ;; Remove our entry from any post-command-hook variable's it's still in
  212. (dolist (minibuf rfn-eshadow-frobbed-minibufs)
  213. (with-current-buffer minibuf
  214. (remove-hook 'post-command-hook #'rfn-eshadow-update-overlay t)))
  215. (setq rfn-eshadow-frobbed-minibufs nil)))
  216. (provide 'rfn-eshadow)
  217. ;;; rfn-eshadow.el ends here