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 (defalias 'smart-yank-yank-pop
134 (let ((r (smart-yank--stopwatch)))
135 (lambda (&optional arg)
136 "\"smart-yank\"'s private version of `yank-pop'.
138 When called directly after a `yank' command (including itself),
141 If its key was hit two times in fast succession - i.e. with a
142 delay less than `smart-yank-yank-pop-multikey-delay' - delete any
143 yanked text; in addition call
144 `smart-yank-browse-kill-ring-command' when set.
146 When not called after a yank, yank the next-to-the-last
147 `kill-ring' entry; with prefix arg, call the
148 `smart-yank-browse-kill-ring-command'."
150 (let ((diff (funcall r)))
152 ((not (eq last-command 'yank)) (if arg (call-interactively smart-yank-browse-kill-ring-command)
153 (rotate-yank-pointer 1)
156 (> diff smart-yank-yank-pop-multikey-delay))
157 (call-interactively #'yank-pop))
158 (t (funcall (or yank-undo-function #'delete-region)
159 (region-beginning) (region-end))
160 (when smart-yank-browse-kill-ring-command
161 (call-interactively smart-yank-browse-kill-ring-command))))))))
163 (declare-function smart-yank-yank-pop 'smart-yank)
169 (define-minor-mode smart-yank-mode
170 "Alter the behavior of yank commands in several ways.
172 Turning on this mode has the following effects:
174 - Makes any command except yank commands reset the
175 `kill-ring-yank-pointer', instead of only killing commands.
177 - Remaps `yank-pop' to `smart-yank-yank-pop'.
179 - When yanking an older element from the `kill-ring' with
180 \\[smart-yank-yank-pop] (and not replacing it with a subsequent \\[smart-yank-yank-pop]), the
181 element is automatically \"moved to the first position\" of
182 the `kill-ring' so that `yank' invoked later will again yank
186 (advice-add 'yank :before #'smart-yank--before-ad)
187 (advice-remove 'yank #'smart-yank--before-ad)))
190 (provide 'smart-yank)
192 ;;; smart-yank.el ends here