Giter Club home page Giter Club logo

rigpa's Introduction

rigpa

A modal UI framework for Emacs.

Rigpa allows you to construct structures relating editing modes (these could be evil modes or hydras, or anything implementing a common modal interface ("chimera")) and manipulate which structures are active at any point in time. It generalizes both vim's notion of editing mode, as well as Emacs's notion of a major mode into a unified way of looking at things, that is, as towers of modes which can be swapped and themselves edited using the very modes they contain.

In addition, rigpa also defines conventions that modes should follow in order to be seamlessly integrated into editing structures. This includes conventions around keybindings for moving up and down the hierarchy of editing levels, standard semantics of modifier keys, defining a canonical action for each mode, and other such conventions to ensure semantic uniformity across editing levels.

Watch the demo from EmacsConf 2020:

Watch video

Installation

This package isn't on MELPA yet, but you can install a pre-release version using straight.el by putting this somewhere in your .emacs.d:

(use-package rigpa

  :after (evil symex)

  :straight
  (rigpa
    :type git
    :host github
    :repo "countvajhula/rigpa")

  :config
  (setq rigpa-mode t)

  ;; temporary workaround for https://github.com/countvajhula/rigpa/issues/9
  (remove-hook 'evil-symex-state-exit-hook #'symex-disable-editing-minor-mode)

  ;; custom config
  (setq rigpa-show-menus nil)

  ;; navigating meta modes
  (global-unset-key (kbd "s-m"))
  (global-set-key (kbd "s-m s-m") 'rigpa-flashback-to-last-tower)
  (global-set-key (kbd "C-<escape>")
                  (lambda ()
                    (interactive)
                    (when (eq rigpa--complex rigpa-meta-complex)
                      (rigpa-exit-mode-mode))
                    (rigpa-enter-tower-mode)))
  (global-set-key (kbd "M-<escape>") 'rigpa-enter-mode-mode)
  (global-set-key (kbd "s-<escape>") 'rigpa-enter-mode-mode)
  (global-set-key (kbd "M-<return>")
                  (lambda ()
                    (interactive)
                    (when (eq rigpa--complex rigpa-meta-complex)
                      (rigpa-enter-selected-level)
                      (let ((ground (rigpa--get-ground-buffer)))
                        (rigpa-exit-mode-mode)
                        (switch-to-buffer ground)))))
  (global-set-key (kbd "s-<return>")
                  (lambda ()
                    (interactive)
                    (when (eq rigpa--complex rigpa-meta-complex)
                      (rigpa-enter-selected-level)
                      (let ((ground (rigpa--get-ground-buffer)))
                        (rigpa-exit-mode-mode)
                        (switch-to-buffer ground)))))
  (global-set-key (kbd "C-<return>")
                  (lambda ()
                    (interactive)
                    (when (eq rigpa--complex rigpa-meta-tower-complex)
                      (rigpa-exit-tower-mode)
                      (rigpa-enter-mode-mode))))

  ;; indexed entry to various modes
  (global-set-key (kbd "s-n") 'evil-normal-state)
  (global-set-key (kbd "s-y")        ; symex mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "symex")))
  (global-set-key (kbd "s-;") (kbd "s-y"))
  (global-set-key (kbd "s-w")        ; window mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "window")))
  (global-set-key (kbd "s-v")        ; view mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "view")))
  (global-set-key (kbd "s-x")        ; char mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "char")))
  (global-set-key (kbd "s-a")        ; activity mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "activity")))
  (global-set-key (kbd "s-z")        ; text mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "text")))
  (global-set-key (kbd "s-g")        ; history mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "history")))
  (global-set-key (kbd "s-i")        ; system mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "system")))
  (global-set-key (kbd "s-b")        ; buffer mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "buffer")))
  (global-set-key (kbd "s-f")        ; file mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "file")))
  (global-set-key (kbd "s-t")        ; tab mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "tab")))
  (global-set-key (kbd "s-l")        ; line mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "line")))
  (global-set-key (kbd "s-e")        ; application mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "application")))
  (global-set-key (kbd "s-r")        ; word mode
                  (lambda ()
                    (interactive)
                    (rigpa-enter-mode "word"))))

Usage

"Direct entry" into modes is (by default) done via the "super" key prefix, e.g. s-v goes into View Mode. Esc and Enter will always return you to a tower-native state and also navigate that tower. If you leave a buffer or window while in some state, by momentarily entering buffer or window mode, you will be placed back in your original state when you return. In-buffer states like Symex, Word, Character mode are backed by Evil, and do not show menus. Global states like View, Window, Buffer, are hydra-backed and can show/dismiss menus on demand (default binding H-m).

