1 ;;; easy-kill.el --- kill things easily -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2013 Leo Liu
5 ;; Author: Leo Liu <sdl.web@gmail.com>
7 ;; Keywords: convenience
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ;; `easy-kill' aims to be a drop-in replacement for `kill-ring-save'.
27 ;; To use: (global-set-key "\M-w" 'easy-kill)
31 (eval-when-compile (require 'cl))
34 (defcustom easy-kill-alist
40 (?b . buffer-file-name))
41 "A list of (Key . THING)."
42 :type '(repeat (cons character symbol))
45 (defun easy-kill-map ()
46 (let ((map (make-sparse-keymap)))
47 (define-key map "-" 'easy-kill-shrink)
48 (define-key map "+" 'easy-kill-enlarge)
49 (define-key map "=" 'easy-kill-enlarge)
51 (define-key map (number-to-string d) 'easy-kill-digit-argument))
52 (number-sequence 0 9))
54 ;; (define-key map (vector meta-prefix-char c) 'easy-kill-select)
55 (define-key map (char-to-string c) 'easy-kill-thing))
56 (mapcar 'car easy-kill-alist))
59 (defface easy-kill-face '((t (:inherit 'secondary-selection)))
60 "Faced used to highlight kill candidate."
63 (defun easy-kill-message-nolog (format-string &rest args)
64 "Same as `message' except not writing to *Messages* buffer."
65 (let (message-log-max)
66 (apply 'message format-string args)))
68 (defun easy-kill-strip-trailing (s)
70 (if (string-match "[ \t\f\r\n]*\\'" s)
71 (substring s 0 (match-beginning 0))
72 (error "`string-match' failed in `easy-kill-strip'")))
75 (defvar easy-kill-candidate nil)
77 (defun easy-kill-candidate ()
78 "Get the kill candidate as a string.
79 If the overlay specified by variable `easy-kill-candidate' has
80 non-zero length, it is the string covered by the overlay.
81 Otherwise, it is the value of the overlay's candidate property."
82 (easy-kill-strip-trailing
83 (if (/= (overlay-start easy-kill-candidate)
84 (overlay-end easy-kill-candidate))
85 (buffer-substring (overlay-start easy-kill-candidate)
86 (overlay-end easy-kill-candidate))
87 (overlay-get easy-kill-candidate 'candidate))))
89 (defun easy-kill-adjust-candidate (thing &optional beg end)
90 "Adjust kill candidate to THING, BEG, END.
91 If BEG is a string, shring the overlay to zero length and set its
92 candidate property instead."
93 (let ((o easy-kill-candidate))
94 (overlay-put o 'thing thing)
97 (move-overlay o (point) (point))
98 (overlay-put o 'candidate beg)
99 (easy-kill-message-nolog "%s" beg))
100 (move-overlay o (or beg (overlay-start o)) (or end (overlay-end 0)))))
101 (and interprogram-cut-function
102 (funcall interprogram-cut-function (easy-kill-candidate))))
104 (defun easy-kill-enlarge (n)
106 (let ((thing (overlay-get easy-kill-candidate 'thing)))
108 (if (get thing 'easy-kill-enlarge)
109 (funcall (get thing 'easy-kill-enlarge) n)
110 (let ((direction (if (minusp n) -1 +1))
111 (start (overlay-start easy-kill-candidate))
112 (end (overlay-end easy-kill-candidate)))
118 (forward-thing thing direction)
119 (when (<= (point) start)
120 (forward-thing thing 1)
122 (when (/= end (point))
123 (easy-kill-adjust-candidate thing nil (point))
126 (defun easy-kill-shrink (n)
128 (easy-kill-enlarge (- n)))
130 (defun easy-kill-digit-argument (&optional n)
132 (list (- (logand (if (integerp last-command-event)
134 (get last-command-event 'ascii-character))
137 (easy-kill-thing (overlay-get easy-kill-candidate 'thing) n))
139 (defun easy-kill-thing (thing &optional n inhibit-handler)
141 (list (cdr (assoc (car (last (listify-key-sequence
142 (single-key-description last-command-event))))
144 (prefix-numeric-value current-prefix-arg)))
147 ((and (not inhibit-handler)
148 (intern-soft (format "easy-kill-on-%s" thing)))
149 (funcall (intern-soft (format "easy-kill-on-%s" thing)) n))
150 ((eq thing (overlay-get easy-kill-candidate 'thing))
151 (easy-kill-enlarge n))
152 (t (let ((bounds (bounds-of-thing-at-point thing)))
154 (when (called-interactively-p 'interact)
155 (easy-kill-message-nolog "No `%s'" thing))
156 (easy-kill-adjust-candidate thing (car bounds) (cdr bounds))
157 (easy-kill-enlarge (1- n))))))))
159 (defun easy-kill-activate-keymap ()
160 (let ((map (easy-kill-map)))
161 (set-temporary-overlay-map
164 ;; When any error happens the keymap is active forever.
166 (or (let ((cmd (lookup-key map (this-command-keys))))
168 (if (and (numberp cmd)
169 universal-argument-num-events
170 (> (length (this-command-keys))
171 universal-argument-num-events))
172 (lookup-key map (substring (this-command-keys)
173 universal-argument-num-events))
175 (when easy-kill-candidate
176 ;; Do not modify the clipboard here because it will
177 ;; intercept pasting from other programs and
178 ;; `easy-kill-remember' already did the work.
179 (let ((interprogram-cut-function nil)
180 (interprogram-paste-function nil)
181 (candidate (easy-kill-candidate)))
182 (unless(member candidate '(nil ""))
183 (kill-new candidate)))
184 (delete-overlay easy-kill-candidate)
185 (setq easy-kill-candidate nil)
189 (defun easy-kill (&optional n)
190 "Kill thing at point in the order of region, url, email and line."
192 (setq easy-kill-candidate
193 (let ((o (make-overlay (point) (point))))
194 (overlay-put o 'face 'easy-kill-face)
195 ;; Use higher priority to avoid shadowing by, for example,
197 (overlay-put o 'priority 999)
199 (setq deactivate-mark t)
200 (dolist (thing (if (use-region-p)
201 '(region url email line)
203 (easy-kill-thing thing n)
204 (when (overlay-get easy-kill-candidate 'thing)
206 (easy-kill-activate-keymap))
210 (put 'region 'bounds-of-thing-at-point
211 (lambda () (cons (region-beginning) (region-end))))
213 (defun easy-kill-on-buffer-file-name (n)
214 "Get `buffer-file-name' or `default-directory'.
215 If N is zero, remove the directory part; negative, remove the
216 file name party; positive, full path."
217 (let ((file (or buffer-file-name default-directory)))
219 (let* ((file (directory-file-name file))
221 ((zerop n) (file-name-nondirectory file))
223 (t (file-name-directory file)))))
224 (easy-kill-adjust-candidate 'buffer-file-name text)))))
226 (put 'buffer-file-name 'easy-kill-enlarge 'easy-kill-on-buffer-file-name)
228 (defun easy-kill-on-url (&optional _n)
229 "Get url at point or from char properties.
230 Char properties `help-echo', `shr-url' and `w3m-href-anchor' are
232 (if (bounds-of-thing-at-point 'url)
233 (easy-kill-thing 'url nil t)
234 (let ((get-url (lambda (text)
238 (and (bounds-of-thing-at-point 'url)
239 (thing-at-point 'url)))))))
240 (dolist (p '(help-echo shr-url w3m-href-anchor))
241 (pcase-let* ((`(,text . ,ov)
242 (get-char-property-and-overlay (point) p))
243 (url (or (funcall get-url text)
245 (and ov (overlay-get ov p))))))
247 (easy-kill-adjust-candidate 'url url)
251 ;;; easy-kill.el ends here