jmorag/kakoune.el

A very simple simulation of the kakoune editor inside of emacs.

Emacs Lisp

168

44 commits

updated Feb 6, 2023

See the code

README

* kakoune.el
A very simple simulation of kakoune inside of emacs. Most of the heavy lifting is provided by [[https://github.com/Kungsgeten/ryo-modal][ryo-modal-mode]], [[https://github.com/magnars/multiple-cursors.el][multiple-cursors.el]], and [[https://github.com/magnars/expand-region.el][expand-region]]. For extended customization, it is recommended that you install these packages and customize their interfaces directly.

** Installation
   =kakoune.el= is on melpa, so for users of =use-package=, installation should be as simple as
#+BEGIN_SRC elisp
(use-package kakoune)
#+END_SRC
.
** Goals
   This is not meant to be a feature-complete emulation of kakoune, like evil is to vim. Evil is a massive undertaking, which reimplements a huge number of editing primitives from scratch. By contrast, kakoune.el uses emacs's native behavior wherever possible. In particular, it binds absolutely no keys in "insert mode," letting you gradually adjust to "normal mode" at your own pace. To actually access normal mode, you can either call =(ryo-modal-mode)= with =M-x=, or bind a key globally. For the true modal experience: =(global-set-key (kbd "<escape>") 'ryo-modal-mode)=.
** Notable differences from Kakoune
- The 'v' family of commands is not implemented here. By default, 'v' is bound to =expand-region=, which I personally find vastly more useful.
- Searching is very different. A cursory glance at evil's =evil-search-forward= function suggests that implementing a search model where 'n' and 'N' take you forwards and backwards in the search ring while not in =isearch-mode= is non-trivial. I just use emacs's native incremental search with =C-s= and =C-r=. If I'm wrong about getting 'n' and 'N' functionality being difficult, though, please let me know.
- This package also includes a couple bindings from =evil-unimpaired= and =evil-exchange= that aren't present in the original.
- 'q' and 'Q' don't toggle and execute macros. [[https://www.emacswiki.org/emacs/KeyboardMacros][This emacswiki article]] seems to suggest that it's possible to have one key start and end a macro, but it's been buggy in my experience, so I just use the default =C-x (= and friends.   
- ';' doesn't shrink regions by default. This is simply my personal preference. You can get this behavior with =(ryo-modal-key ";" 'kakoune-deactivate-mark)=.
- '>' and '<' behave normally on when the region is active, but when given a count when the region is inactive, they indent that many lines instead of indenting the current line that many times. This is also my personal preference, but if anyone wants kakoune's default behavior for this, open an issue and I'll change it.  
- I was never a full time Kakoune user, so there will certainly be many other small things that aren't covered here. Please open issues or PR's if you feel that including omissions would be useful to you or others.
** Configuration