The most useful towers at the moment are Vim tower, Lisp tower (containing Symex mode), and Emacs tower. If you are working with Lisp code, then alternating (e.g. via s-m s-m) between Vim and Lisp towers, or between Emacs and Lisp towers, is a common usage pattern. Whatever towers you define, you will probably want to leverage direct entry into View, Window, Buffer modes as part of normal usage, especially for their canonical actions. E.g. s-b s-b to alternate to most recent buffer, s-w s-w to alternate to most recent window, s-v <tab> to set to preferred zoom, s-w w to maximize window, besides using the usual navigation commands in these modes to get around.

See the Keybinding Model for keys that work in every mode, including navigations for getting around, and transformations for moving things around, or deleting, transforming them in some way.

"License"

This work is "part of the world." You are free to do whatever you like with it and it isn't owned by anybody, not even the creators. Attribution would be appreciated and would help, but it is not strictly necessary nor required. If you'd like to learn more about this way of doing things and how it could lead to a peaceful, efficient, and creative world, and how you can help, visit drym.org.

rigpa's People

Contributors

countvajhula avatar egregius313 avatar j-shilling avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rigpa's Issues

Missing parsec package in Package-Requires

Hello,

I'd like to report that the parsec package seems to be missing from the Packages-Requires dependencies in: https://github.com/countvajhula/rigpa/blob/master/rigpa.el.
I've tried to install the package with straight.el and ended up with the following error:

Debugger entered--Lisp error: (file-missing "Cannot open load file" "Aucun fichier ou dossier de ce type" "parsec")
  require(parsec)
  ...

P.S. Thanks for your presentation at EmacsConf 2020, I really enjoyed it !

Brilliant!

This is such a fantastic idea! I loved your EmacsConf 2020 talk. This makes me want to modify my entire Emacs setup that I have been using for many years to use this instead.

I am curious about your future plans for this package.

This is probably an annoying question, but do you think your code could be made independent from evil mode? It would be great if a user could select whether or not they would like to depend on evil mode and use its key bindings or use more traditional Emacs keybindings or something totally custom.

Right now it seems like the key bindings are pretty deeply embedded into each mode. It seems like all bindings would have to be changed in each mode in order to switch from evil bindings to Emacs bindings. That could require a huge amount of configuration if the user does not want to use your default settings.

It might be nice to have some more generalized motion primitives with key bindings for those set in one place, something like "forward", "backward", "up", "down", etc. Then the same key could be used for "forward" in every mode and every mode would just change what "forward" means, saving you from having to put a specific key binding into each mode. There would still need to be unique key bindings for some modes, but it would be great to reuse as many as possible for every mode. I am not sure what all of the other generalized primitives would be, what else tends to be common in all of your modes?

Another awesome feature would be to have the option of seeing where all of the motion commands will move for each mode, like how you can see where you are about to jump in avy mode. Like if you had the "h" key assigned to "backward" and "l" assigned to "forward", in word mode the letter "h" would be on top of the previous word and "l" would be on top of the next word, showing you where you would jump if you pressed those keys. In line mode the "h" and "l" would be on the previous line and next line, in window mode they would be on each of the windows where you would jump, etc. Every time you switched modes, those indicators would jump to their new locations. That would give really useful super fast feedback on where you can move without having to look down at the mode line to see what mode you are in or have to remember exactly what each mode means.

Thank you so much for your great idea and amazing work!

Cursor disappears after exiting View mode through a "foreign key"

Workaround: Enter view mode again and exit it by hitting Enter or Escape.

Description: View mode is a hydra-backed mode. Hydra supports many different ways of exiting, one of which is exiting via a "foreign key," that is, the hydra can be configured to exit when a key not explicitly handled by the hydra/mode is pressed. This behavior is desirable for modes like View mode, where we typically use it in a transitory way, and would like it to disappear when we start doing the thing we really want to do.

Rigpa, as a modal interface framework, is interested whenever a mode is entered and exited, no matter which modal interface provider is being used. We would like to take certain actions upon mode entry and exit, for which we need to formally tie into mode lifecycle hooks. Rigpa relies on the chimera abstraction layer to make the determination that a mode has exited. As there does not seem to be a post-exit hook in hydra, Chimera relies on the after-exit hook to detect this event, which treats a semaphore in the post hook being set as indicating that the mode has truly exited. This is a roundabout way to do it, but was necessary without a true post-exit hook being present. Unfortunately, exiting a hydra through a "foreign key" does not call the after-exit hook. So if View mode is exited without explicitly hitting Enter or Escape, the cursor does not reappear (it is hidden upon View mode entry).

That said, this bug is recent and this did not happen before. I think it was probably introduced in e51b14b.

Fix: Either (a) request a true post-exit hook from hydra itself, (b) or write a minimal modal interface provider that exposes lifecycle hooks, or (c) revert to the old way of handling hooks in Rigpa.

