Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/aatmunbaxi/lasgun.el

Avy-backed, actionable placement of multiple marks
https://github.com/aatmunbaxi/lasgun.el

avy emacs emacs-lisp

Last synced: about 1 month ago
JSON representation

Avy-backed, actionable placement of multiple marks

Awesome Lists containing this project

README

        

#+title: lasgun.el
#+subtitle: Avy-backed marking of multiple positions

[[file:lasgun-demo.gif]]

=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 [[file:showcase.md][demo by examples]].
* Why =lasgun.el=?
- Avy provides an excellent =Filter -> Select -> Act= loop. =lasgun.el= generalizes this to allow multiple runs of =Filter -> Select=, and =Acting= when all candidates are selected.
- Therefore, using =lasgun.el= only makes sense when acting on multiple candidates in bulk
- This being said, it should not be considered a strict superset of avy's features

* Usage
- Call a =lasgun-mark= function to mark a buffer position via =avy=
- Repeat until desired positions are marked
- Decide to act on each of these positions or not
- Actions can be defined with the =define-lasgun-action= macro. See its docstring for information.

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=.
* Installation
For example, via =straight=:
#+begin_src emacs-lisp
(straight-use-package
'(lasgun :type git :host github :repo "aatmunbaxi/lasgun.el")
#+end_src

DOOM emacs, in =packages.el=:
#+begin_src emacs-lisp
(package! lasgun :recipe (:host github "aatmunbaxi/lasgun.el"))
#+end_src

If your package manager does not support git recipes, a simple =git clone= and placement of
#+begin_src emacs-lisp
(add-to-list 'load-path "path/to/lasgun.el")
(require 'lasgun)
#+end_src
in your =init.el= will do.
* Example =transient= Configuration
The demo gif shows an example UI with hercules.
[[https://www.reddit.com/r/emacs/comments/1c6epwl/comment/l02r9vx/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button][u/Hammar_Morty kindly provided]] a close translation making use of =transient=:
#+begin_src emacs-lisp
(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)
#+end_src
* Example Embark Action Menu
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.
#+begin_src emacs-lisp
(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))
#+end_src
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:
#+begin_src emacs-lisp
(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))))
#+end_src
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 [[https://github.com/oantolin/embark?tab=readme-ov-file#defining-actions-for-new-categories-of-targets][embark documentation.]]
* Dependencies
- =avy=
- Optional:
- =multiple-cursors= for =lasgun-make-multiple-cursors=
- =embark= for =lasgun-embark-act-all=
* Customizing
By "lasgun mark" we mean a buffer position stored in =lasgun-mark-ring=.
- =lasgun-mark-ring-max=: Maximum number of lasgun marks
- =lasgun-pop-before-make-cursors=: Place =multiple-cursors= cursors only at lasgun marks (can negate interactively, see =lasgun-make-multiple-cursors= docstring)
- =lasgun-also-push-mark-ring=: Also push lasgun marks to buffer-local =mark-ring=
- =lasgun-use-lasgun-mark-overlay=: Use visual overlays for lasgun marks
- =lasgun-persist-lasgun-mark-ring=: Persist =lasgun-mark-ring= after performing action (Can override when defining lasgun actions, see =define-lasgun-action= docstring.)
- =lasgun-persist-negation-prefix-arg=: Prefix arg with which to negate =lasgun-persist-lasgun-mark-ring= behavior

- =lasgun-mark-face=: Face used to visually indicated lasgun marks
* =lasgun-mark= functions
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-line=
- =lasgun-mark-line=
- =lasgun-mark-word=
- =lasgun-mark-char-2=
- =lasgun-mark-symbol-1=
- =lasgun-mark-subword-0=
- =lasgun-mark-subword-1=
- =lasgun-mark-char-timer=
- =lasgun-mark-char-2-above=
- =lasgun-mark-char-2-below=
- =lasgun-mark-word-0-above=
- =lasgun-mark-word-0-below=
- =lasgun-mark-symbol-1-above=
- =lasgun-mark-symbol-1-below=
- =lasgun-mark-whitespace-end=
- =lasgun-mark-whitespace-end-above=
- =lasgun-mark-whitespace-end-below=