summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cask4
-rwxr-xr-xMakefile28
-rw-r--r--modes/magit/README.org211
-rw-r--r--modes/magit/evil-collection-magit.el652
-rw-r--r--test/evil-collection-magit-tests.el112
5 files changed, 978 insertions, 29 deletions
diff --git a/Cask b/Cask
index 09184ea..c9db627 100644
--- a/Cask
+++ b/Cask
@@ -4,7 +4,9 @@
(package-file "evil-collection.el")
(development
+ (depends-on "evil")
(depends-on "f")
(depends-on "ert-runner")
(depends-on "package-lint")
- (depends-on "annalist"))
+ (depends-on "annalist")
+ (depends-on "magit"))
diff --git a/Makefile b/Makefile
index d436604..381885c 100755
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,21 @@
EMACS ?= emacs
+CASK ?= cask
+
+LOADPATH = -L .
+TESTPATH = -L ./test
+
+ELPA_DIR = \
+ .cask/$(shell $(EMACS) -Q --batch --eval '(princ emacs-version)')/elpa
compile:
- cask exec $(EMACS) -Q -batch \
+ $(CASK) exec $(EMACS) -Q -batch \
-L . \
--eval '(setq evil-want-integration nil)' \
--eval '(setq byte-compile-error-on-warn t)' \
-f batch-byte-compile *.el modes/*/*.el
lint:
- cask exec $(EMACS) -Q -batch \
+ $(CASK) exec $(EMACS) -Q -batch \
--eval "(require 'package)" \
--eval "(push '(\"melpa\" . \"http://melpa.org/packages/\") package-archives)" \
--eval "(package-initialize)" \
@@ -18,7 +25,18 @@ lint:
--eval "(advice-add 'package-lint--check-version-regexp-list :around 'ignore)" \
-f package-lint-batch-and-exit *.el modes/*/*.el
-test:
- cask exec ert-runner
+test: elpa
+ $(CASK) exec $(EMACS) -Q -batch $(LOADPATH) $(TESTPATH) \
+-l evil-collection-test.el -l evil-collection-magit-tests.el -f ert-run-tests-batch-and-exit
+
+magit-test: elpa
+ $(CASK) exec $(EMACS) -Q -batch $(LOADPATH) $(TESTPATH) \
+-l evil-collection-magit-tests.el -f ert-run-tests-batch-and-exit
+
+elpa: $(ELPA_DIR)
+$(ELPA_DIR): Cask
+ $(CASK) install
+ mkdir -p $(ELPA_DIR)
+ touch $@
-.PHONY: compile lint test
+.PHONY: compile lint test elpa
diff --git a/modes/magit/README.org b/modes/magit/README.org
new file mode 100644
index 0000000..6664989
--- /dev/null
+++ b/modes/magit/README.org
@@ -0,0 +1,211 @@
+* Black magic
+
+ This library configures Magit and Evil to play well with each other. For some
+ background see [[https://github.com/justbur/evil-magit/issues/1][Issue #1]].
+
+ *Note*: I intend to track the latest commits to the master branch of the [[https://github.com/magit/magit][magit
+ repo]], meaning the keybindings here are potentially ahead of the last stable
+ release of magit. Once the code in evil-collection-magit stabilizes, I may switch to
+ primarily tracking the stable release of magit and secondarily track the latest
+ commits to master. Any help is welcomed.
+
+* Recent Changes (most recent first)
+
+ 1. [2019-09-04] Don't use =evil-next-visual-line= and
+ =evil=previous-visual-line=. See [[https://github.com/emacs-evil/evil-magit/issues/70][Issue #70]].
+ 1. [2019-06-16] Added =evil-collection-magit-stage-untracked-file-with-intent= at
+ =I=. See [[https://github.com/emacs-evil/evil-magit/issues/67][Issue #67]].
+ 2. [2019-02-15] Added =forge-dispatch= at =@=.
+ 3. [2018-05-22] Added =evil-collection-magit-use-z-for-folds=. See docstring for more
+ information.
+ 4. [2018-03-13] Added basic evil support for =magit-list-repositories=.
+ 5. [2016-07-27] Moved submodules popup to ' and added the new subtree popup at
+ ". This is not mnemonic in any way but easy to reach and keeps the two keys
+ together.
+ 6. [2016-03-24] Moved =magit-diff-less-context= to = to fix conflict with
+ moved revert.
+ 7. [2016-03-21] Moved revert commands from =o= and =O= to =-= and
+ =_=. Rationale is that you are subtracting a commit. This makes room for
+ =o= and =O= to be reset and the new reset popup command. Think of resetting
+ to an "old" state. =¯\_(ツ)_/¯=
+ 8. Added =evil-collection-magit-want-horizontal-movement=. Use =h= and =l= for movement
+ like vim, moving =h= to =H=, =l= to =L=, and =L= to =C-l=.
+ 9. Added =evil-collection-magit-toggle-text-mode= on =C-t=. This is a quick way to enter
+ text mode in a magit buffer, which allows arbitrary movement, copying, etc.
+ Use =C-t= to return to the previous magit mode.
+ 10. When =evil-collection-magit-use-y-for-yank= is non nil, =C-w= will prefix the evil
+ window switching functions from magit buffers.
+ 11. =evil-collection-magit-use-y-for-yank= is now the default. It has worked well for me so
+ far, and I've had good feedback, but please let me know if you see issues.
+ You can use the original behavior with =(setq evil-collection-magit-use-y-for-yank
+ nil)=. See the table below for a summary of differences.
+
+
+* Installation and Use
+
+ Evil and Magit are both required. After requiring those packages, the following
+ will setup the new key bindings for you.
+
+ #+BEGIN_SRC emacs-lisp
+ ;; optional: this is the evil state that evil-collection-magit will use
+ ;; (setq evil-collection-magit-state 'normal)
+ ;; optional: disable additional bindings for yanking text
+ ;; (setq evil-collection-magit-use-y-for-yank nil)
+ (evil-collection-init)
+ #+END_SRC emacs-lisp
+
+** Note on Evil usage
+
+ This package assumes that you either use the global variant of evil mode (e.g.,
+ through =(evil-mode 1)=), or at least have =evil-local-mode= (the local variant)
+ enabled in the magit buffers you want these bindings to take effect in. When
+ evil is disabled in a magit buffer, this package will not affect the default key
+ bindings (with one minor exception).
+
+** Note on =evil-collection-magit-use-y-for-yank=
+
+ =evil-collection-magit-use-y-for-yank= enables evil's visual state for linewise selection,
+ and as a consequnce =y= will yank text from the buffer.
+
+ With this enabled which it is by default evil-collection-magit uses =v= and =V= to select
+ by line. Selection in magit occurs linewise, so this choice is to avoid
+ confusion that might arise if someone thought they could stage part of a line
+ with =v= for example.
+
+** Text mode
+
+ Text mode can be toggled with =evil-collection-magit-toggle-text-mode= (triggered with
+ =C-t= or =\=). This takes nearly any magit buffer out of the related magit mode
+ and puts it into =text-mode=. This allows free movement in the buffer using the
+ standard evil movement and selection commands, making it easy to for example
+ copy arbitrary text in the buffer. It also effectively prevents magit keys from
+ shadowing evil ones, so =f= runs =evil-find-char= instead of
+ =magit-fetch-popup=, allowing all vim related movement commands to be used in
+ magit buffers. You can think of this if you like as another state for evil-collection-magit
+ to be in.
+
+ Several requests have been made to allow selecting and copying arbitrary text in
+ the magit buffers, but there are many conflicts between evil bindings and magit
+ bindings and there is no elegant solution to this problem in my opinion. Text
+ mode is the best that I have come up with.
+
+* Key Bindings
+
+ The basic key binding scheme for evil-collection-magit (EM) is described in the following
+ tables. Blank columns indicate that the key is carried over from the left.
+
+ | Category | Default | EM w/o yank opt | w/ yank opt (default) | w/ horiz move | w/ folds |
+ |------------------------+---------+------------------------+-----------------------+---------------+----------|
+ | cherry pick | =a/A= | | | | |
+ | branch | =b= | | | | |
+ | bisect | =B= | | | | |
+ | commit | =c= | | | | |
+ | diff | =d/D= | | | | |
+ | ediff | =e/E= | | | | |
+ | fetch | =f= | | | | |
+ | pull | =F= | | | | |
+ | refresh | =g= | =gr/gR= (=g= in popup) | | | |
+ | help | =h/?= | | | =H/?= | |
+ | ignore | =i/I= | | | | |
+ | intent to stage | =I= | | | | |
+ | jump | =j= | =g= | | | |
+ | delete | =k= | =x= | | | |
+ | untrack | =K= | =X= | | | |
+ | log | =l/L= | | | =L/C-l= | |
+ | merge | =m= | | | | |
+ | remote | =M= | | | | |
+ | next section | =n= | =C-j= | | | |
+ | next section sibling | =M-n= | =gj= or =]= | | | |
+ | submodule | =o= | ' | | | |
+ | subtree | =O= | " | | | |
+ | prev section | =p= | =C-k= | | | |
+ | prev section sibling | =M-p= | =gk= or =[= | | | |
+ | push | =P= | =P= or =p= | | | |
+ | quit | =q= | =q= or =ESC= | | | |
+ | rebase | =r= | | | | |
+ | rename | =R= | | | | |
+ | stage | =s/S= | | | | |
+ | tag | =t= | | | | |
+ | notes | =T= | | | | |
+ | unstage | =u/U= | | | | |
+ | revert | =v/V= | =-/_= | | | |
+ | am | =w= | | | | |
+ | patch | =W= | | | | |
+ | reset | =x/X= | =o/O= | | | |
+ | show-refs | =y= | | =yr= (=y= in popup) | | |
+ | cherry | =Y= | | | | |
+ | stash | =z/Z= | | | | =Z= |
+ | git-cmd | =:= | =¦= | | | |
+ | run | =!= | | | | |
+ | forge | =@= | | | | |
+ | diff less/more context | =-/+= | = / + | | | |
+ | copy section info | =C-w= | | =ys= | | |
+ | copy buffer info | =M-w= | | =yb= | | |
+
+** New Commands
+
+ | Command | EM w/o yank opt | EM w/ yank opt (default) | w/ horiz move |
+ |-----------------------------+--------------------------+--------------------------+---------------|
+ | evil-goto-line | =G= | | |
+ | evil-next-visual-line | =j= | | |
+ | evil-previous-visual-line | =k= | | |
+ | evil-backward-char | under =M-x= | | =h= |
+ | evil-forward-char | under =M-x= | | =l= |
+ | evil-search-next | =n= | | |
+ | evil-search-previous | =N= | | |
+ | set-mark-command | =v= or =V= | =C-SPC= | |
+ | evil-visual-line | under =M-x= | =v= or =V= | |
+ | evil-ex | =:= | | |
+ | evil-search-forward | =/= | | |
+ | evil-scroll-page-up | =C-b= | | |
+ | evil-scroll-down | =C-d= | | |
+ | evil-scroll-page-down | =C-f= | | |
+ | evil-scroll-up | =C-u= (if =C-u= scrolls) | | |
+ | evil-emacs-state | =C-z= | | |
+ | evil-yank-line | under =M-x= | =yy= | |
+ | evil-window-map | under =M-x= | =C-w= | |
+ | evil-collection-magit-toggle-text-mode | =C-t/\= | | |
+
+
+ Any other bindings are meant to be consistent with these.
+
+ Use =evil-collection-magit-revert= to revert changes made by evil-collection-magit to the default
+ evil+magit behavior.
+
+** To add other common evil commands
+
+ Some may want =?= to search backward instead of launching the popup which is
+ also bound to =h=. To get this behavior, add the following line after
+ =(evil-collection-init)= in your configuration.
+
+ #+BEGIN_SRC emacs-lisp
+(evil-define-key evil-collection-magit-state magit-mode-map "?" 'evil-search-backward)
+ #+END_SRC
+
+ Most (but not all) magit bindings are in =magit-mode-map=, so other commands can
+ be bound in this way too.
+
+** To remove commands
+
+ Typically, to prevent evil-collection-magit from overriding the default behavior with evil
+ and magit loaded, you should bind the respective key to =nil= after loading
+ evil-collection-magit. For example, to make =escape= behave as default:
+
+ #+BEGIN_SRC emacs-lisp
+(evil-define-key* evil-collection-magit-state magit-mode-map [escape] nil)
+ #+END_SRC
+
+* Known Conflicts
+
+ These are the third-party packages that conflict with these bindings and will
+ probably need to be disabled in magit buffers for evil-collection-magit to work properly.
+
+ 1. [[https://github.com/hlissner/evil-snipe][evil-snipe]]
+ 2. [[https://github.com/syl20bnr/evil-escape][evil-escape]] with [[https://github.com/justbur/evil-magit/issues/4][certain escape sequences]]
+
+* Disclaimer
+
+ Given the complexity of magit key bindings combined with the complexity of git
+ itself, it is possible that there are some rough edges where the current binding
+ is not the expected one in a buffer. It will be very helpful for you to report
+ any such instances.
diff --git a/modes/magit/evil-collection-magit.el b/modes/magit/evil-collection-magit.el
index 8fbed2d..2a9c6fc 100644
--- a/modes/magit/evil-collection-magit.el
+++ b/modes/magit/evil-collection-magit.el
@@ -1,36 +1,39 @@
-;;; evil-collection-magit.el --- Bindings for `magit' -*- lexical-binding: t -*-
+;;; evil-collection-magit.el --- Evil-based key bindings for magit
-;; Copyright (C) 2018 James Nguyen
+;; Copyright (C) 2015-2016 Justin Burkett
-;; Author: James Nguyen <james@jojojames.com>
-;; Maintainer: James Nguyen <james@jojojames.com>
+;; Author: Justin Burkett <justin@burkett.cc>
+;; Maintainer: Justin Burkett <justin@burkett.cc>
+;; James Nguyen <james@jojojames.com>
;; Pierre Neidhardt <mail@ambrevar.xyz>
-;; URL: https://github.com/emacs-evil/evil-collection
-;; Version: 0.0.1
-;; Package-Requires: ((emacs "25.1"))
-;; Keywords: evil, emacs, tools
+;; Package-Requires: ((emacs "25.1") (evil "1.2.3") (magit "2.6.0"))
+;; Homepage: https://github.com/emacs-evil/evil-collection
+;; Version: 0.4.1
-;; This program is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation; either version 3, or (at your
+;; option) any later version.
+;;
+;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+;;
+;; For a full copy of the GNU General Public License
+;; see <http://www.gnu.org/licenses/>.
;;; Commentary:
-;;; Bindings for `magit'
-;;; This file is to work around an issue described in
-;;; https://github.com/emacs-evil/evil-collection/issues/108
-;;; Ideally this file is only temporary and should be removed once
-;;; #108 is resolved.
+
+;; This library configures Magit and Evil to play well with each
+;; other. For some background see https://github.com/magit/evil-magit/issues/1.
+
+;; See the README at
+;; https://github.com/emacs-evil/evil-collection/tree/master/modes/magit
+;; for a table describing the key binding changes.
;;; Code:
+
(require 'evil-collection)
(require 'magit nil t)
@@ -39,13 +42,616 @@
(defconst evil-collection-magit-maps '(magit-blame-mode-map
magit-blame-read-only-mode-map))
+(defcustom evil-collection-magit-use-y-for-yank t
+ "When non nil, replace \"y\" for `magit-show-refs-popup' with
+\"yy\" for `evil-yank-line', `ys' `magit-copy-section-value',
+\"yb\" for `magit-copy-buffer-revision' and \"yr\" for
+`magit-show-refs-popup'. This keeps \"y\" for
+`magit-show-refs-popup' in the help
+popup (`magit-dispatch-popup'). Default is t."
+ :group 'magit
+ :type 'boolean)
+
+(defcustom evil-collection-magit-want-horizontal-movement nil
+ "When non nil, use \"h\" and \"l\" for horizontal movement in
+most magit buffers. The old \"h\" for help is moved to \"H\" and
+similarly for the old \"l\" for the log popup. The old \"L\" is
+then put on \"C-l\"."
+ :group 'magit
+ :type 'boolean)
+
+(defcustom evil-collection-magit-use-z-for-folds nil
+ "When non nil, use \"z\" as a prefix for common vim fold commands, such as
+ - z1 Reset visibility to level 1 for all sections
+ - z2 Reset visibility to level 2 for all sections
+ - z3 Reset visibility to level 3 for all sections
+ - z4 Reset visibility to level 4 for all sections
+ - za Toggle a section
+ - zo Show section
+ - zO Show sections recursively
+ - zc Hide section
+ - zC Hide sections recursively
+ - zr Same as z4.
+
+When this option is enabled, the stash popup is available on \"Z\"."
+ :group 'magit
+ :type 'boolean)
+
+(defcustom evil-collection-magit-state (if evil-collection-magit-use-y-for-yank 'normal 'motion)
+ "State to use for most magit buffers."
+ :group 'magit
+ :type 'symbol)
+
+;; without this set-mark-command activates visual-state which is just annoying
+;; and introduces possible bugs
+(defun evil-collection-magit-remove-visual-activate-hook ()
+ (when (derived-mode-p 'magit-mode)
+ (remove-hook 'activate-mark-hook 'evil-visual-activate-hook t)))
+(add-hook 'evil-local-mode-hook 'evil-collection-magit-remove-visual-activate-hook)
+
+(defun evil-collection-magit-maybe-deactivate-mark ()
+ "Deactivate mark if region is active. Used for ESC binding."
+ (interactive)
+ (when (region-active-p) (deactivate-mark)))
+
+(defvar evil-collection-magit-emacs-to-default-state-modes
+ '(git-commit-mode)
+ "Modes that should be in the default evil state")
+
+(defvar evil-collection-magit-emacs-to-evil-collection-magit-state-modes
+ '(git-rebase-mode
+ magit-mode
+ magit-cherry-mode
+ magit-diff-mode
+ magit-log-mode
+ magit-log-select-mode
+ magit-process-mode
+ magit-reflog-mode
+ magit-refs-mode
+ magit-revision-mode
+ magit-stash-mode
+ magit-stashes-mode
+ magit-status-mode)
+ "Modes that switch from emacs state to `evil-collection-magit-state'")
+
+(defvar evil-collection-magit-default-to-evil-collection-magit-state-modes
+ '(magit-blob-mode
+ magit-gitflow-mode)
+ "Modes that switch from default state to `evil-collection-magit-state'")
+
+(defvar evil-collection-magit-untouched-modes
+ ;; TODO do something here
+ '(git-popup-mode
+ magit-blame-mode
+ magit-blame-read-only-mode
+ magit-file-mode)
+ "Modes whose evil states are unchanged")
+
+(defvar evil-collection-magit-ignored-modes
+ '(git-commit-major-mode
+ magit-auto-revert-mode
+ magit-blame-put-keymap-before-view-mode
+ magit-diff-mode
+ magit-merge-preview-mode
+ transient-resume-mode
+ magit-rebase-mode
+ magit-file-mode-major-mode
+ magit-wip-after-save-mode
+ magit-wip-after-save-local-mode-major-mode
+ magit-wip-after-save-local-mode
+ magit-wip-after-apply-mode
+ magit-wip-before-change-mode
+ magit-wip-initial-backup-mode
+ magit-wip-mode
+ ;; gh
+ magit-gh-pulls-mode
+ ;; git-gutter
+ git-gutter-mode
+ git-gutter-mode-major-mode
+ git-gutter+-commit-mode
+ git-gutter+-mode
+ git-gutter+-enable-fringe-display-mode
+ git-gutter+-enable-default-display-mode)
+ "Currently ignored modes. They are collected here for testing
+purposes.")
+
+(defun evil-collection-magit-set-initial-states ()
+ "Set the initial state for relevant modes."
+ (dolist (mode (append evil-collection-magit-emacs-to-evil-collection-magit-state-modes
+ evil-collection-magit-default-to-evil-collection-magit-state-modes))
+ (evil-set-initial-state mode evil-collection-magit-state))
+ (dolist (mode evil-collection-magit-emacs-to-default-state-modes)
+ (evil-set-initial-state mode evil-default-state)))
+
+(defun evil-collection-magit-revert-initial-states ()
+ "Revert the initial state for modes to their values before
+evil-collection-magit was loaded."
+ (dolist (mode (append evil-collection-magit-emacs-to-evil-collection-magit-state-modes
+ evil-collection-magit-emacs-to-default-state-modes))
+ (evil-set-initial-state mode 'emacs))
+ (dolist (mode evil-collection-magit-default-to-evil-collection-magit-state-modes)
+ (evil-set-initial-state mode evil-default-state)))
+
+(defvar evil-collection-magit-section-maps
+ '(magit-branch-section-map
+ magit-commit-section-map
+ magit-commit-message-section-map
+ magit-error-section-map
+ magit-file-section-map
+ magit-hunk-section-map
+ magit-module-commit-section-map
+ magit-remote-section-map
+ magit-staged-section-map
+ magit-stash-section-map
+ magit-stashes-section-map
+ magit-tag-section-map
+ magit-unpulled-section-map
+ magit-unpushed-section-map
+ magit-unstaged-section-map
+ magit-untracked-section-map
+
+ ;; new ones that I haven't looked at yet
+ magit-button-section-map
+ magit-commitbuf-section-map
+ magit-diffbuf-section-map
+ magit-diffstat-section-map
+ magit-headers-section-map
+ magit-message-section-map
+ ;; FIXME: deal with new bindings in this one
+ magit-module-section-map
+ magit-modules-section-map
+ magit-processbuf-section-map
+ magit-process-section-map
+ magit-pulls-section-map
+ magit-unmerged-section-map
+ magit-status-section-map
+ magit-worktree-section-map)
+ "All magit section maps. For testing purposes only at the
+moment.")
+
+;; Old way of excluding newlines
+;; (when evil-collection-magit-use-y-for-yank
+;; (dolist (map evil-collection-magit-section-maps)
+;; (when (and map (keymapp (symbol-value map)))
+;; (map-keymap
+;; (lambda (_ def)
+;; (when (commandp def)
+;; (evil-set-command-property def :exclude-newline t)))
+;; (symbol-value map)))))
+
+(defvar evil-collection-magit-in-visual-pre-command)
+
+(defun evil-collection-magit--around-visual-pre-command (orig-func &rest args)
+ (let ((evil-collection-magit-in-visual-pre-command t))
+ (apply orig-func args)))
+
+(defun evil-collection-magit--filter-args-visual-expand-region (arglist)
+ ;; pretend that the command has the :exclude-newline property by rewriting the
+ ;; EXCLUDE-NEWLINE arg to this function
+ (cons (and (bound-and-true-p evil-collection-magit-in-visual-pre-command)
+ (null (car arglist))
+ (eq (evil-visual-type) 'line)
+ (derived-mode-p 'magit-mode))
+ ;; shouldn't be necessary, but this will prevent it from failing if an
+ ;; arg is added.
+ (cdr arglist)))
+
+(when (and (fboundp 'advice-add) evil-collection-magit-use-y-for-yank)
+ (advice-add 'evil-visual-pre-command
+ :around #'evil-collection-magit--around-visual-pre-command)
+ (advice-add 'evil-visual-expand-region
+ :filter-args #'evil-collection-magit--filter-args-visual-expand-region))
+
+(defvar evil-collection-magit-mode-map-bindings
+ (let ((states (if evil-collection-magit-use-y-for-yank
+ `(,evil-collection-magit-state visual)
+ `(,evil-collection-magit-state))))
+ (append
+ `((,states magit-mode-map "g")
+ (,states magit-mode-map "C-j" magit-section-forward "n")
+ (,states magit-mode-map "gj" magit-section-forward-sibling "M-n")
+ (,states magit-mode-map "]" magit-section-forward-sibling "M-n")
+ (,states magit-mode-map "C-k" magit-section-backward "p")
+ (,states magit-mode-map "gk" magit-section-backward-sibling "M-p")
+ (,states magit-mode-map "[" magit-section-backward-sibling "M-p")
+ (,states magit-mode-map "gr" magit-refresh "g")
+ (,states magit-mode-map "gR" magit-refresh-all "G")
+ (,states magit-mode-map "x" magit-delete-thing "k")
+ (,states magit-mode-map "X" magit-file-untrack "K")
+ (,states magit-mode-map "-" magit-revert-no-commit "v")
+ (,states magit-mode-map "_" magit-revert "V")
+ (,states magit-mode-map "p" magit-push "P")
+ (,states magit-mode-map "o" magit-reset-quickly "x")
+ (,states magit-mode-map "O" magit-reset "X")
+ (,states magit-mode-map "|" magit-git-command ":")
+ (,states magit-mode-map "'" magit-submodule "o")
+ (,states magit-mode-map "\"" magit-subtree "O")
+ (,states magit-mode-map "=" magit-diff-less-context "-")
+ (,states magit-mode-map "@" forge-dispatch)
+ (,states magit-mode-map "j" evil-next-line)
+ (,states magit-mode-map "k" evil-previous-line)
+ (,states magit-mode-map "gg" evil-goto-first-line)
+ (,states magit-mode-map "G" evil-goto-line)
+ (,states magit-mode-map "C-d" evil-scroll-down)
+ (,states magit-mode-map "C-f" evil-scroll-page-down)
+ (,states magit-mode-map "C-b" evil-scroll-page-up)
+ (,states magit-mode-map ":" evil-ex)
+
+ ;; these are to fix the priority of the log mode map and the magit mode map
+ ;; FIXME: Conflict between this and revert. Revert seems more important here
+ ;; (,states magit-log-mode-map "-" magit-log-half-commit-limit "-")
+ (,states magit-log-mode-map "=" magit-log-toggle-commit-limit "=")
+
+ (,states magit-mode-map "S-SPC" magit-diff-show-or-scroll-up "SPC")
+ (,states magit-mode-map "S-DEL" magit-diff-show-or-scroll-down "DEL")
+
+ ((,evil-collection-magit-state) magit-mode-map "C-z" evil-emacs-state)
+ ((,evil-collection-magit-state) magit-mode-map "<escape>" magit-mode-bury-buffer))
+
+ (if (eq evil-search-module 'evil-search)
+ `((,states magit-mode-map "/" evil-ex-search-forward)
+ (,states magit-mode-map "n" evil-ex-search-next)
+ (,states magit-mode-map "N" evil-ex-search-previous))
+ `((,states magit-mode-map "/" evil-search-forward)
+ (,states magit-mode-map "n" evil-search-next)
+ (,states magit-mode-map "N" evil-search-previous)))
+
+ `((,states magit-status-mode-map "gz" magit-jump-to-stashes "jz")
+ (,states magit-status-mode-map "gt" magit-jump-to-tracked "jt")
+ (,states magit-status-mode-map "gn" magit-jump-to-untracked "jn")
+ (,states magit-status-mode-map "gu" magit-jump-to-unstaged "ju")
+ (,states magit-status-mode-map "gs" magit-jump-to-staged "js")
+ (,states magit-status-mode-map "gfu" magit-jump-to-unpulled-from-upstream "jfu")
+ (,states magit-status-mode-map "gfp" magit-jump-to-unpulled-from-pushremote "jfp")
+ (,states magit-status-mode-map "gpu" magit-jump-to-unpushed-to-upstream "jpu")
+ (,states magit-status-mode-map "gpp" magit-jump-to-unpushed-to-pushremote "jpp")
+ (,states magit-status-mode-map "gh" magit-section-up "^")
+ (,states magit-diff-mode-map "gj" magit-section-forward)
+ (,states magit-diff-mode-map "gd" magit-jump-to-diffstat-or-diff "j")
+ ;; NOTE This is now transient-map and the binding is C-g.
+ ;; ((emacs) magit-popup-mode-map "<escape>" "q")
+ )
+
+ (when evil-collection-magit-want-horizontal-movement
+ `((,states magit-mode-map "H" magit-dispatch "h")
+ (,states magit-mode-map "L" magit-log "l")
+ (,states magit-mode-map "C-l" magit-log-refresh "L")
+ (,states magit-mode-map "h" evil-backward-char)
+ (,states magit-mode-map "l" evil-forward-char)))
+
+ (when evil-want-C-u-scroll
+ `((,states magit-mode-map "C-u" evil-scroll-up)))
+
+ (if evil-collection-magit-use-y-for-yank
+ `((,states magit-mode-map "v" evil-visual-line)
+ (,states magit-mode-map "V" evil-visual-line)
+ (,states magit-mode-map "C-w" evil-window-map)
+ (,states magit-mode-map "y")
+ (,states magit-mode-map "yy" evil-yank-line)
+ (,states magit-mode-map "yr" magit-show-refs "y")
+ (,states magit-mode-map "ys" magit-copy-section-value "C-w")
+ (,states magit-mode-map "yb" magit-copy-buffer-revision "M-w")
+ ((visual) magit-mode-map "y" evil-yank))
+ `((,states magit-mode-map "v" set-mark-command)
+ (,states magit-mode-map "V" set-mark-command)
+ (,states magit-mode-map "<escape>" evil-collection-magit-maybe-deactivate-mark)))
+
+ (when evil-collection-magit-use-z-for-folds
+ `((,states magit-mode-map "z")
+ (,states magit-mode-map "z1" magit-section-show-level-1-all)
+ (,states magit-mode-map "z2" magit-section-show-level-2-all)
+ (,states magit-mode-map "z3" magit-section-show-level-3-all)
+ (,states magit-mode-map "z4" magit-section-show-level-4-all)
+ (,states magit-mode-map "za" magit-section-toggle)
+ (,states magit-mode-map "zc" magit-section-hide)
+ (,states magit-mode-map "zC" magit-section-hide-children)
+ (,states magit-mode-map "zo" magit-section-show)
+ (,states magit-mode-map "zO" magit-section-show-children)
+ (,states magit-mode-map "zr" magit-section-show-level-4-all)))))
+ "evil-collection-magit bindings for major modes. Each element of this list
+takes the form
+
+\(EVIL-STATE MAGIT-MAP NEW-KEY DEF ORIG-KEY\).
+
+ORIG-KEY is only used for testing purposes, and
+denotes the original magit key for this command.")
+
+(dolist (binding evil-collection-magit-mode-map-bindings)
+ (when binding
+ (dolist (state (nth 0 binding))
+ (evil-collection-define-key
+ state (nth 1 binding) (nth 2 binding) (nth 3 binding)))))
+
+(defvar evil-collection-magit-minor-mode-map-bindings
+ `(((,evil-collection-magit-state visual) magit-blob-mode-map "gj" magit-blob-next "n")
+ ((,evil-collection-magit-state visual) magit-blob-mode-map "gk" magit-blob-previous "p")
+ ((,evil-collection-magit-state visual) git-commit-mode-map "gk" git-commit-prev-message "M-p")
+ ((,evil-collection-magit-state visual) git-commit-mode-map "gj" git-commit-next-message "M-n")
+ ((normal) magit-blame-read-only-mode-map "j" evil-next-line)
+ ((normal) magit-blame-read-only-mode-map "C-j" magit-blame-next-chunk "n")
+ ((normal) magit-blame-read-only-mode-map "gj" magit-blame-next-chunk "n")
+ ((normal) magit-blame-read-only-mode-map "gJ" magit-blame-next-chunk-same-commit "N")
+ ((normal) magit-blame-read-only-mode-map "k" evil-previous-line)
+ ((normal) magit-blame-read-only-mode-map "C-k" magit-blame-previous-chunk "p")
+ ((normal) magit-blame-read-only-mode-map "gk" magit-blame-previous-chunk "p")
+ ((normal) magit-blame-read-only-mode-map "gK" magit-blame-previous-chunk-same-commit "P"))
+ "evil-collection-magit bindings for minor modes. Each element of
+this list takes the form
+
+\(EVIL-STATE MAGIT-MAP NEW-KEY DEF ORIG-KEY)\.
+
+ORIG-KEY is only used for testing purposes, and
+denotes the original magit key for this command.")
+
+(dolist (binding evil-collection-magit-minor-mode-map-bindings)
+ (when binding
+ (dolist (state (nth 0 binding))
+ ;; TODO: Maybe switch to `evil-define-minor-mode-key'
+ (evil-collection-define-key
+ state (nth 1 binding) (nth 2 binding) (nth 3 binding)))))
+
+;; Make relevant maps into overriding maps so that they shadow the global evil
+;; maps by default
+(dolist (map (list magit-mode-map
+ magit-cherry-mode-map
+ magit-mode-map
+ magit-blob-mode-map
+ magit-diff-mode-map
+ magit-log-mode-map
+ magit-log-select-mode-map
+ magit-reflog-mode-map
+ magit-status-mode-map
+ magit-file-mode-map
+ magit-log-read-revs-map
+ magit-process-mode-map
+ magit-refs-mode-map))
+ (evil-make-overriding-map map (if evil-collection-magit-use-y-for-yank
+ 'all
+ evil-collection-magit-state)))
+
+(evil-make-overriding-map magit-blame-read-only-mode-map 'normal)
+
+(eval-after-load 'magit-gh-pulls
+ `(evil-make-overriding-map magit-gh-pulls-mode-map ',evil-collection-magit-state))
+
+;; Need to refresh evil keymaps when blame mode is entered.
+(add-hook 'magit-blame-mode-hook 'evil-normalize-keymaps)
+
+(evil-set-initial-state 'magit-repolist-mode 'motion)
+(evil-define-key 'motion magit-repolist-mode-map
+ (kbd "RET") 'magit-repolist-status
+ (kbd "gr") 'magit-list-repositories)
+(add-hook 'magit-repolist-mode-hook 'evil-normalize-keymaps)
+
+(evil-set-initial-state 'magit-submodule-list-mode 'motion)
+(evil-define-key 'motion magit-submodule-list-mode-map
+ (kbd "RET") 'magit-repolist-status
+ (kbd "gr") 'magit-list-submodules)
+(add-hook 'magit-submodule-list-mode-hook 'evil-normalize-keymaps)
+
+
+(eval-after-load 'git-rebase
+ `(progn
+ ;; for the compiler
+ (defvar git-rebase-mode-map)
+ (defvar evil-collection-magit-rebase-commands-w-descriptions
+ ;; nil in the first element means don't bind here
+ '(("p" git-rebase-pick "pick = use commit")
+ ("r" git-rebase-reword "reword = use commit, but edit the commit message")
+ ("e" git-rebase-edit "edit = use commit, but stop for amending")
+ ("s" git-rebase-squash "squash = use commit, but meld into previous commit")
+ ("f" git-rebase-fixup "fixup = like \"squash\", but discard this commit's log message")
+ ("x" git-rebase-exec "exec = run command (the rest of the line) using shell")
+ ("d" git-rebase-kill-line "drop = remove commit" "k")
+ ("u" git-rebase-undo "undo last change")
+ (nil with-editor-finish "tell Git to make it happen")
+ (nil with-editor-cancel "tell Git that you changed your mind, i.e. abort")
+ ("k" evil-previous-line "move point to previous line" "p")
+ ("j" evil-next-line "move point to next line" "n")
+ ("M-k" git-rebase-move-line-up "move the commit at point up" "\M-p")
+ ("M-j" git-rebase-move-line-down "move the commit at point down" "\M-n")
+ (nil git-rebase-show-commit "show the commit at point in another buffer")))
+
+ (dolist (cmd evil-collection-magit-rebase-commands-w-descriptions)
+ (when (car cmd)
+ (evil-collection-define-key evil-collection-magit-state 'git-rebase-mode-map
+ (car cmd) (nth 1 cmd))))
+
+ (evil-make-overriding-map git-rebase-mode-map evil-collection-magit-state)
+
+ (defun evil-collection-magit-add-rebase-messages ()
+ "Remove evil-state annotations and reformat git-rebase buffer."
+ (goto-char (point-min))
+ (let ((inhibit-read-only t)
+ (state-regexp (format "<%s-state> " evil-collection-magit-state))
+ (aux-map (evil-get-auxiliary-keymap git-rebase-mode-map evil-collection-magit-state)))
+ (save-excursion
+ (save-match-data
+ (flush-lines "^#.+ = ")
+ (goto-char (point-min))
+ (when (and (boundp 'git-rebase-show-instructions)
+ git-rebase-show-instructions
+ (re-search-forward "^# Commands:\n" nil t))
+ (dolist (cmd evil-collection-magit-rebase-commands-w-descriptions)
+ (insert
+ (format "# %-8s %s\n"
+ (if (and (car cmd)
+ (eq (nth 1 cmd)
+ (lookup-key aux-map (kbd (car cmd)))))
+ (car cmd)
+ (replace-regexp-in-string
+ state-regexp ""
+ (substitute-command-keys
+ (format "\\[%s]" (nth 1 cmd)))))
+ (nth 2 cmd)))))))))
+ (remove-hook 'git-rebase-mode-hook 'git-rebase-mode-show-keybindings)
+ (add-hook 'git-rebase-mode-hook 'evil-collection-magit-add-rebase-messages t)))
+
+;; section maps: evil's auxiliary maps don't work here, because these maps are
+;; text overlays
+
+(defun evil-collection-magit-stage-untracked-file-with-intent ()
+ "Call `magit-stage-untracked' with optional arg."
+ (interactive)
+ (when (and (derived-mode-p 'magit-mode)
+ (magit-apply--get-selection)
+ (eq (magit-diff-type) 'untracked))
+ (magit-stage-untracked t)))
+
+(defvar evil-collection-magit-original-section-bindings
+ `((,(copy-keymap magit-file-section-map) "\C-j" magit-diff-visit-file-worktree)
+ (,(copy-keymap magit-hunk-section-map) "\C-j" magit-diff-visit-file-worktree))
+ "For testing purposes only. The original magit keybindings that
+evil-collection-magit affects.")
+
+(defun evil-collection-magit-adjust-section-bindings ()
+ "Revert changed bindings in section maps generated by evil-collection-magit"
+ (define-key magit-file-section-map "I"
+ 'evil-collection-magit-stage-untracked-file-with-intent)
+ (define-key magit-file-section-map "\C-j" nil) ; breaking change
+ (define-key magit-hunk-section-map "\C-j" nil)) ; breaking change
+
+(defun evil-collection-magit-revert-section-bindings ()
+ "Revert changed bindings in section maps generated by evil-collection-magit"
+ (define-key magit-file-section-map "I" nil)
+ (define-key magit-file-section-map "\C-j" 'magit-diff-visit-file-worktree)
+ (define-key magit-hunk-section-map "\C-j" 'magit-diff-visit-file-worktree))
+
+;; Popups
+
+(defvar evil-collection-magit-dispatch-popup-backup
+ (copy-tree (get 'magit-dispatch 'transient--layout) t))
+(defvar evil-collection-magit-popup-keys-changed nil)
+
+(defvar evil-collection-magit-popup-changes
+ (append
+ (when evil-collection-magit-use-z-for-folds
+ '((magit-dispatch "z" "Z" magit-stash)))
+ (when evil-collection-magit-want-horizontal-movement
+ '((magit-dispatch "L" "\C-l" magit-log-refresh)
+ (magit-dispatch "l" "L" magit-log)))
+ '((magit-branch "x" "X" magit-branch-reset)
+ (magit-branch "k" "x" magit-branch-delete)
+ (magit-dispatch "o" "'" magit-submodule)
+ (magit-dispatch "O" "\"" magit-subtree)
+ (magit-dispatch "V" "_" magit-revert)
+ (magit-dispatch "X" "O" magit-reset)
+ (magit-dispatch "v" "-" magit-reverse)
+ (magit-dispatch "k" "x" magit-discard)
+ (magit-remote "k" "x" magit-remote-remove)
+ (magit-revert "v" "o" magit-revert-no-commit)
+ ;; FIXME: how to properly handle a popup with a key that appears twice (in
+ ;; `define-transient-command' definition)? Currently we rely on:
+ ;; 1. first call to `evil-collection-magit-change-popup-key' changes the first "V"
+ ;; entry of `magit-revert' (the first entry in `define-transient-command'
+ ;; definition of `magit-revert'), second call changes the second "V".
+ ;; 2. the remapping here are in the same order as in `magit-revert'
+ ;; definition
+ (magit-revert "V" "O" magit-revert-and-commit)
+ (magit-revert "V" "O" magit-sequencer-continue)
+ (magit-tag "k" "x" magit-tag-delete)))
+ "Changes to popup keys")
+
+(defun evil-collection-magit-change-popup-key (popup from to &rest _args)
+ "Wrap `magit-change-popup-key'."
+ (transient-suffix-put popup from :key to))
+
+(defun evil-collection-magit-adjust-popups ()
+ "Adjust popup keys to match evil-collection-magit."
+ (unless evil-collection-magit-popup-keys-changed
+ (dolist (change evil-collection-magit-popup-changes)
+ (apply #'evil-collection-magit-change-popup-key change))
+ (with-eval-after-load 'forge
+ (transient-remove-suffix 'magit-dispatch 'forge-dispatch)
+ (transient-append-suffix 'magit-dispatch "!"
+ '("@" "Forge" forge-dispatch)))
+ (setq evil-collection-magit-popup-keys-changed t)))
+
+(defun evil-collection-magit-revert-popups ()
+ "Revert popup keys changed by evil-collection-magit."
+ (put 'magit-dispatch 'transient--layout evil-collection-magit-dispatch-popup-backup)
+ (when evil-collection-magit-popup-keys-changed
+ (dolist (change evil-collection-magit-popup-changes)
+ (evil-collection-magit-change-popup-key
+ (nth 0 change) (nth 2 change) (nth 1 change)))
+ (with-eval-after-load 'forge
+ (transient-suffix-put 'magit-dispatch "@" :key "'"))
+ (setq evil-collection-magit-popup-keys-changed nil)))
+
+;;;###autoload
+(defun evil-collection-magit-init ()
+ "This function completes the setup of evil-collection-magit. It is called
+automatically when evil-collection-magit-setup is called.. The only reason to use
+this function is if you've called `evil-collection-magit-revert' and wish to
+go back to evil-collection-magit behavior."
+ (interactive)
+ (evil-collection-magit-adjust-section-bindings)
+ (evil-collection-magit-adjust-popups)
+ (evil-collection-magit-set-initial-states))
+
+;;;###autoload
+(defun evil-collection-magit-revert ()
+ "Revert changes by evil-collection-magit that affect default evil+magit behavior."
+ (interactive)
+ (evil-collection-magit-revert-section-bindings)
+ (evil-collection-magit-revert-popups)
+ (evil-collection-magit-revert-initial-states)
+ (message "evil-collection-magit reverted"))
+
+(define-minor-mode evil-collection-magit-toggle-text-minor-mode
+ "Minor mode used to enabled toggle key in `text-mode' after
+using `evil-collection-magit-toggle-text-mode'"
+ :keymap (make-sparse-keymap))
+
+(evil-define-key 'normal evil-collection-magit-toggle-text-minor-mode-map
+ "\C-t" 'evil-collection-magit-toggle-text-mode
+ "\\" 'evil-collection-magit-toggle-text-mode)
+(evil-define-key evil-collection-magit-state magit-mode-map
+ "\C-t" 'evil-collection-magit-toggle-text-mode
+ "\\" 'evil-collection-magit-toggle-text-mode)
+
+(defvar evil-collection-magit-last-mode nil
+ "Used to store last magit mode before entering text mode using
+`evil-collection-magit-toggle-text-mode'.")
+
+(defun evil-collection-magit-toggle-text-mode ()
+ "Switch to `text-mode' and back from magit buffers."
+ (interactive)
+ (cond ((derived-mode-p 'magit-mode)
+ (setq evil-collection-magit-last-mode major-mode)
+ (message "Switching to text-mode")
+ (text-mode)
+ (evil-collection-magit-toggle-text-minor-mode 1)
+ (evil-normalize-keymaps))
+ ((and (eq major-mode 'text-mode)
+ (functionp evil-collection-magit-last-mode))
+ (message "Switching to %s" evil-collection-magit-last-mode)
+ (evil-collection-magit-toggle-text-minor-mode -1)
+ (evil-normalize-keymaps)
+ (funcall evil-collection-magit-last-mode)
+ (magit-refresh)
+ (evil-change-state evil-collection-magit-state))
+ (t
+ (user-error "evil-collection-magit-toggle-text-mode unexpected state"))))
+
;;;###autoload
(defun evil-collection-magit-setup ()
"Set up `evil' bindings for `magit'."
+
+ ;; This is to work around an issue described in
+ ;; https://github.com/emacs-evil/evil-collection/issues/108
+ ;; Ideally this file is only temporary and should be removed once
+ ;; #108 is resolved.
(evil-collection-define-key 'normal 'magit-blame-mode-map
"q" 'magit-blame-quit)
(evil-collection-define-key 'normal 'magit-blame-read-only-mode-map
- "q" 'magit-blame-quit))
+ "q" 'magit-blame-quit)
+
+ (evil-collection-magit-init))
+;;; evil-collection-magit.el ends soon
(provide 'evil-collection-magit)
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
;;; evil-collection-magit.el ends here
diff --git a/test/evil-collection-magit-tests.el b/test/evil-collection-magit-tests.el
new file mode 100644
index 0000000..9b4c892
--- /dev/null
+++ b/test/evil-collection-magit-tests.el
@@ -0,0 +1,112 @@
+;;; evil-collection-magit-tests.el --- evil-based key bindings for magit
+
+;; Copyright (C) 2015-2016 Justin Burkett
+
+;; Author: Justin Burkett <justin@burkett.cc>
+;; Homepage: https://github.com/emacs-evil/evil-collection
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation; either version 3, or (at your
+;; option) any later version.
+;;
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; For a full copy of the GNU General Public License
+;; see <http://www.gnu.org/licenses/>.
+(require 'evil-collection)
+(evil-collection-require 'magit)
+
+(ert-deftest evil-collection-magit-mode-map-tests ()
+ "Test that original bindings in `evil-collection-magit-mode-map-bindings'
+are correct."
+ (dolist (binding evil-collection-magit-mode-map-bindings)
+ (when (nth 4 binding)
+ (should (eq (lookup-key (symbol-value (nth 1 binding)) (kbd (nth 4 binding)))
+ (nth 3 binding)))))
+ (dolist (binding evil-collection-magit-minor-mode-map-bindings)
+ (when (nth 4 binding)
+ (should (eq (lookup-key (symbol-value (nth 1 binding)) (kbd (nth 4 binding)))
+ (nth 3 binding))))))
+
+(ert-deftest evil-collection-magit-section-map-tests ()
+ "Test that original bindings in
+`evil-collection-magit-original-section-bindings' are correct."
+ (dolist (binding evil-collection-magit-original-section-bindings)
+ (should (eq (lookup-key (nth 0 binding) (nth 1 binding))
+ (nth 2 binding)))))
+
+;; (ert-deftest evil-collection-magit-popup-action-tests ()
+;; "Test that bindings are as expected in popups."
+;; (when evil-collection-magit-popup-keys-changed
+;; (dolist (change evil-collection-magit-popup-changes)
+;; (let ((alist (plist-get (symbol-value (nth 0 change)) (nth 1 change))))
+;; (should
+;; (eq (nth 2 (assoc (string-to-char (nth 3 change)) alist))
+;; (nth 4 change)))))))
+
+(defun evil-collection-magit-collect-magit-section-maps ()
+ (let (res)
+ (mapatoms
+ (lambda (sym)
+ (when (string-match-p "^magit-.*-section-map$" (symbol-name sym))
+ (push sym res))))
+ res))
+
+(setq evil-collection-magit-section-maps-test (evil-collection-magit-collect-magit-section-maps))
+;; (setq evil-collection-magit-commands-in-section-maps
+;; (let (res)
+;; (dolist (map evil-collection-magit-section-maps-test)
+;; (when (and (boundp map) (keymapp (symbol-value map)))
+;; (map-keymap
+;; (lambda (_ def)
+;; (when (commandp def)
+;; (if res
+;; (add-to-list 'res def)
+;; (setq res (list def)))))
+;; (symbol-value map))))
+;; res))
+
+(ert-deftest evil-collection-magit-section-maps-accounted-for ()
+ "Check that `evil-collection-magit-section-maps' includes all section-maps
+we can find."
+ (dolist (map evil-collection-magit-section-maps-test)
+ (when (and (boundp map) (keymapp (symbol-value map)))
+ (should (memq map evil-collection-magit-section-maps)))))
+
+(defun evil-collection-magit-collect-git-magit-modes ()
+ (let (res)
+ (mapatoms
+ (lambda (sym)
+ (when (and (or (boundp sym)
+ (fboundp sym))
+ (string-match-p "^\\(git\\|magit\\)-.*-mode$" (symbol-name sym)))
+ (push sym res))))
+ res))
+
+(ert-deftest evil-collection-magit-all-modes-accounted-for ()
+ "Check that mode lists include all modes we can find."
+ (let ((modes (evil-collection-magit-collect-git-magit-modes))
+ res)
+ (dolist (mode modes)
+ (when (boundp mode)
+ (should (memq mode
+ (append
+ evil-collection-magit-emacs-to-default-state-modes
+ evil-collection-magit-emacs-to-evil-collection-magit-state-modes
+ evil-collection-magit-default-to-evil-collection-magit-state-modes
+ evil-collection-magit-untouched-modes
+ evil-collection-magit-ignored-modes)))))))
+
+(ert-deftest evil-collection-magit-expand-region-arg-number ()
+ "Check that the number of args accepted by
+`evil-visual-expand-region' does not change."
+ (should-not (evil-visual-expand-region))
+ (should-not (evil-visual-expand-region t))
+ (should-error (evil-visual-expand-region t t) :type
+ 'wrong-number-of-arguments))
+
+;;; evil-collection-magit-tests.el ends here