enhancement: Use keybinding signals?

@countvajhula Aha, I've found your "mode-mode" at last! Here I think is huge potential for this idea

I really like corkey's idea of signals, which decouple conceptual operations (e.g. "eval current form") from concrete and mode-specific implementations.

There is a lightweight (57 sloc) package around this idea here:
https://github.com/lilactown/kitten/blob/main/lisp/reflex.el

that let's you write code like this:

binding keys to signals:
https://github.com/lilactown/kitten/blob/main/user/keybinds.el#L91-L112

providing functionality to the signals for a given mode:
https://github.com/lilactown/kitten/blob/main/modules/kitten-clojure.el#L40-L57

See also drym-org/symex.el#65

Symex state bindings become inactive upon entering other states

(Note: the workaround below has already been added to the Rigpa Emacs config in the README. It will be removed once we have a fix.)

As a result of a recent change in Symex, entering View mode or Window mode momentarily (to e.g. adjust the zoom level) and then returning to Symex mode loses the symex keybindings. The bindings are restored upon re-entering Symex state once again.

(Scroll to the bottom for a workaround)

Explanation
This happens because the fix in the symex package involved adding a hook to disable the editing minor mode in case rigpa isn't present, to ensure that the bindings are disabled upon exiting symex state. This behavior conflicts with Rigpa's handling of the same functionality. They ordinarily should not interfere since symex defers to Rigpa's handling if it knows rigpa is installed.

But at the moment, Rigpa (via init.el configuration) loads after symex (I don't recall exactly why at the moment), so at the time Symex loads on emacs init, rigpa isn't available and the hook to disable the minor mode within Symex gets initialized, causing it to interfere with Rigpa later on.

Workaround
The workaround for now is to evaluate this expression sometime after Emacs loads:

(remove-hook 'evil-symex-state-exit-hook #'symex-disable-editing-minor-mode)

Possible Fixes
A proper fix would be to:

  1. understand why Rigpa disables minor modes on the entry hooks to other states rather than on the exit hook of the concerned state (I think there was a reason, but it would be nice if we could do it on the latter, since that's cleaner and it's what Symex does now)
  2. avoid dependence of these packages on one another at load time. Or at least, have Symex load after Rigpa
  3. Ideally make the behavior here compatible across the two packages whether the other package is installed or not
  4. Worst case, explicitly disable third-party (i.e. Symex) hooks in Rigpa that may interfere with its functionality

"Enter" should select in certain modes

Rigpa maps the Enter key to entering a lower level in the current editing tower, overriding the handling in the underlying major mode. In many major modes, there is a more natural interpretation of the Enter key provided by evil-mode or evil-collection for that mode -- e.g. typically non-editing buffers like popups, dired buffers, Magit buffers, etc, where we would like Enter to "visit" or "choose" something. To restore this preferred handling of the Enter key, Rigpa currently manually remaps the Enter key to the preferred command in known cases - in other words, overriding its own override of the keybinding for that major mode - so that the Enter key behaves the way we expect in such buffers.

This is not a real solution. Besides requiring manual work to add these double-overrides (and needing to know about such cases), it also doesn't always work. In Magit, for instance, the same keybindings in different parts of the buffer seem to be bound to different commands (e.g. in the stashes section vs the commits section). I'm not sure how that is accomplished, but it means that a simple manual double-override is not possible here, and Enter does not do what we expect in Magit (workaround: temporarily go to Emacs state (e.g. via C-z) and then hit Enter).

In principle, it would be great if we could simply defer to a lower-priority keymap. That is, in resolving a keybinding, Emacs has a prioritized list of keymaps, and it consults them in order until a binding for the input keys is found. It would be great if we could say something like "if major mode = Magit, then I abstain from specifying the binding - please continue consulting other keymaps". Otherwise, is there another standard way in Emacs to do something like this?

Cannot load rigpa.el when writing in text mode, without a graphic display

When running emacs in a terminal with the -nw flag, rigpa.el cannot load because it tries to pass the 'unspecified font from the 'default face to #'font-get. Below is the stack trace of the error:

Debugger entered--Lisp error: (wrong-type-argument font unspecified)
   font-get(unspecified :name)
   (cond ((find-font (font-spec :name "Consolas")) "Consolas") ((find-font (font-sp$
   (set-face-font 'rigpa-face (cond ((find-font (font-spec :name "Consolas")) "Cons$
   #<subr eval-buffer>()
   apply(#<subr eval-buffer> nil)
   eval-buffer()  ; Reading at buffer position 3119
   funcall-interactively(eval-buffer)
   command-execute(eval-buffer)

I submitted a pull request with a quick fix; I hope that's helpful: #7

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.