double-saber icon indicating copy to clipboard operation
double-saber copied to clipboard

Filter search output with extreme prejudice

  • [[file:https://i.imgur.com/7axtkyH.png]] double-saber

[[https://stable.melpa.org/#/double-saber][file:https://stable.melpa.org/packages/double-saber-badge.svg]] [[https://melpa.org/#/double-saber][file:https://melpa.org/packages/double-saber-badge.svg]] [[https://travis-ci.org/dp12/double-saber][file:https://api.travis-ci.org/dp12/double-saber.png?branch=master]] [[http://www.gnu.org/licenses/gpl-3.0.html][file:http://img.shields.io/:license-gpl3-blue.svg]]

[[file:https://i.imgur.com/7YoialK.png]]

  • Overview Search buffer output from tools like [[https://github.com/nlamirault/ripgrep.el][ripgrep]], [[https://github.com/leoliu/ggtags][ggtags]], or [[https://oremacs.com/2015/11/04/ivy-occur/][ivy-occur]] often gets cluttered with results that are irrelevant. How can we remove the unwanted cruft?

double-saber provides two methods of cutting away undesired output: narrow and delete.

=double-saber-narrow= (bound to =x=) deletes all lines of text that don't match a certain keyword.

=double-saber-delete= (bound to =d=) does the inverse and deletes all matching lines.

[[file:double-saber.gif]]

Smite undesired output by either providing a single regexp (with no spaces), or a series of space-delimited strings. For fans of narrowing (e.g. =narrow-to-defun= or =narrow-region=), it essentially acts like the mythical "=narrow-regexp=" tool.

Please note that double-saber, being a chopping weapon, is not meant to be very intelligent or precise. It won't play nicely with packages like [[https://github.com/Wilfred/deadgrep][deadgrep]] that interleave lines of search metadata between search results. But when grep is spitting out dark clouds of text and raw greptitude does not avail you, it might be exactly what you need.

  • Installation double-saber is available on MELPA. You can install it with:

#+begin_src emacs-lisp M-x package-install double-saber #+end_src

double-saber can also be loaded with use-package: #+begin_src emacs-lisp (use-package double-saber :load-path "~/double-saber" :config ;; add double-saber hooks here #+end_src

Or the traditional way with require: #+begin_src emacs-lisp (add-to-list 'load-path "/path/to/double-saber-dir/") (require 'double-saber) #+end_src

To get double-saber working for a given mode, enable =double-saber-mode= in that function's mode hook. Additionally, you will likely want to set the following variables locally for that mode:

  • =double-saber-start-line=: The start line where double-saber should start deleting/narrowing from. In most cases, it should be the line after the search command. Can be =nil=.
  • =double-saber-end-text=: The end text, which denotes the line beyond which double-saber should not delete (inclusive). This prevents useful text like the number of search hits from getting deleted. Can be =nil=.

The following are some examples for how to set up double-saber for different major modes: #+begin_src emacs-lisp (with-eval-after-load "ripgrep" (add-hook 'ripgrep-search-mode-hook (lambda () (double-saber-mode) (setq-local double-saber-start-line 5) (setq-local double-saber-end-text "Ripgrep finished"))))

(with-eval-after-load "grep" (add-hook 'grep-mode-hook (lambda () (double-saber-mode) (setq-local double-saber-start-line 5) (setq-local double-saber-end-text "Grep finished"))))

(with-eval-after-load "ggtags" (add-hook 'ggtags-global-mode-hook (lambda () (double-saber-mode) (setq-local double-saber-start-line 5) (setq-local double-saber-end-text "Global found"))))

(with-eval-after-load "ivy" (add-hook 'ivy-occur-grep-mode-hook (lambda () (double-saber-mode) (setq-local double-saber-start-line 5)))) #+end_src

  • Keybindings =double-saber-mode= provides the following keybindings when enabled: | Key | Command | |--------------+-------------------------| | =d= | double-saber-delete | | =x= | double-saber-narrow | | =s= | double-saber-sort-lines | | =u= | double-saber-undo | | =C-r=, =C-_= | double-saber-redo |
  • Hydras and Transient States If you have abo-abo's excellent [[https://github.com/abo-abo/hydra][hydra]] package, you can define a keymap to narrow or delete specific strings without having to type them. #+begin_src emacs-lisp (defhydra hydra-foo-filter () "Foo filter" ("0" (double-saber-narrow "foo0") "foo0") ("1" (double-saber-narrow "foo1") "foo1") ("2" (double-saber-narrow "foo2") "foo2") ("b" (double-saber-narrow "bar") "bar") ("z" (double-saber-narrow "baz") "baz") ("h" (double-saber-narrow ".h:") "*.h") ;; show only .h files ("d" (call-interactively 'double-saber-delete) "delete") ("x" (call-interactively 'double-saber-narrow) "narrow") ("q" nil "quit" :exit t )) (with-eval-after-load "ripgrep" (define-key ripgrep-search-mode-map (kbd "x") 'hydra-foo-filter/body)) #+end_src

Or, if you are a spacemacs user, you can use =spacemacs|define-transient-state=: #+begin_src emacs-lisp (spacemacs|define-transient-state foo-filter :title "Foo Filter Transient State" :doc "\n[0] foo0 [1] foo1 [2] foo2 [b] bar [z] baz [h] *.h [d] delete [x] narrow [q] quit" :bindings ("0" (double-saber-narrow "foo0")) ("1" (double-saber-narrow "foo1")) ("2" (double-saber-narrow "foo2")) ("b" (double-saber-narrow "bar")) ("z" (double-saber-narrow "baz")) ("h" (double-saber-narrow ".h:")) ;; show only .h files ("d" (call-interactively 'double-saber-delete)) ("x" (call-interactively 'double-saber-narrow)) ("q" nil :exit t)) (with-eval-after-load "ripgrep" (define-key ripgrep-search-mode-map (kbd "x") 'spacemacs/foo-filter-transient-state/body)) #+end_src

  • FAQ Isn't this just the same as "flush-lines"?

One difference vs. =flush-lines= is if you enter multiple words for double-saber-delete, it will generate a matching regex that is handed to =delete-matching-lines=. So entering "emacs vim" will generate a regex that deletes lines containing emacs or vim. =flush-lines= will treat that as a single regex and delete neither. Another difference is that =flush-lines= deletes from the current line to the end of the buffer. double-saber will start deleting from the top of the buffer or whatever starting line is specified (usually after the search command).

double-saber also enables undo so that you can bring back what you deleted, something that is usually disabled in search buffers. On a similar note, most search buffers enable read-only mode, so double-saber temporarily disables that to modify the buffer.

Lastly, it keeps metadata like the search command and original number of search hits from being deleted.

What's up with the name?

The inspiration for this package came when I saw a reddit comment that jokingly wished =M-x sword-mode= existed in emacs. After I created double-saber's core filter functions to help me at my day job, I thought that two swords were better than one.

Your demo video of double-saber was a little lacking in excitement. Do you have a better one?

While I would love to bring you amazing gifs of Michelle Yeoh using emacs and doing M-x butterfly-twists, I regret that I have but one YouTube link to give for my country. [[https://youtu.be/S9MafXsex2Y][Here you go]].

  • Misc double-saber is integration-tested with ecukes [[https://github.com/ecukes/ecukes][🥒]] and is licensed under the GPLv3.

Saber icon by [[http://bogo-d.deviantart.com][Mihaiciuc Bogdan]], with slight modifications.

Feature requests and contributions welcome!