1 ;;; smart-yank.el --- A different approach of yank pointer handling -*- lexical-binding: t -*-
3 ;; Copyright (C) 2016 Free Software Foundation, Inc
5 ;; Author: Michael Heerdegen <michael_heerdegen@web.de>
6 ;; Maintainer: Michael Heerdegen <michael_heerdegen@web.de>
7 ;; Created: 14 May 2016
8 ;; Keywords: convenience
9 ;; Compatibility: GNU Emacs 24
11 ;; Package-Requires: ((emacs "24"))
14 ;; This file is not part of GNU Emacs.
16 ;; GNU Emacs is free software: you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation, either version 3 of the License, or
19 ;; (at your option) any later version.
21 ;; GNU Emacs is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
35 ;; This library implements the global minor mode `smart-yank-mode'
36 ;; that changes the way Emacs handles the `kill-ring-yank-pointer' in
37 ;; a way that some people prefer over the default behavior.
39 ;; Normally, only a kill command resets the yank pointer. With
40 ;; `smart-yank-mode' enabled, any command except yank commands resets
43 ;; In addition, when yanking any "older" element from the kill-ring
44 ;; with yank-pop (and not replacing it with a subsequent yank-pop), it
45 ;; is automatically moved to the "first position" so `yank' invoked
46 ;; later will yank this element again.
48 ;; Finally, `yank-pop' (normally bound to M-y) is replaced with
49 ;; `smart-yank-yank-pop' that is a bit more sophisticated:
51 ;; - When _not_ called after a `yank', instead of raising an error
52 ;; like `yank-pop', yank the next-to-the-last kill.
54 ;; - Hit M-y twice in fast succession (delay < 0.2 secs by default)
55 ;; when you got lost. This will remove the yanked text. If you
56 ;; bind a command to `smart-yank-browse-kill-ring-command', this
57 ;; command will be called too (typically something like
58 ;; `browse-kill-ring').
61 ;; Example: you want to manually replace some words in some buffer
62 ;; with a new word "foo". With `smart-yank-mode' enabled, you can do
65 ;; 1. Put "foo" into the kill ring.
66 ;; 2. Move to the next word to be replaced.
68 ;; 4. Back to 2, iterate.
74 ;; Just enable `smart-yank-mode' and you are done.
80 ;;;; Configuration stuff
82 (defgroup smart-yank nil
83 "A different approach of yank pointer handling."
86 (defcustom smart-yank-yank-pop-multikey-delay .2
87 "Max delay between two \\[smart-yank-yank-pop] invocations revealing special behavior.
88 See `smart-yank-yank-pop' for details."
91 (defcustom smart-yank-browse-kill-ring-command nil
92 "Command to invoke when hitting \\[smart-yank-yank-pop] twice (fast)."
93 :type '(choice (const :tag "None" nil)
94 (const browse-kill-ring)
95 (const helm-show-kill-ring)
96 (function :tag "Other Function")))
98 (defvar smart-yank-mode-map
99 (let ((map (make-sparse-keymap)))
100 (define-key map [remap yank-pop] #'smart-yank-yank-pop)
102 "Map used by `smart-yank-mode'.")
107 (defun smart-yank--stopwatch ()
108 "Return a fresh stopwatch.
109 This is a function accepting zero arguments that upon each call
110 will return the time difference from its last call in seconds.
111 When called the first time it will return nil."
112 (let ((last-invocation nil))
114 (prog1 (and last-invocation
115 (time-to-seconds (time-subtract (current-time) last-invocation)))
116 (setq last-invocation (current-time))))))
118 (defun smart-yank-reset-yank-pointer ()
119 (unless (eq last-command #'yank)
120 (setq kill-ring-yank-pointer kill-ring)))
122 (defun smart-yank--before-ad (&rest _args)
123 "Before advice function for `yank'.
125 Reset `kill-ring-yank-pointer'. For yank-pop, move the really
126 yanked text \"to the beginning\" of the kill ring."
127 (unless (eq kill-ring kill-ring-yank-pointer)
128 (let ((last-yank (car kill-ring-yank-pointer)))
130 (setq kill-ring (cons last-yank (delete last-yank kill-ring)))
131 (smart-yank-reset-yank-pointer)))))
133 (let ((r (smart-yank--stopwatch)))
134 (defun smart-yank-yank-pop (&optional arg)
135 "\"smart-yank\"'s private version of `yank-pop'.
137 When called directly after a `yank' command (including itself),
140 If its key was hit two times in fast succession - i.e. with a
141 delay less than `smart-yank-yank-pop-multikey-delay' - delete any
142 yanked text; in addition call
143 `smart-yank-browse-kill-ring-command' when set.
145 When not called after a yank, yank the next-to-the-last
146 `kill-ring' entry; with prefix arg, call the
147 `smart-yank-browse-kill-ring-command'."
149 (let ((diff (funcall r)))
151 ((not (eq last-command 'yank)) (if arg (call-interactively smart-yank-browse-kill-ring-command)
152 (rotate-yank-pointer 1)
155 (> diff smart-yank-yank-pop-multikey-delay))
156 (call-interactively #'yank-pop))
157 (t (funcall (or yank-undo-function #'delete-region)
158 (region-beginning) (region-end))
159 (when smart-yank-browse-kill-ring-command
160 (call-interactively smart-yank-browse-kill-ring-command)))))))
162 (declare-function smart-yank-yank-pop 'smart-yank)
168 (define-minor-mode smart-yank-mode
169 "Alter the behavior of yank commands in several ways.
171 Turning on this mode has the following effects:
173 - Makes any command except yank commands reset the
174 `kill-ring-yank-pointer', instead of only killing commands.
176 - Remaps `yank-pop' to `smart-yank-yank-pop'.
178 - When yanking an older element from the `kill-ring' with
179 \\[smart-yank-yank-pop] (and not replacing it with a subsequent \\[smart-yank-yank-pop]), the
180 element is automatically \"moved to the first position\" of
181 the `kill-ring' so that `yank' invoked later will again yank
185 (advice-add 'yank :before #'smart-yank--before-ad)
186 (advice-remove 'yank #'smart-yank--before-ad)))
189 (provide 'smart-yank)
191 ;;; smart-yank.el ends here