14

in Helm I ignore some buffers from showing when using C-x b, I'm looking for a way to make the commands previous-buffer and next-buffer behave the same ignoring some buffers.

shackra
  • 2,782
  • 19
  • 49

3 Answers3

13
(defcustom my-skippable-buffers '("*Messages*" "*scratch*" "*Help*")
  "Buffer names ignored by `my-next-buffer' and `my-previous-buffer'."
  :type '(repeat string))

(defun my-change-buffer (change-buffer)
  "Call CHANGE-BUFFER until current buffer is not in `my-skippable-buffers'."
  (let ((initial (current-buffer)))
    (funcall change-buffer)
    (let ((first-change (current-buffer)))
      (catch 'loop
        (while (member (buffer-name) my-skippable-buffers)
          (funcall change-buffer)
          (when (eq (current-buffer) first-change)
            (switch-to-buffer initial)
            (throw 'loop t)))))))

(defun my-next-buffer ()
  "Variant of `next-buffer' that skips `my-skippable-buffers'."
  (interactive)
  (my-change-buffer 'next-buffer))

(defun my-previous-buffer ()
  "Variant of `previous-buffer' that skips `my-skippable-buffers'."
  (interactive)
  (my-change-buffer 'previous-buffer))

(global-set-key [remap next-buffer] 'my-next-buffer)
(global-set-key [remap previous-buffer] 'my-previous-buffer)

Or if you wanted to ignore buffer names matching a pattern, you could use this variant.

(defcustom my-skippable-buffer-regexp
  (rx bos (or "*Messages*" "*scratch*" "*Help*") eos)
  "Matching buffer names are ignored by `my-next-buffer'
and `my-previous-buffer'."
  :type 'regexp)

(defun my-change-buffer (change-buffer) "Call CHANGE-BUFFER until `my-skippable-buffer-regexp' doesn't match." (let ((initial (current-buffer))) (funcall change-buffer) (let ((first-change (current-buffer))) (catch 'loop (while (string-match-p my-skippable-buffer-regexp (buffer-name)) (funcall change-buffer) (when (eq (current-buffer) first-change) (switch-to-buffer initial) (throw 'loop t)))))))

(defun my-next-buffer () "Variant of next-buffer' that skipsmy-skippable-buffer-regexp'." (interactive) (my-change-buffer 'next-buffer))

(defun my-previous-buffer () "Variant of previous-buffer' that skipsmy-skippable-buffer-regexp'." (interactive) (my-change-buffer 'previous-buffer))

(global-set-key [remap next-buffer] 'my-next-buffer) (global-set-key [remap previous-buffer] 'my-previous-buffer)

For the example from the comments, to tell this latter version to also skip all buffer names beginning with helm, we could use:

(setq my-skippable-buffer-regexp
      (rx bos (or (or "*Messages*" "*scratch*" "*Help*")
                  (seq "helm" (zero-or-more anything)))
          eos))
phils
  • 50,977
  • 3
  • 79
  • 122
  • What should I do to hide all helm* buffers as well? – alper Aug 09 '20 at 23:51
  • yes it does! Thanks @phils – alper Aug 10 '20 at 00:12
  • I've updated the answer accordingly. Please test. – phils Aug 10 '20 at 00:56
  • With the current updated code it works without a problem. If I add new patterns would I just add (seq "patter" (zero-or-more anything) line? – alper Aug 10 '20 at 01:19
  • 1
    You can do, but I would suggest using (seq (or "helm" "foo" "bar") (zero-or-more anything)) to match a variety of prefixes. – phils Aug 10 '20 at 01:43
  • Also what could we do to skip all the buffers names ending with .txt? – alper Aug 15 '21 at 14:25
  • Well if (seq "helm" (zero-or-more anything)) is "all buffer names beginning with helm" then what's your guess at the equivalent "all buffer names ending with .txt" ? – phils Aug 15 '21 at 14:36
  • (anything zero-or-more)? – alper Aug 15 '21 at 16:00
  • It will be (seq (zero-or-more anything) ".txt"). The sequence of "zero-or-more of any character" followed by .txt. Just like the earlier example was the sequence of helm followed by "zero-or-more of any character". – phils Aug 15 '21 at 16:04
  • Thanks. Ah basically its *.txt. I get confused when (zero-or-more anything) is actually alias for *. – alper Aug 15 '21 at 17:41
  • You're thinking of shell globbing syntax, rather than regexps. (zero-or-more anything) is rx syntax for the regexp "\\(.\\|\n\\)*", with the trailing * being the zero-or-more quantifier, and the preceding part matching any character. – phils Aug 16 '21 at 00:15
  • 1
    I.e. almost the same as the regexp .* except that . doesn't match newlines. – phils Aug 16 '21 at 03:38
  • this one still has some problem, dunno what is root cause but in my system the next-buffer one stops after a few buffers while the prev-buffer keeps cycling through??? – ftravers Sep 26 '21 at 00:32
9

Here are two ways:

  1. If you take a look at the definition of function switch-to-next-buffer (in library window.el) you will see that it filters the buffers by the predicate (if any) that is the value of frame parameter buffer-predicate. That function is used by next-buffer.

    You can set that frame parameter to a predicate that filters the way you want.

  2. You can simply advise function switch-to-next-buffer to filter the way you want. You will essentially replace its definition by almost the same definition, but filter additionally the way you want.

Similarly for switch-to-previous-buffer.

Lindydancer
  • 6,150
  • 1
  • 15
  • 26
Drew
  • 77,472
  • 10
  • 114
  • 243
4

You can as of 2022 (since Emacs 27.1?) achieve the same result as in the accepted answer with Emacs' built-in functionality, using switch-to-prev-buffer-skip. For example, here is my current config (inspired by @phils' answer):

(defcustom aj8/buffer-skip-regexp
  (rx bos (or (or "*Backtrace*" "*Compile-Log*" "*Completions*"
                  "*Messages*" "*package*" "*Warnings*"
                  "*Async-native-compile-log*")
              (seq "magit-diff" (zero-or-more anything))
              (seq "magit-process" (zero-or-more anything))
              (seq "magit-revision" (zero-or-more anything))
              (seq "magit-stash" (zero-or-more anything)))
              eos)
  "Regular expression matching buffers ignored by `next-buffer' and
`previous-buffer'."
  :type 'regexp)

(defun aj8/buffer-skip-p (window buffer bury-or-kill) "Return t if BUFFER name matches `aj8/buffer-skip-regexp'." (string-match-p aj8/buffer-skip-regexp (buffer-name buffer)))

(setq switch-to-prev-buffer-skip 'aj8/buffer-skip-p)

ajdev8
  • 156
  • 3