1 ;;; easy-kill.el --- kill & mark things easily -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2013-2014 Free Software Foundation, Inc.
5 ;; Author: Leo Liu <sdl.web@gmail.com>
7 ;; Package-Requires: ((emacs "24") (cl-lib "0.5"))
8 ;; Keywords: killing, convenience
10 ;; URL: https://github.com/leoliu/easy-kill
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
27 ;; `easy-kill' aims to be a drop-in replacement for `kill-ring-save'.
29 ;; To use: (global-set-key [remap kill-ring-save] 'easy-kill)
31 ;; `easy-mark' is similar to `easy-kill' but marks the region
32 ;; immediately. It can be a handy replacement for `mark-sexp' allowing
33 ;; `+'/`-' to do list-wise expanding/shrinking.
35 ;; To use: (global-set-key [remap mark-sexp] 'easy-mark)
37 ;; Please send bug reports or feature requests to:
38 ;; https://github.com/leoliu/easy-kill/issues
44 (eval-when-compile (require 'cl)) ;For `defsetf'.
48 ((fboundp 'set-transient-map) nil)
49 ((fboundp 'set-temporary-overlay-map) ; new in 24.3
50 (defalias 'set-transient-map 'set-temporary-overlay-map))
52 (defun set-transient-map (map &optional keep-pred)
53 (let* ((clearfunsym (make-symbol "clear-temporary-overlay-map"))
54 (overlaysym (make-symbol "t"))
55 (alist (list (cons overlaysym map)))
58 (unless ,(cond ((null keep-pred) nil)
62 (this-command-keys-vector))))
63 (t `(funcall ',keep-pred)))
64 (set ',overlaysym nil) ;Just in case.
65 (remove-hook 'pre-command-hook ',clearfunsym)
66 (setq emulation-mode-map-alists
67 (delq ',alist emulation-mode-map-alists))))))
68 (set overlaysym overlaysym)
69 (fset clearfunsym clearfun)
70 (add-hook 'pre-command-hook clearfunsym)
71 (push alist emulation-mode-map-alists))))))
73 (defcustom easy-kill-alist
80 (?b buffer-file-name))
81 "A list of (CHAR THING APPEND).
82 CHAR is used immediately following `easy-kill' to select THING.
83 APPEND is optional and if non-nil specifies the separator (a
84 string) for appending current selection to previous kill.
86 Note: each element can also be (CHAR . THING) but this is
88 :type '(repeat (list character symbol
89 (choice string (const :tag "None" nil))))
92 (defcustom easy-kill-try-things '(url email line)
93 "A list of things for `easy-kill' to try."
94 :type '(repeat symbol)
97 (defcustom easy-mark-try-things '(url email sexp)
98 "A list of things for `easy-mark' to try."
99 :type '(repeat symbol)
102 (defface easy-kill-selection '((t (:inherit secondary-selection)))
103 "Faced used to highlight kill candidate."
106 (defface easy-kill-origin '((t (:inverse-video t :inherit error)))
107 "Faced used to highlight the origin."
110 (defvar easy-kill-base-map
111 (let ((map (make-sparse-keymap)))
112 (define-key map "-" 'easy-kill-shrink)
113 (define-key map "+" 'easy-kill-expand)
114 (define-key map "=" 'easy-kill-expand)
115 (define-key map "@" 'easy-kill-append)
116 (define-key map [remap set-mark-command] 'easy-kill-mark-region)
117 (define-key map [remap kill-region] 'easy-kill-region)
118 (define-key map [remap keyboard-quit] 'easy-kill-abort)
120 (define-key map (number-to-string d) 'easy-kill-digit-argument))
121 (number-sequence 0 9))
124 (defun easy-kill-map ()
125 "Build the keymap according to `easy-kill-alist'."
126 (let ((map (make-sparse-keymap)))
127 (set-keymap-parent map easy-kill-base-map)
129 ;; (define-key map (vector meta-prefix-char c) 'easy-kill-select)
130 (define-key map (char-to-string c) 'easy-kill-thing))
131 (mapcar 'car easy-kill-alist))
134 (defvar easy-kill-inhibit-message nil)
136 (defun easy-kill-echo (format-string &rest args)
137 "Same as `message' except not writing to *Messages* buffer.
138 Do nothing if `easy-kill-inhibit-message' is non-nil."
139 (unless easy-kill-inhibit-message
140 (let (message-log-max)
141 (apply 'message format-string args))))
143 (defun easy-kill-trim (s &optional how)
144 (let ((wchars "[ \t\n\r\f\v]*"))
146 (`left (and (string-match (concat "\\`" wchars) s)
147 (substring s (match-end 0))))
148 (`right (substring s 0 (string-match-p (concat wchars "\\'") s)))
149 (_ (easy-kill-trim (easy-kill-trim s 'left) 'right)))))
151 (defun easy-kill-fboundp (name)
152 "Like `fboundp' but NAME can be string or symbol.
153 The value is the function's symbol if non-nil."
155 (string (easy-kill-fboundp (intern-soft name)))
156 (symbol (and (fboundp name) name))))
158 (defun easy-kill-pair-to-list (pair)
161 (`(,beg . ,end) (list beg end))
162 (_ (signal 'wrong-type-argument (list pair "Not a dot pair")))))
164 (defun easy-kill-interprogram-cut (text)
165 "Make non-empty TEXT available to other programs."
166 (cl-check-type text string)
167 (and interprogram-cut-function
168 (not (equal text ""))
169 (funcall interprogram-cut-function text)))
171 (defvar easy-kill-candidate nil)
172 (defvar easy-kill-append nil)
173 (defvar easy-kill-mark nil)
175 (defun easy-kill--bounds ()
176 (cons (overlay-start easy-kill-candidate)
177 (overlay-end easy-kill-candidate)))
179 ;;; Note: gv-define-setter not available in 24.1 and 24.2
180 ;; (gv-define-setter easy-kill--bounds (val)
181 ;; (macroexp-let2 macroexp-copyable-p v val
182 ;; `(move-overlay easy-kill-candidate (car ,v) (cdr ,v))))
184 (defsetf easy-kill--bounds () (v)
186 (move-overlay easy-kill-candidate (car tmp) (cdr tmp))))
188 (defmacro easy-kill-get (prop)
189 "Get the value of the kill candidate's property PROP.
190 Use `setf' to change property value."
192 (`start '(overlay-start easy-kill-candidate))
193 (`end '(overlay-end easy-kill-candidate))
194 (`bounds '(easy-kill--bounds))
195 (`buffer '(overlay-buffer easy-kill-candidate))
196 (`properties '(append (list 'start (easy-kill-get start))
197 (list 'end (easy-kill-get end))
198 (overlay-properties easy-kill-candidate)))
199 (_ `(overlay-get easy-kill-candidate ',prop))))
201 (defun easy-kill-init-candidate (n)
202 ;; Manipulate `easy-kill-candidate' directly during initialisation;
203 ;; should use `easy-kill-get' elsewhere.
204 (let ((o (make-overlay (point) (point))))
205 (unless easy-kill-mark
206 (overlay-put o 'face 'easy-kill-selection))
207 (overlay-put o 'origin (point))
208 (overlay-put o 'help-echo #'easy-kill-describe-candidate)
209 ;; Use higher priority to avoid shadowing by, for example,
211 (overlay-put o 'priority 999)
213 (let ((i (make-overlay (point) (point))))
214 (overlay-put i 'priority (1+ (overlay-get o 'priority)))
215 (overlay-put i 'face 'easy-kill-origin)
216 (overlay-put i 'as (propertize " " 'face 'easy-kill-origin))
217 (overlay-put o 'origin-indicator i)))
218 (setq easy-kill-candidate o)
220 ;; Work around http://debbugs.gnu.org/15808; not needed in 24.4.
221 (narrow-to-region (max (point-min) (- (point) 1000))
222 (min (point-max) (+ (point) 1000)))
223 (let ((easy-kill-inhibit-message t))
224 (cl-dolist (thing easy-kill-try-things)
225 (easy-kill-thing thing n)
226 (or (string= (easy-kill-candidate) "")
230 (defun easy-kill-indicate-origin ()
231 (let ((i (easy-kill-get origin-indicator))
232 (origin (easy-kill-get origin)))
234 ((not (overlayp i)) nil)
236 (overlay-put i 'after-string nil))
237 ((memq (char-after origin) '(?\t ?\n))
238 (overlay-put i 'after-string (overlay-get i 'as)))
239 (t (move-overlay i origin (1+ origin))
240 (overlay-put i 'after-string nil)))))
242 (defun easy-kill-candidate ()
243 "Get the kill candidate as a string.
244 If the overlay specified by variable `easy-kill-candidate' has
245 non-zero length, it is the string covered by the overlay.
246 Otherwise, it is the value of the overlay's candidate property."
247 (with-current-buffer (easy-kill-get buffer)
248 (or (pcase (easy-kill-get bounds)
249 (`(,_x . ,_x) (easy-kill-get candidate))
250 (`(,beg . ,end) (filter-buffer-substring beg end)))
253 (defun easy-kill-describe-candidate (&rest _)
254 "Return a string that describes current kill candidate."
255 (let* ((props (cl-loop for k in '(thing start end origin)
256 with all = (easy-kill-get properties)
257 ;; Allow describe-PROP to provide customised
259 for dk = (intern-soft (format "describe-%s" k))
260 for dv = (and dk (plist-get all dk))
261 for v = (or (if (functionp dv) (funcall dv) dv)
263 when v collect (format "%s:\t%s" k v)))
264 (txt (mapconcat #'identity props "\n")))
265 (format "cmd:\t%s\n%s" (if easy-kill-mark "easy-mark" "easy-kill") txt)))
267 (defun easy-kill-adjust-candidate (thing &optional beg end)
268 "Adjust kill candidate to THING, BEG, END.
269 If BEG is a string, shrink the overlay to zero length and set its
270 candidate property instead."
271 (setf (easy-kill-get thing) thing)
273 (setf (easy-kill-get bounds) (cons (point) (point)))
274 (setf (easy-kill-get candidate) beg)
275 (let ((easy-kill-inhibit-message nil))
276 (easy-kill-echo "%s" beg)))
278 (setf (easy-kill-get bounds) (cons (or beg (easy-kill-get start))
279 (or end (easy-kill-get end))))))
280 (cond (easy-kill-mark (easy-kill-mark-region)
281 (easy-kill-indicate-origin))
282 (t (easy-kill-interprogram-cut (easy-kill-candidate)))))
284 (defun easy-kill-save-candidate ()
285 (unless (string= (easy-kill-candidate) "")
286 ;; Don't modify the clipboard here since it is called in
287 ;; `pre-command-hook' per `easy-kill-activate-keymap' and will
288 ;; confuse `yank' if it is current command. Also
289 ;; `easy-kill-adjust-candidate' already did that.
290 (let ((interprogram-cut-function nil)
291 (interprogram-paste-function nil))
292 (kill-new (if (and easy-kill-append kill-ring)
293 (cl-labels ((join (x sep y)
294 (if sep (concat (easy-kill-trim x 'right)
296 (easy-kill-trim y 'left))
298 (join (car kill-ring)
299 (nth 2 (cl-rassoc (easy-kill-get thing)
300 easy-kill-alist :key #'car))
301 (easy-kill-candidate)))
302 (easy-kill-candidate))
306 (defun easy-kill-destroy-candidate ()
307 (let ((hook (make-symbol "easy-kill-destroy-candidate")))
308 (fset hook `(lambda ()
309 (let ((o ,easy-kill-candidate))
311 (let ((i (overlay-get o 'origin-indicator)))
312 (and (overlayp i) (delete-overlay i)))
314 (remove-hook 'post-command-hook ',hook)))
315 ;; Run in `post-command-hook' so that exit commands can still use
316 ;; `easy-kill-candidate'.
317 (add-hook 'post-command-hook hook)))
319 (defun easy-kill-expand ()
321 (easy-kill-thing nil '+))
323 (defun easy-kill-digit-argument (n)
325 (list (- (logand (if (integerp last-command-event)
327 (get last-command-event 'ascii-character))
330 (easy-kill-thing nil n))
332 (defun easy-kill-shrink ()
334 (easy-kill-thing nil '-))
336 ;; Helper for `easy-kill-thing'.
337 (defun easy-kill-thing-forward (n)
338 (when (and (easy-kill-get thing) (/= n 0))
339 (let* ((step (if (cl-minusp n) -1 +1))
340 (thing (easy-kill-get thing))
341 (bounds1 (or (easy-kill-pair-to-list (bounds-of-thing-at-point thing))
342 (list (point) (point))))
343 (start (easy-kill-get start))
344 (end (easy-kill-get end))
345 (front (or (car (cl-set-difference (list end start) bounds1))
349 (new-front (save-excursion
352 (cl-labels ((forward-defun (s)
354 (`-1 (beginning-of-defun 1))
355 (`+1 (end-of-defun 1)))))
357 ;; Work around http://debbugs.gnu.org/17247
358 (if (eq thing 'defun)
360 (forward-thing thing step)))))
362 (pcase (and (/= front new-front)
363 (sort (cons new-front bounds1) #'<))
365 (easy-kill-adjust-candidate thing start end)
368 (defun easy-kill-thing-handler (thing mode)
369 "Get the handler for THING or nil if none is defined.
370 For example, if THING is list and MODE is nxml-mode
371 `nxml:easy-kill-on-list', `easy-kill-on-list:nxml' are checked in
372 order. The former is never defined in this package and is safe
373 for users to customise. If neither is defined continue checking
374 on the parent mode. Finally `easy-kill-on-list' is checked."
375 (cl-labels ((sname (m) (cl-etypecase m
376 (symbol (sname (symbol-name m)))
377 (string (substring m 0 (string-match-p
378 "\\(?:-minor\\)?-mode\\'" m))))))
379 (let ((parent (get mode 'derived-mode-parent)))
380 (or (and mode (or (easy-kill-fboundp
381 (format "%s:easy-kill-on-%s" (sname mode) thing))
383 (format "easy-kill-on-%s:%s" thing (sname mode)))))
384 (and parent (easy-kill-thing-handler thing parent))
385 (easy-kill-fboundp (format "easy-kill-on-%s" thing))))))
387 (defun easy-kill-thing (&optional thing n inhibit-handler)
388 ;; N can be -, + and digits
390 (list (pcase (assq last-command-event easy-kill-alist)
393 (prefix-numeric-value current-prefix-arg)))
394 (let* ((thing (or thing (easy-kill-get thing)))
396 (handler (and (not inhibit-handler)
397 (easy-kill-thing-handler thing major-mode))))
399 (goto-char (easy-kill-get origin)))
401 (handler (funcall handler n))
402 ((or (eq thing (easy-kill-get thing))
404 (easy-kill-thing-forward (pcase n
408 (t (pcase (bounds-of-thing-at-point thing)
409 (`nil (easy-kill-echo "No `%s'" thing))
411 (easy-kill-adjust-candidate thing start end)
412 (easy-kill-thing-forward (1- n))))))
414 (easy-kill-adjust-candidate (easy-kill-get thing)))))
416 (put 'easy-kill-abort 'easy-kill-exit t)
417 (defun easy-kill-abort ()
420 ;; The after-string may interfere with `goto-char'.
421 (overlay-put (easy-kill-get origin-indicator) 'after-string nil)
422 (goto-char (easy-kill-get origin))
423 (setq deactivate-mark t))
426 (put 'easy-kill-region 'easy-kill-exit t)
427 (defun easy-kill-region ()
428 "Kill current selection and exit."
430 (pcase (easy-kill-get bounds)
431 (`(,_x . ,_x) (easy-kill-echo "Empty region"))
432 (`(,beg . ,end) (kill-region beg end))))
434 (put 'easy-kill-mark-region 'easy-kill-exit t)
435 (defun easy-kill-mark-region ()
437 (pcase (easy-kill-get bounds)
439 (easy-kill-echo "Empty region"))
445 (put 'easy-kill-append 'easy-kill-exit t)
446 (defun easy-kill-append ()
448 (setq easy-kill-append t)
449 (when (easy-kill-save-candidate)
450 (easy-kill-interprogram-cut (car kill-ring))
451 (setq deactivate-mark t)
452 (easy-kill-echo "Appended")))
454 (defun easy-kill-exit-p (cmd)
455 (and (symbolp cmd) (get cmd 'easy-kill-exit)))
457 (defun easy-kill-activate-keymap ()
458 (let ((map (easy-kill-map)))
462 ;; Prevent any error from activating the keymap forever.
464 (or (and (not (easy-kill-exit-p this-command))
466 (lookup-key map (this-single-command-keys)))
467 (let ((cmd (key-binding
468 (this-single-command-keys) nil t)))
469 (command-remapping cmd nil (list map)))))
471 (easy-kill-destroy-candidate)
472 (unless (or easy-kill-mark (easy-kill-exit-p this-command))
473 (easy-kill-save-candidate))))
474 (error (message "%s:%s" this-command (error-message-string err))
478 (defun easy-kill (&optional n)
479 "Kill thing at point in the order of region, url, email and line.
480 Temporally activate additional key bindings as follows:
482 letters => select or expand selection according to `easy-kill-alist';
483 0..9 => expand selection by that number;
484 +,=/- => expand or shrink selection;
485 @ => append selection to previous kill;
486 C-w => kill selection;
487 C-SPC => turn selection into an active region;
489 others => save selection and exit."
492 (if (fboundp 'rectangle-mark-mode) ; New in 24.4
494 (kill-ring-save (region-beginning) (region-end) t))
495 (kill-ring-save (region-beginning) (region-end)))
496 (setq easy-kill-mark nil)
497 (setq easy-kill-append (eq last-command 'kill-region))
498 (easy-kill-init-candidate n)
499 (when (zerop (buffer-size))
500 (easy-kill-echo "Warn: `easy-kill' activated in empty buffer"))
501 (easy-kill-activate-keymap)))
504 (defalias 'easy-mark-sexp 'easy-mark
505 "Use `easy-mark' instead. The alias may be removed in future.")
508 (defun easy-mark (&optional n)
509 "Similar to `easy-kill' (which see) but for marking."
511 (let ((easy-kill-try-things easy-mark-try-things))
512 (setq easy-kill-mark t)
513 (easy-kill-init-candidate n)
514 (easy-kill-activate-keymap)
515 (unless (easy-kill-get thing)
516 (setf (easy-kill-get thing) 'sexp)
517 (easy-kill-thing 'sexp n))))
521 ;;; Handler for `buffer-file-name'.
523 (defun easy-kill-on-buffer-file-name (n)
524 "Get `buffer-file-name' or `default-directory'.
525 If N is zero, remove the directory part; -, remove the file name
528 (easy-kill-echo "Not supported in `easy-mark'")
529 (pcase (or buffer-file-name default-directory)
530 (`nil (easy-kill-echo "No `buffer-file-name'"))
531 (file (let* ((file (directory-file-name file))
533 (`- (file-name-directory file))
534 (`0 (file-name-nondirectory file))
536 (easy-kill-adjust-candidate 'buffer-file-name text))))))
538 ;;; Handler for `defun-name'.
540 (defun easy-kill-on-defun-name (_n)
541 "Get current defun name."
543 (easy-kill-echo "Not supported in `easy-mark'")
544 (pcase (add-log-current-defun)
545 (`nil (easy-kill-echo "No `defun-name' at point"))
546 (name (easy-kill-adjust-candidate 'defun-name name)))))
548 ;;; Handler for `url'.
550 (defun easy-kill-on-url (&optional _n)
551 "Get url at point or from char properties.
552 Char properties `help-echo', `shr-url' and `w3m-href-anchor' are
554 (if (or easy-kill-mark (bounds-of-thing-at-point 'url))
555 (easy-kill-thing 'url nil t)
556 (cl-labels ((get-url (text)
560 (pcase (bounds-of-thing-at-point 'url)
561 (`(,beg . ,end) (buffer-substring beg end)))))))
562 (cl-dolist (p '(help-echo shr-url w3m-href-anchor))
563 (pcase (get-char-property-and-overlay (point) p)
565 (pcase (or (get-url text)
566 (get-url (and ov (overlay-get ov p))))
567 ((and url (guard url))
568 (easy-kill-adjust-candidate 'url url)
569 (cl-return url)))))))))
571 ;;; Handler for `sexp' and `list'.
573 (defvar up-list-fn) ; Dynamically bound
575 (defun easy-kill-backward-up ()
576 (let ((ppss (syntax-ppss)))
579 (funcall (or (bound-and-true-p up-list-fn) #'up-list) -1)
580 ;; `up-list' may jump to another string.
581 (when (and (nth 3 ppss) (< (point) (nth 8 ppss)))
582 (goto-char (nth 8 ppss))))
583 (scan-error (and (nth 3 ppss) (goto-char (nth 8 ppss)))))))
585 (defun easy-kill-forward-down (point &optional bound)
588 (easy-kill-backward-up)
589 (backward-prefix-chars)
590 (if (and (or (not bound) (> (point) bound))
592 (easy-kill-forward-down (point) bound)
594 (scan-error (goto-char point))))
596 (defun easy-kill-bounds-of-list (n)
599 (`+ (goto-char (easy-kill-get start))
600 (easy-kill-backward-up))
601 (`- (easy-kill-forward-down (point) (easy-kill-get start)))
602 (_ (error "Unsupported argument `%s'" n)))
603 (bounds-of-thing-at-point 'sexp)))
605 (defun easy-kill-on-list (n)
608 (pcase (easy-kill-bounds-of-list n)
610 (easy-kill-adjust-candidate 'list beg end))))
611 (_ (easy-kill-thing 'list n t))))
613 (defun easy-kill-on-sexp (n)
616 (unwind-protect (easy-kill-thing 'list n)
617 (setf (easy-kill-get thing) 'sexp)))
618 (_ (easy-kill-thing 'sexp n t))))
620 ;;; nxml support for list-wise +/-
622 (defvar nxml-sexp-element-flag)
624 (defun easy-kill-on-list:nxml (n)
625 (let ((nxml-sexp-element-flag t)
626 (up-list-fn 'nxml-up-element))
629 (pcase (easy-kill-bounds-of-list n)
630 (`(,beg . ,end) (easy-kill-adjust-candidate 'list beg end))))
631 ((eq 'list (easy-kill-get thing))
632 (let ((new-end (save-excursion
633 (goto-char (easy-kill-get end))
636 (when (and new-end (/= new-end (easy-kill-get end)))
637 (easy-kill-adjust-candidate 'list nil new-end))))
639 (ignore-errors (easy-kill-backward-up))
640 (easy-kill-thing 'sexp n t)
641 (setf (easy-kill-get thing) 'list))))))
643 ;;; js2 support for list-wise +/-
645 (defun easy-kill-find-js2-node (beg end &optional inner)
646 (eval-and-compile (require 'js2-mode nil t))
647 (let* ((node (js2-node-at-point))
650 (if (or (js2-ast-root-p node)
651 (and (<= (js2-node-abs-pos node) beg)
652 (>= (js2-node-abs-end node) end)
654 (not (and (= (js2-node-abs-pos node) beg)
655 (= (js2-node-abs-end node) end))))))
658 node (js2-node-parent node))
660 (if inner last-node node)))
662 (defun easy-kill-on-list:js2 (n)
665 (easy-kill-find-js2-node (easy-kill-get start)
668 ((guard (eq 'list (easy-kill-get thing)))
669 (error "List forward not supported in js2-mode"))
670 (_ (js2-node-at-point)))))
671 (easy-kill-adjust-candidate 'list
672 (js2-node-abs-pos node)
673 (js2-node-abs-end node))
674 (setf (easy-kill-get describe-thing)
675 ;; Also used by `sexp' so delay computation until needed.
677 (format "%s (%s)" (easy-kill-get thing) (js2-node-short-name node))))
678 (easy-kill-echo "%s" (js2-node-short-name node))))
681 ;;; easy-kill.el ends here