/double-saber

Filter search output with extreme prejudice

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

https://i.imgur.com/7axtkyH.png double-saber

https://stable.melpa.org/packages/double-saber-badge.svg https://melpa.org/packages/double-saber-badge.svg https://api.travis-ci.org/dp12/double-saber.png?branch=master http://img.shields.io/:license-gpl3-blue.svg

https://i.imgur.com/7YoialK.png

Overview

Search buffer output from tools like ripgrep, ggtags, or 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.

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 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:

M-x package-install double-saber

double-saber can also be loaded with use-package:

(use-package double-saber
  :load-path "~/double-saber"
  :config
  ;; add double-saber hooks here

Or the traditional way with require:

(add-to-list 'load-path "/path/to/double-saber-dir/")
(require 'double-saber)

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:

(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))))

Keybindings

double-saber-mode provides the following keybindings when enabled:

KeyCommand
ddouble-saber-delete
xdouble-saber-narrow
sdouble-saber-sort-lines
udouble-saber-undo
C-r, C-_double-saber-redo

Hydras and Transient States

If you have abo-abo’s excellent hydra package, you can define a keymap to narrow or delete specific strings without having to type them.

(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))

Or, if you are a spacemacs user, you can use spacemacs|define-transient-state:

(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))

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. Here you go.

Misc

double-saber is integration-tested with ecukes 🥒 and is licensed under the GPLv3.

Saber icon by Mihaiciuc Bogdan, with slight modifications.

Feature requests and contributions welcome!