lasgun.el (lays-gun) provides avy-backed, actionable placement of multiple inactive marks in the current buffer.
Once these marks have been collected, you can act on the marks in bulk, without disturbing your point (with some obvious exceptions).
If this sounds familiar to how avy works, it is!
lasgun simply generalizes the Filter -> Select -> Act from avy to one that works on multiple selected candidates.
More examples on usage can be found in the demo by examples.
- Avy provides an excellent
Filter -> Select -> Actloop.lasgun.elgeneralizes this to allow multiple runs ofFilter -> Select, andActingwhen all candidates are selected.- Therefore, using
lasgun.elonly makes sense when acting on multiple candidates in bulk - This being said, it should not be considered a strict superset of avy’s features
- Therefore, using
- Call a
lasgun-markfunction to mark a buffer position viaavy- Repeat until desired positions are marked
- Decide to act on each of these positions or not
- Actions can be defined with the
define-lasgun-actionmacro. See its docstring for information.
- Actions can be defined with the
If not acting at lasgun marks, it might be useful to set lasgun-also-push-mark-ring to t, so lasgun marks remain in the mark-ring after clearing the lasgun-mark-ring.
For example, via straight:
(straight-use-package
'(lasgun :type git :host github :repo "aatmunbaxi/lasgun.el")DOOM emacs, in packages.el:
(package! lasgun :recipe (:host github "aatmunbaxi/lasgun.el"))If your package manager does not support git recipes, a simple git clone and placement of
(add-to-list 'load-path "path/to/lasgun.el")
(require 'lasgun)in your init.el will do.
The demo gif shows an example UI with hercules.
u/Hammar_Morty kindly provided a close translation making use of transient:
(require 'transient)
;; Defines some lasgun actions
(define-lasgun-action lasgun-action-upcase-word t upcase-word)
(define-lasgun-action lasgun-action-downcase-word t downcase-word)
(define-lasgun-action lasgun-action-kill-word nil kill-word)
(transient-define-prefix lasgun-transient ()
"Main transient for lasgun."
[["Marks"
("c" "Char timer" lasgun-mark-char-timer :transient t)
("w" "Word" lasgun-mark-word-0 :transient t)
("l" "Begin of line" lasgun-mark-line :transient t)
("s" "Symbol" lasgun-mark-symbol-1 :transient t)
("o" "Whitespace end" lasgun-mark-whitespace-end :transient t)
("x" "Clear lasgun mark ring" lasgun-clear-lasgun-mark-ring :transient t)
("u" "Undo lasgun mark" lasgun-pop-lasgun-mark :transient t)]
["Actions"
("SPC" "Make cursors" lasgun-make-multiple-cursors)
("." "Embark act all" lasgun-embark-act-all)
("U" "Upcase" lasgun-action-upcase-word)
("l" "Downcase" lasgun-action-downcase-word)
("K" "Kill word" lasgun-action-kill-word)
("q" "Quit" transient-quit-one)]])
(global-set-key (kbd "C-c t g") 'lasgun-transient)If transients (or their half-siblings hydra, hercules, etc) aren’t your thing, here is a skeleton for an embark dispatch menu for lasgun actions.
(defun my-embark-lasgun-mark ()
"Provides `embark-target-finders' function for lasgun marks
when point is on lasgun mark"
;; can make this logic as complicated/simple as you want
(when (ring-member lasgun-mark-ring (point))
`(lasgun-mark
,(buffer-substring-no-properties (point) (point))
,(point) . ,(1+ (point)))))
;; tell embark to search for lasgun-marks
(add-to-list 'embark-target-finders #'my-embark-lasgun-mark)
(defvar-keymap embark-lasgun-mark-actions
:doc "Embark action keymap for lasgun targets"
;; your keybinds and actions here
"SPC" #'lasgun-make-multiple-cursors)
;; embark will use your keymap for lasgun-marks dispatch menu
(add-to-list 'embark-keymap-alist '(lasgun-mark . embark-lasgun-mark-actions))The targeting function my-embark-lasgun-mark will match a lasgun mark if your point is currently on a lasgun mark, meaning you’d have to jump to one such mark before invocation of embark-act (does this defeat the purpose of lasgun? Up to you).
Fortunately, the logic of such a targeting function is limited only by your ability to write elisp.
Here’s one that will intercept all calls of embark-act to target lasgun marks so long as the lasgun-mark-ring is nonempty:
(defun my-embark-lasgun-mark ()
"Use lasgun embark actions so long as lasgun marks exist"
(unless (ring-empty-p lasgun-mark-ring)
(let ((lgmark (ring-ref lasgun-mark-ring 0)))
`(lasgun-mark ,(buffer-substring-no-properties lgmark lgmark)
,lgmark . ,lgmark))))See the docstring for embark-target-finders information if you want to hack on the targeting function.
Add your keys for defined actions to embark-lasgun-mark-actions to expand the functionality!
More information on defining your own embark target actions can be found in the embark documentation.
avy- Optional:
multiple-cursorsforlasgun-make-multiple-cursorsembarkforlasgun-embark-act-all
By “lasgun mark” we mean a buffer position stored in lasgun-mark-ring.
lasgun-mark-ring-max: Maximum number of lasgun markslasgun-pop-before-make-cursors: Placemultiple-cursorscursors only at lasgun marks (can negate interactively, seelasgun-make-multiple-cursorsdocstring)lasgun-also-push-mark-ring: Also push lasgun marks to buffer-localmark-ringlasgun-use-lasgun-mark-overlay: Use visual overlays for lasgun markslasgun-persist-lasgun-mark-ring: Persistlasgun-mark-ringafter performing action (Can override when defining lasgun actions, seedefine-lasgun-actiondocstring.)lasgun-persist-negation-prefix-arg: Prefix arg with which to negatelasgun-persist-lasgun-mark-ringbehaviorlasgun-mark-face: Face used to visually indicated lasgun marks
Lasgun provides analogues to nearly every avy-goto function. They are listed below. IMHO, it is an overwhelming number of choices; they are simply provided for completeness. It is recommended that you stick to a few staples, unless you’re using something to remember where each function is bound, like hercules or hydra.
lasgun-mark-end-of-linelasgun-mark-linelasgun-mark-wordlasgun-mark-char-2lasgun-mark-symbol-1lasgun-mark-subword-0lasgun-mark-subword-1lasgun-mark-char-timerlasgun-mark-char-2-abovelasgun-mark-char-2-belowlasgun-mark-word-0-abovelasgun-mark-word-0-belowlasgun-mark-symbol-1-abovelasgun-mark-symbol-1-belowlasgun-mark-whitespace-endlasgun-mark-whitespace-end-abovelasgun-mark-whitespace-end-below