The following is my entire configuration that I use every day with respect to overriding and extending the functionality provided by this package.
#+BEGIN_SRC elisp
(use-package kakoune
  ;; Having a non-chord way to escape is important, since key-chords don't work in macros
  :bind ("C-z" . ryo-modal-mode)
  :hook (after-init . my/kakoune-setup)
  :config
  (defun ryo-enter () "Enter normal mode" (interactive) (ryo-modal-mode 1))
  (defun my/kakoune-setup ()
      "Call kakoune-setup-keybinds and then add some personal config."
      (kakoune-setup-keybinds)
      (setq ryo-modal-cursor-type 'box)
      (add-hook 'prog-mode-hook #'ryo-enter)
      (define-key ryo-modal-mode-map (kbd "SPC h") 'help-command)
      ;; Access all C-x bindings easily
      (define-key ryo-modal-mode-map (kbd "z") ctl-x-map)
      (ryo-modal-keys
       ("," save-buffer)
       ("P" counsel-yank-pop)
       ("m" mc/mark-next-like-this)
       ("M" mc/skip-to-next-like-this)
       ("n" mc/mark-previous-like-this)
       ("N" mc/skip-to-previous-like-this)
       ("M-m" mc/edit-lines)
       ("*" mc/mark-all-like-this)
       ("v" er/expand-region)
       ("C-v" set-rectangular-region-anchor)
       ("M-s" mc/split-region)
       (";" (("q" delete-window)
             ("v" split-window-horizontally)
             ("s" split-window-vertically)))
       ("C-h" windmove-left)
       ("C-j" windmove-down)
       ("C-k" windmove-up)
       ("C-l" windmove-right)
       ("C-u" scroll-down-command :first '(deactivate-mark))
       ("C-d" scroll-up-command :first '(deactivate-mark)))))

;; This overrides the default mark-in-region with a prettier-looking one,
;; and provides a couple extra commands
(use-package visual-regexp
  :ryo
  ("s" vr/mc-mark)
  ("?" vr/replace)
  ("M-/" vr/query-replace))

;; Emacs incremental search doesn't work with multiple cursors, but this fixes that
(use-package phi-search
  :bind (("C-s" . phi-search)
         ("C-r" . phi-search-backward)))

;; Probably the first thing you'd miss is undo and redo, which requires an extra package
;; to work like it does in kakoune (and almost every other editor).
(use-package undo-tree
  :config
  (global-undo-tree-mode)
  :ryo
  ("u" undo-tree-undo)
  ("U" undo-tree-redo)
  ("SPC u" undo-tree-visualize)
  :bind (:map undo-tree-visualizer-mode-map
              ("h" . undo-tree-visualize-switch-branch-left)
              ("j" . undo-tree-visualize-redo)
              ("k" . undo-tree-visualize-undo)
              ("l" . undo-tree-visualize-switch-branch-right)))

#+END_SRC
It's kind of a lot, but it does provide a good example of how to override defaults and bind your own keys to your own commands. I use this daily, along with the rest of the commands provided by the package, and am very happy with it.

** Known bugs
- There will be many commands that, when run for the first time with multiple active selections, will prompt you "Run <command> for all cursors? (y/n)." Hitting 'y' will add the command to an .mc-lists file in your .emacs.d and save your preference. There should be a way to add all the kakoune-* functions to this list by default, but because ryo adds hashes to the beginnings of commands, this isn't as easy as it could be. This problem is usually just a one-time annoyance, but it can be irritating. I would gladly accept a PR to fix it.
- Certain commands, like =kakoune-replace-char= and =kakoune-select-to-char= prompt for input once per cursor, instead of using the first input for each one. This behavior is unlike Kakoune's but shouldn't be. Fixing it shouldn't be too difficult, but requires more knowledge of =multiple-cursors.el='s implementation than I have.
- When you first download this package, it is possible that non-primary cursors will have a weird color in insert mode. I have no idea why that is, but restarting emacs should take care of it.  

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

jmorag

44 commits

jmorag/kakoune.el

A very simple simulation of the kakoune editor inside of emacs.

Emacs Lisp

168

44 commits

updated Feb 6, 2023

See the code

README

* kakoune.el
A very simple simulation of kakoune inside of emacs. Most of the heavy lifting is provided by [[https://github.com/Kungsgeten/ryo-modal][ryo-modal-mode]], [[https://github.com/magnars/multiple-cursors.el][multiple-cursors.el]], and [[https://github.com/magnars/expand-region.el][expand-region]]. For extended customization, it is recommended that you install these packages and customize their interfaces directly.

** Installation
   =kakoune.el= is on melpa, so for users of =use-package=, installation should be as simple as
#+BEGIN_SRC elisp
(use-package kakoune)
#+END_SRC
.
** Goals
   This is not meant to be a feature-complete emulation of kakoune, like evil is to vim. Evil is a massive undertaking, which reimplements a huge number of editing primitives from scratch. By contrast, kakoune.el uses emacs's native behavior wherever possible. In particular, it binds absolutely no keys in "insert mode," letting you gradually adjust to "normal mode" at your own pace. To actually access normal mode, you can either call =(ryo-modal-mode)= with =M-x=, or bind a key globally. For the true modal experience: =(global-set-key (kbd "<escape>") 'ryo-modal-mode)=.
** Notable differences from Kakoune
- The 'v' family of commands is not implemented here. By default, 'v' is bound to =expand-region=, which I personally find vastly more useful.
- Searching is very different. A cursory glance at evil's =evil-search-forward= function suggests that implementing a search model where 'n' and 'N' take you forwards and backwards in the search ring while not in =isearch-mode= is non-trivial. I just use emacs's native incremental search with =C-s= and =C-r=. If I'm wrong about getting 'n' and 'N' functionality being difficult, though, please let me know.
- This package also includes a couple bindings from =evil-unimpaired= and =evil-exchange= that aren't present in the original.
- 'q' and 'Q' don't toggle and execute macros. [[https://www.emacswiki.org/emacs/KeyboardMacros][This emacswiki article]] seems to suggest that it's possible to have one key start and end a macro, but it's been buggy in my experience, so I just use the default =C-x (= and friends.   
- ';' doesn't shrink regions by default. This is simply my personal preference. You can get this behavior with =(ryo-modal-key ";" 'kakoune-deactivate-mark)=.
- '>' and '<' behave normally on when the region is active, but when given a count when the region is inactive, they indent that many lines instead of indenting the current line that many times. This is also my personal preference, but if anyone wants kakoune's default behavior for this, open an issue and I'll change it.  
- I was never a full time Kakoune user, so there will certainly be many other small things that aren't covered here. Please open issues or PR's if you feel that including omissions would be useful to you or others.
** Configuration

The following is my entire configuration that I use every day with respect to overriding and extending the functionality provided by this package.
#+BEGIN_SRC elisp
(use-package kakoune
  ;; Having a non-chord way to escape is important, since key-chords don't work in macros
  :bind ("C-z" . ryo-modal-mode)
  :hook (after-init . my/kakoune-setup)
  :config
  (defun ryo-enter () "Enter normal mode" (interactive) (ryo-modal-mode 1))
  (defun my/kakoune-setup ()
      "Call kakoune-setup-keybinds and then add some personal config."
      (kakoune-setup-keybinds)
      (setq ryo-modal-cursor-type 'box)
      (add-hook 'prog-mode-hook #'ryo-enter)
      (define-key ryo-modal-mode-map (kbd "SPC h") 'help-command)
      ;; Access all C-x bindings easily
      (define-key ryo-modal-mode-map (kbd "z") ctl-x-map)
      (ryo-modal-keys
       ("," save-buffer)
       ("P" counsel-yank-pop)
       ("m" mc/mark-next-like-this)
       ("M" mc/skip-to-next-like-this)
       ("n" mc/mark-previous-like-this)
       ("N" mc/skip-to-previous-like-this)
       ("M-m" mc/edit-lines)
       ("*" mc/mark-all-like-this)
       ("v" er/expand-region)
       ("C-v" set-rectangular-region-anchor)
       ("M-s" mc/split-region)
       (";" (("q" delete-window)
             ("v" split-window-horizontally)
             ("s" split-window-vertically)))
       ("C-h" windmove-left)
       ("C-j" windmove-down)
       ("C-k" windmove-up)
       ("C-l" windmove-right)
       ("C-u" scroll-down-command :first '(deactivate-mark))
       ("C-d" scroll-up-command :first '(deactivate-mark)))))

;; This overrides the default mark-in-region with a prettier-looking one,
;; and provides a couple extra commands
(use-package visual-regexp
  :ryo
  ("s" vr/mc-mark)
  ("?" vr/replace)
  ("M-/" vr/query-replace))

;; Emacs incremental search doesn't work with multiple cursors, but this fixes that
(use-package phi-search
  :bind (("C-s" . phi-search)
         ("C-r" . phi-search-backward)))

;; Probably the first thing you'd miss is undo and redo, which requires an extra package
;; to work like it does in kakoune (and almost every other editor).
(use-package undo-tree
  :config
  (global-undo-tree-mode)
  :ryo
  ("u" undo-tree-undo)
  ("U" undo-tree-redo)
  ("SPC u" undo-tree-visualize)
  :bind (:map undo-tree-visualizer-mode-map
              ("h" . undo-tree-visualize-switch-branch-left)
              ("j" . undo-tree-visualize-redo)
              ("k" . undo-tree-visualize-undo)
              ("l" . undo-tree-visualize-switch-branch-right)))

#+END_SRC
It's kind of a lot, but it does provide a good example of how to override defaults and bind your own keys to your own commands. I use this daily, along with the rest of the commands provided by the package, and am very happy with it.

** Known bugs
- There will be many commands that, when run for the first time with multiple active selections, will prompt you "Run <command> for all cursors? (y/n)." Hitting 'y' will add the command to an .mc-lists file in your .emacs.d and save your preference. There should be a way to add all the kakoune-* functions to this list by default, but because ryo adds hashes to the beginnings of commands, this isn't as easy as it could be. This problem is usually just a one-time annoyance, but it can be irritating. I would gladly accept a PR to fix it.
- Certain commands, like =kakoune-replace-char= and =kakoune-select-to-char= prompt for input once per cursor, instead of using the first input for each one. This behavior is unlike Kakoune's but shouldn't be. Fixing it shouldn't be too difficult, but requires more knowledge of =multiple-cursors.el='s implementation than I have.
- When you first download this package, it is possible that non-primary cursors will have a weird color in insert mode. I have no idea why that is, but restarting emacs should take care of it.  

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

jmorag

44 commits

Languages

Emacs Lisp

100.0%