$ emacs -Q ;; Considering the next commands : (defun pils/cycle-buffer-of-major-mode (&optional arg) "Switch to previous buffer of this major mode. With ARG as \\[universal-argument], switch to the next instead." (interactive "P") (let ((switch-to-prev-buffer-skip (lambda (window buffer _bury-or-kill) (not (eq (buffer-local-value 'major-mode (window-buffer window)) (buffer-local-value 'major-mode buffer)))))) (if arg (switch-to-next-buffer) (switch-to-prev-buffer nil 'append))) (when (eq (current-buffer) (window-old-buffer)) (user-error "No other %s buffer available." major-mode))) (defun pils/next-buffer-of-major-mode () "Switch to the next buffer of this major mode." (interactive) (pils/cycle-buffer-of-major-mode t)) ;; That we could temporary bind to : (global-set-key (kbd "M-p") #'pils/cycle-buffer-of-major-mode) (global-set-key (kbd "M-n") #'pils/next-buffer-of-major-mode) Now gently but firmly, play theses emacs chords. What happened ? The first command will put you in another mode. That is unexpected ... Worse the second one will quickly jam. The current implementation 'switch-to-prev-buffer-skip' fallback to the first skipped buffer if no one have satisfied its anti-predicate. That's why you could end up in another mode despite setting 'switch-to-prev-buffer-skip' to not select others modes. Conservatively I fix that by checking if 'switch-to-prev-buffer-skip' is a function. The second issue is a bug in the implementation of 'switch-to-next-buffer'. This command should never return the same buffer, it is wrote in its docstring, and it escaped me in commit d0c7d8bc22. So here a little patch to fix both.