|
|
- ;;; helm-ring.el --- kill-ring, mark-ring, and register browsers for helm. -*- lexical-binding: t -*-
-
- ;; Copyright (C) 2012 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
-
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
-
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Code:
-
- (require 'cl-lib)
- (require 'helm)
- (require 'helm-utils)
- (require 'helm-help)
- (require 'helm-elisp)
-
- (declare-function undo-tree-restore-state-from-register "ext:undo-tree.el" (register))
-
- (defgroup helm-ring nil
- "Ring related Applications and libraries for Helm."
- :group 'helm)
-
- (defcustom helm-kill-ring-threshold 3
- "Minimum length of a candidate to be listed by `helm-source-kill-ring'."
- :type 'integer
- :group 'helm-ring)
-
- (defcustom helm-kill-ring-max-offset 400
- "Max number of chars displayed per candidate in kill-ring browser.
- When `t', don't truncate candidate, show all.
- By default it is approximatively the number of bits contained in five lines
- of 80 chars each i.e 80*5.
- Note that if you set this to nil multiline will be disabled, i.e you
- will not have anymore separators between candidates."
- :type '(choice (const :tag "Disabled" t)
- (integer :tag "Max candidate offset"))
- :group 'helm-ring)
-
- (defcustom helm-kill-ring-actions
- '(("Yank marked" . helm-kill-ring-action-yank)
- ("Delete marked" . helm-kill-ring-action-delete))
- "List of actions for kill ring source."
- :group 'helm-ring
- :type '(alist :key-type string :value-type function))
-
- (defcustom helm-kill-ring-separator "\n"
- "The separator used to separate marked candidates when yanking."
- :group 'helm-ring
- :type 'string)
-
- (defcustom helm-register-max-offset 160
- "Max size of string register entries before truncating."
- :group 'helm-ring
- :type 'integer)
- ;;; Kill ring
- ;;
- ;;
- (defvar helm-kill-ring-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map helm-map)
- (define-key map (kbd "M-y") 'helm-next-line)
- (define-key map (kbd "M-u") 'helm-previous-line)
- (define-key map (kbd "M-D") 'helm-kill-ring-delete)
- (define-key map (kbd "C-]") 'helm-kill-ring-toggle-truncated)
- (define-key map (kbd "C-c C-k") 'helm-kill-ring-kill-selection)
- (define-key map (kbd "C-c d") 'helm-kill-ring-run-persistent-delete)
- map)
- "Keymap for `helm-show-kill-ring'.")
-
- (defvar helm-source-kill-ring
- (helm-build-sync-source "Kill Ring"
- :init (lambda ()
- (helm-attrset 'last-command last-command)
- (helm-attrset 'multiline helm-kill-ring-max-offset))
- :candidates #'helm-kill-ring-candidates
- :filtered-candidate-transformer #'helm-kill-ring-transformer
- :action 'helm-kill-ring-actions
- :persistent-action 'ignore
- :help-message 'helm-kill-ring-help-message
- :persistent-help "DoNothing"
- :keymap helm-kill-ring-map
- :migemo t
- :multiline 'helm-kill-ring-max-offset
- :group 'helm-ring)
- "Source for browse and insert contents of kill-ring.")
-
- (defun helm-kill-ring-candidates ()
- (cl-loop with cands = (helm-fast-remove-dups kill-ring :test 'equal)
- for kill in (if (eq (helm-attr 'last-command) 'yank)
- (cdr cands)
- cands)
- unless (or (< (length kill) helm-kill-ring-threshold)
- (string-match "\\`[\n[:blank:]]+\\'" kill))
- collect kill))
-
- (defun helm-kill-ring-transformer (candidates _source)
- "Ensure CANDIDATES are not read-only."
- (cl-loop for i in candidates
- when (get-text-property 0 'read-only i)
- do (set-text-properties 0 (length i) '(read-only nil) i)
- collect i))
-
- (defvar helm-kill-ring--truncated-flag nil)
- (defun helm-kill-ring-toggle-truncated ()
- "Toggle truncated view of candidates in helm kill-ring browser."
- (interactive)
- (with-helm-alive-p
- (setq helm-kill-ring--truncated-flag (not helm-kill-ring--truncated-flag))
- (let* ((cur-cand (helm-get-selection))
- (presel-fn (lambda ()
- (helm-kill-ring--preselect-fn cur-cand))))
- (helm-attrset 'multiline
- (if helm-kill-ring--truncated-flag
- 15000000
- helm-kill-ring-max-offset))
- (helm-update presel-fn))))
- (put 'helm-kill-ring-toggle-truncated 'helm-only t)
-
- (defun helm-kill-ring-kill-selection ()
- "Store the real value of candidate in kill-ring.
- Same as `helm-kill-selection-and-quit' called with a prefix arg."
- (interactive)
- (helm-kill-selection-and-quit t))
- (put 'helm-kill-ring-kill-selection 'helm-only t)
-
- (defun helm-kill-ring--preselect-fn (candidate)
- "Internal, used to preselect CANDIDATE when toggling truncated view."
- ;; Preselection by regexp may not work if candidate is huge, so walk
- ;; the helm buffer until selection is on CANDIDATE.
- (helm-awhile (condition-case-unless-debug nil
- (and (not (helm-pos-header-line-p))
- (helm-get-selection))
- (error nil))
- (if (string= it candidate)
- (cl-return)
- (helm-next-line))))
-
- (defun helm-kill-ring-action-yank (_str)
- "Insert concatenated marked candidates in current-buffer.
-
- When two prefix args are given prompt to choose separator, otherwise
- use `helm-kill-ring-separator' as default."
- (let ((marked (helm-marked-candidates))
- (sep (if (equal helm-current-prefix-arg '(16))
- (read-string "Separator: ")
- helm-kill-ring-separator)))
- (helm-kill-ring-action-yank-1
- (cl-loop for c in (butlast marked)
- concat (concat c sep) into str
- finally return (concat str (car (last marked)))))))
-
- (defun helm-kill-ring-action-yank-1 (str)
- "Insert STR in `kill-ring' and set STR to the head.
-
- When called with a prefix arg, point and mark are exchanged without
- activating region.
- If this action is executed just after `yank',
- replace with STR as yanked string."
- (let ((yank-fn (lambda (&optional before yank-pop)
- (insert-for-yank str)
- ;; Set the window start back where it was in
- ;; the yank command, if possible.
- (when yank-pop
- (set-window-start (selected-window) yank-window-start t))
- (when (or (equal helm-current-prefix-arg '(4)) before)
- ;; Same as exchange-point-and-mark but without
- ;; activating region.
- (goto-char (prog1 (mark t)
- (set-marker (mark-marker)
- (point)
- helm-current-buffer)))))))
- ;; Prevent inserting and saving highlighted items.
- (set-text-properties 0 (length str) nil str)
- (with-helm-current-buffer
- (unwind-protect
- (progn
- (setq kill-ring (delete str kill-ring))
- ;; Adding a `delete-selection' property
- ;; to `helm-kill-ring-action' is not working
- ;; because `this-command' will be `helm-maybe-exit-minibuffer',
- ;; so use this workaround (Issue #1520).
- (when (and (region-active-p) delete-selection-mode)
- (delete-region (region-beginning) (region-end)))
- (if (not (eq (helm-attr 'last-command helm-source-kill-ring) 'yank))
- (progn
- ;; Ensure mark is at beginning of inserted text.
- (push-mark)
- ;; When yanking in a helm minibuffer we need a small
- ;; delay to detect the mark in previous minibuffer. [1]
- (run-with-timer 0.01 nil yank-fn))
- ;; from `yank-pop'
- (let ((inhibit-read-only t)
- (before (< (point) (mark t))))
- (if before
- (funcall (or yank-undo-function 'delete-region) (point) (mark t))
- (funcall (or yank-undo-function 'delete-region) (mark t) (point)))
- (setq yank-undo-function nil)
- (set-marker (mark-marker) (point) helm-current-buffer)
- ;; Same as [1] but use the same mark and point as in
- ;; the initial yank according to BEFORE even if no
- ;; prefix arg is given.
- (run-with-timer 0.01 nil yank-fn before 'pop))))
- (kill-new str)))))
- (define-obsolete-function-alias 'helm-kill-ring-action 'helm-kill-ring-action-yank "2.4.0")
-
- (defun helm-kill-ring-action-delete (_candidate)
- "Delete marked candidates from `kill-ring'."
- (cl-loop for c in (helm-marked-candidates)
- do (setq kill-ring
- (delete c kill-ring))))
-
- (defun helm-kill-ring-persistent-delete (_candidate)
- (unwind-protect
- (cl-loop for c in (helm-marked-candidates)
- do (progn
- (helm-preselect (format "^%s" (regexp-quote c)))
- (setq kill-ring (delete c kill-ring))
- (helm-delete-current-selection)
- (helm--remove-marked-and-update-mode-line c)))
- (with-helm-buffer
- (setq helm-marked-candidates nil
- helm-visible-mark-overlays nil))
- (helm-force-update (helm-aif (helm-get-selection nil t) (regexp-quote it)))))
-
- (defun helm-kill-ring-run-persistent-delete ()
- "Delete current candidate without quitting."
- (interactive)
- (with-helm-alive-p
- (helm-attrset 'quick-delete '(helm-kill-ring-persistent-delete . never-split))
- (helm-execute-persistent-action 'quick-delete)))
- (put 'helm-kill-ring-run-persistent-delete 'helm-only t)
-
- (defun helm-kill-ring-delete ()
- "Delete marked candidates from `kill-ring'.
-
- This is a command for `helm-kill-ring-map'."
- (interactive)
- (with-helm-alive-p
- (helm-exit-and-execute-action 'helm-kill-ring-action-delete)))
- (put 'helm-kill-ring-delete 'helm-only t)
-
- ;;;; <Mark ring>
- ;; DO NOT use these sources with other sources use
- ;; the commands `helm-mark-ring', `helm-global-mark-ring' or
- ;; `helm-all-mark-rings' instead.
-
- (defun helm-mark-ring-line-string-at-pos (pos)
- "Return line string at position POS."
- (save-excursion
- (goto-char pos)
- (forward-line 0)
- (let ((line (car (split-string (thing-at-point 'line) "[\n\r]"))))
- (remove-text-properties 0 (length line) '(read-only) line)
- (if (string= "" line)
- "<EMPTY LINE>"
- line))))
-
- (defun helm-mark-ring-get-candidates ()
- (with-helm-current-buffer
- (cl-loop with marks = (if (mark t)
- (cons (mark-marker) mark-ring)
- mark-ring)
- for marker in marks
- with max-line-number = (line-number-at-pos (point-max))
- with width = (length (number-to-string max-line-number))
- for m = (format (concat "%" (number-to-string width) "d: %s")
- (line-number-at-pos marker)
- (helm-mark-ring-line-string-at-pos marker))
- unless (and recip (assoc m recip))
- collect (cons m marker) into recip
- finally return recip)))
-
- (defun helm-mark-ring-default-action (candidate)
- (let ((target (copy-marker candidate)))
- (helm-aif (marker-buffer candidate)
- (progn
- (switch-to-buffer it)
- (helm-log-run-hook 'helm-goto-line-before-hook)
- (helm-match-line-cleanup)
- (with-helm-current-buffer
- (unless helm-yank-point (setq helm-yank-point (point))))
- (helm-goto-char target)
- (helm-highlight-current-line))
- ;; marker points to no buffer, no need to dereference it, just
- ;; delete it.
- (setq mark-ring (delete target mark-ring))
- (error "Marker points to no buffer"))))
-
- (defvar helm-source-mark-ring
- (helm-build-sync-source "mark-ring"
- :candidates #'helm-mark-ring-get-candidates
- :action '(("Goto line" . helm-mark-ring-default-action))
- :persistent-help "Show this line"
- :group 'helm-ring))
-
- ;;; Global-mark-ring
- (defvar helm-source-global-mark-ring
- (helm-build-sync-source "global-mark-ring"
- :candidates #'helm-global-mark-ring-get-candidates
- :action '(("Goto line" . helm-mark-ring-default-action))
- :persistent-help "Show this line"
- :group 'helm-ring))
-
- (defun helm-global-mark-ring-format-buffer (marker)
- (with-current-buffer (marker-buffer marker)
- (goto-char marker)
- (forward-line 0)
- (let ((line (pcase (thing-at-point 'line)
- ((and line (pred stringp)
- (guard (not (string-match-p "\\`\n?\\'" line))))
- (car (split-string line "[\n\r]")))
- (_ "<EMPTY LINE>"))))
- (remove-text-properties 0 (length line) '(read-only) line)
- (format "%7d:%s: %s"
- (line-number-at-pos) (marker-buffer marker) line))))
-
- (defun helm-global-mark-ring-get-candidates ()
- (let ((marks global-mark-ring))
- (when marks
- (cl-loop for marker in marks
- for mb = (marker-buffer marker)
- for gm = (unless (or (string-match "^ " (format "%s" mb))
- (null mb))
- (helm-global-mark-ring-format-buffer marker))
- when (and gm (not (assoc gm recip)))
- collect (cons gm marker) into recip
- finally return recip))))
- ;;;; <Register>
- ;;; Insert from register
- (defvar helm-source-register
- (helm-build-sync-source "Registers"
- :candidates #'helm-register-candidates
- :action-transformer #'helm-register-action-transformer
- :persistent-help ""
- :multiline t
- :action '(("Delete Register(s)" .
- (lambda (_candidate)
- (cl-loop for candidate in (helm-marked-candidates)
- for register = (car candidate)
- do (setq register-alist
- (delq (assoc register register-alist)
- register-alist))))))
- :group 'helm-ring)
- "See (info \"(emacs)Registers\")")
-
- (defun helm-register-candidates ()
- "Collecting register contents and appropriate commands."
- (cl-loop for (char . val) in register-alist
- for key = (single-key-description char)
- for string-actions =
- (cond
- ((numberp val)
- (list (int-to-string val)
- 'insert-register
- 'increment-register))
- ((markerp val)
- (let ((buf (marker-buffer val)))
- (if (null buf)
- (list "a marker in no buffer")
- (list (concat
- "a buffer position:"
- (buffer-name buf)
- ", position "
- (int-to-string (marker-position val)))
- 'jump-to-register
- 'insert-register))))
- ((and (consp val) (window-configuration-p (car val)))
- (list "window configuration."
- 'jump-to-register))
- ((and (vectorp val)
- (fboundp 'undo-tree-register-data-p)
- (undo-tree-register-data-p (elt val 1)))
- (list
- "Undo-tree entry."
- 'undo-tree-restore-state-from-register))
- ((or (and (vectorp val) (eq 'registerv (aref val 0)))
- (and (consp val) (frame-configuration-p (car val))))
- (list "frame configuration."
- 'jump-to-register))
- ((and (consp val) (eq (car val) 'file))
- (list (concat "file:"
- (prin1-to-string (cdr val))
- ".")
- 'jump-to-register))
- ((and (consp val) (eq (car val) 'file-query))
- (list (concat "file:a file-query reference: file "
- (car (cdr val))
- ", position "
- (int-to-string (car (cdr (cdr val))))
- ".")
- 'jump-to-register))
- ((consp val)
- (let ((lines (format "%4d" (length val))))
- (list (format "%s: %s\n" lines
- (truncate-string-to-width
- (mapconcat 'identity (list (car val))
- "^J")
- (- (window-width) 15)))
- 'insert-register)))
- ((stringp val)
- (list
- (concat (substring-no-properties
- val 0 (min (length val) helm-register-max-offset))
- (if (> (length val) helm-register-max-offset)
- "[...]" ""))
- 'insert-register
- 'kill-new
- 'append-to-register
- 'prepend-to-register)))
- unless (null string-actions) ; Fix Issue #1107.
- collect (cons (format "Register %3s:\n %s" key (car string-actions))
- (cons char (cdr string-actions)))))
-
- (defun helm-register-action-transformer (actions register-and-functions)
- "Decide actions by the contents of register."
- (cl-loop with func-actions =
- '((insert-register
- "Insert Register" .
- (lambda (c) (insert-register (car c))))
- (kill-new
- "Kill Register" .
- (lambda (c) (with-temp-buffer
- (insert-register (car c))
- (kill-new (buffer-string)))))
- (jump-to-register
- "Jump to Register" .
- (lambda (c) (jump-to-register (car c))))
- (append-to-register
- "Append Region to Register" .
- (lambda (c) (append-to-register
- (car c) (region-beginning) (region-end))))
- (prepend-to-register
- "Prepend Region to Register" .
- (lambda (c) (prepend-to-register
- (car c) (region-beginning) (region-end))))
- (increment-register
- "Increment Prefix Arg to Register" .
- (lambda (c) (increment-register
- helm-current-prefix-arg (car c))))
- (undo-tree-restore-state-from-register
- "Restore Undo-tree register" .
- (lambda (c) (and (fboundp 'undo-tree-restore-state-from-register)
- (undo-tree-restore-state-from-register (car c))))))
- for func in (cdr register-and-functions)
- when (assq func func-actions)
- collect (cdr it) into transformer-actions
- finally return (append transformer-actions actions)))
-
- ;;;###autoload
- (defun helm-mark-ring ()
- "Preconfigured `helm' for `helm-source-mark-ring'."
- (interactive)
- (helm :sources 'helm-source-mark-ring
- :resume 'noresume
- :buffer "*helm mark*"))
-
- ;;;###autoload
- (defun helm-global-mark-ring ()
- "Preconfigured `helm' for `helm-source-global-mark-ring'."
- (interactive)
- (helm :sources 'helm-source-global-mark-ring
- :resume 'noresume
- :buffer "*helm global mark*"))
-
- ;;;###autoload
- (defun helm-all-mark-rings ()
- "Preconfigured `helm' for `helm-source-global-mark-ring' and \
- `helm-source-mark-ring'."
- (interactive)
- (helm :sources '(helm-source-mark-ring
- helm-source-global-mark-ring)
- :resume 'noresume
- :buffer "*helm mark ring*"))
-
- ;;;###autoload
- (defun helm-register ()
- "Preconfigured `helm' for Emacs registers."
- (interactive)
- (helm :sources 'helm-source-register
- :resume 'noresume
- :buffer "*helm register*"))
-
- ;;;###autoload
- (defun helm-show-kill-ring ()
- "Preconfigured `helm' for `kill-ring'.
- It is drop-in replacement of `yank-pop'.
-
- First call open the kill-ring browser, next calls move to next line."
- (interactive)
- (setq helm-kill-ring--truncated-flag nil)
- (let ((enable-recursive-minibuffers t))
- (helm :sources helm-source-kill-ring
- :buffer "*helm kill ring*"
- :resume 'noresume
- :allow-nest t)))
-
- ;;;###autoload
- (defun helm-execute-kmacro ()
- "Preconfigured helm for keyboard macros.
- Define your macros with `f3' and `f4'.
- See (info \"(emacs) Keyboard Macros\") for detailed infos.
- This command is useful when used with persistent action."
- (interactive)
- (let ((helm-quit-if-no-candidate
- (lambda () (message "No kbd macro has been defined"))))
- (helm :sources
- (helm-build-sync-source "Kmacro"
- :candidates (lambda ()
- (helm-fast-remove-dups
- (cons (kmacro-ring-head)
- kmacro-ring)
- :test 'equal))
- :multiline t
- :candidate-transformer
- (lambda (candidates)
- (cl-loop for c in candidates collect
- (propertize (help-key-description (car c) nil)
- 'helm-realvalue c)))
- :persistent-help "Execute kmacro"
- :help-message 'helm-kmacro-help-message
- :action
- (helm-make-actions
- "Execute kmacro (`C-u <n>' to execute <n> times)"
- 'helm-kbd-macro-execute
- "Concat marked macros"
- 'helm-kbd-macro-concat-macros
- "Delete marked macros"
- 'helm-kbd-macro-delete-macro
- "Edit marked macro"
- 'helm-kbd-macro-edit-macro)
- :group 'helm-ring)
- :buffer "*helm kmacro*")))
-
- (defun helm-kbd-macro-execute (candidate)
- ;; Move candidate on top of list for next use.
- (setq kmacro-ring (delete candidate kmacro-ring))
- (kmacro-push-ring)
- (kmacro-split-ring-element candidate)
- (kmacro-exec-ring-item
- candidate helm-current-prefix-arg))
-
- (defun helm-kbd-macro-concat-macros (_candidate)
- (let ((mkd (helm-marked-candidates)))
- (when (cdr mkd)
- (kmacro-push-ring)
- (setq last-kbd-macro
- (mapconcat 'identity
- (cl-loop for km in mkd
- if (vectorp km)
- append (cl-loop for k across km collect
- (key-description (vector k)))
- into result
- else collect (car km) into result
- finally return result)
- "")))))
-
- (defun helm-kbd-macro-delete-macro (_candidate)
- (let ((mkd (helm-marked-candidates)))
- (kmacro-push-ring)
- (cl-loop for km in mkd
- do (setq kmacro-ring (delete km kmacro-ring)))
- (kmacro-pop-ring1)))
-
- (defun helm-kbd-macro-edit-macro (candidate)
- (kmacro-push-ring)
- (setq kmacro-ring (delete candidate kmacro-ring))
- (kmacro-split-ring-element candidate)
- (kmacro-edit-macro))
-
- (provide 'helm-ring)
-
- ;; Local Variables:
- ;; byte-compile-warnings: (not obsolete)
- ;; coding: utf-8
- ;; indent-tabs-mode: nil
- ;; End:
-
- ;;; helm-ring.el ends here
|