kickingvegas / casual

A collection of opinionated keyboard-driven user interfaces for various built-in Emacs modes.
GNU General Public License v3.0
11 stars 2 forks source link

Build general dispatch function for Casual porcelains #26

Open minad opened 3 months ago

minad commented 3 months ago

For the suite I'd like to propose adding a general casual-tmenu command which could be bound to a key like C-c c (C-c a, C-c t, C-c m, ...) to open the Casual transient menu corresponding to the currently active mode. Do you think this would make sense? I've seen you currently use C-o everywhere, binding it in the mode maps separately.

kickingvegas commented 3 months ago

@minad Thanks for the suggestion! To help clarify thinking here, I think there are two issues you raise.

  1. Should there be a global binding to Casual?
  2. At current, Casual Suite requires manual installation and configuration of each porcelain. Should this be streamlined?

With your proposal for casual-tmenu, it seems like I would have to answer yes to both of the above. With binding decisions often being contentious in Emacs (especially global ones), I’m reluctant to press for this.

I’m not sure it is desirable for a globally bound function (in this case the proposed casual-tmenu) to interrogate the current mode and select the appropriate Transient as it would have to cover modes that Casual does not support.

That said, I think streamlining installation and configuration would be a good thing. Here’s a quick and dirty example I’ve pulled together.

;; Example installer for Casual Suite.
(defun casual-suite-install (main)
  "Installer for Casual Suite with binding MAIN.
- MAIN binding specified for Casual main menu for specific mode."
  (keymap-set calc-mode-map main #'casual-calc-tmenu)
  (keymap-set dired-mode-map main #'casual-dired-tmenu)
  (keymap-set dired-mode-map "s" #'casual-dired-sort-by-tmenu)
  (keymap-set isearch-mode-map "<f2>" #'cc-isearch-menu-transient)
  (keymap-set ibuffer-mode-map main #'casual-ibuffer-tmenu)
  (keymap-set ibuffer-mode-map "F" #'casual-ibuffer-filter-tmenu)
  (keymap-set ibuffer-mode-map "s" #'casual-ibuffer-sortby-tmenu)
  (keymap-set Info-mode-map main #'casual-info-tmenu)
  (keymap-global-set "M-g" #'casual-avy-tmenu))

With such a function (casual-suite-install) in place, then the install procedure becomes trivial.

(require 'casual-suite)
(casual-suite-install "C-o")

The benefit of the above approach is that it requires no global-binding yet offers the same user experience. It is also optional, so that users who want finer grained control can continue to install each package individually.

I’d be interested in your thoughts on this and come up with a clear set of requirements before moving forward.

Thanks!

minad commented 3 months ago

Should there be a global binding to Casual?

Not necessarily. My proposal is to only add some kind of dispatching command which makes the whole suite a little easier to use. I only call casual-tmenu and get the appropriate menu. In any case, no binding should be installed automatically. It is up to the user where and how to install the binding.

At current, Casual Suite requires manual installation and configuration of each porcelain. Should this be streamlined?

No, I don't think the setup should be streamlined. I just thought it would be neat to have an abstract single entry point casual-tmenu. However in the end the overall setup would get easier as an effect, so maybe this is a partial yes to your question.

The benefit of the above approach is that it requires no global-binding yet offers the same user experience. It is also optional, so that users who want finer grained control can continue to install each package individually.

I don't really like the setup function approach for various reasons. Such setup functions don't interact nicely with lazy loading. Setup functions are less transparent and creates clutter across various maps. Also a user may not want to install all the bindings. In this sense a setup function takes control away from the user. My preference would be to have a single casual entry point which one could invoke everywhere. So my proposal is really that specific.

I am just mentioning this (but I also think it wouldn't be great) - a better, cleaner alternative than a setup function would be a global minor mode which can be used to globally activate/deactivate the entry points. However I also dislike global minor modes which do nothing else than adding bindings. In summary, I consider both setup functions and keybinding-only minor modes malpractices.

So I hope you are not going to add a setup function or a minor mode as the result of my proposal... ;)

I’m not sure it is desirable for a globally bound function (in this case the proposed casual-tmenu) to interrogate the current mode and select the appropriate Transient as it would have to cover modes that Casual does not support.

Why do you feel like that? The command would be quite simple. It could look up the appropriate command in a mode/command alist. However if you don't like this idea, let's just close this and do nothing. :)

kickingvegas commented 3 months ago

@minad Thanks for your clarifying thoughts, particularly around setup functions and minor modes.

So I suppose you’re looking for something like this behavior?

(defun casual-tmenu ()
  "Dispatcher for the Casual main menu of a supported mode."
  (let ((current-mode major-mode))
    (cond
     ((equal current-mode 'calc-mode) (call-interactively #'casual-calc-tmenu))
     ((equal current-mode 'dired-mode) (call-interactively #'casual-dired-tmenu))
     ((equal current-mode 'Info-mode) (call-interactively #'casual-info-tmenu))
     ((equal current-mode 'ibuffer-mode) (call-interactively #'casual-ibuffer-tmenu))
     (t (message "Casual does not support this mode.")))))

Sidestepping an implementation using an alist and other implementation details like how the Avy and I-Search menus really aren’t mode specific, if this is what you’re looking for then I’d be open to putting this in.

minad commented 3 months ago

Yes, like this - a simple Casual DWIM comand. I'd prefer an alist for config, but this is really just an implementation detail. As a special case, Isearch could also be handled by checking if isearch-mode is active. In this case the Isearch Casual should be preferred over the other modes I guess?

I am unsure about casual-avy. While I have Avy installed, I barely use it. Also I haven't tried casual-avy yet. My problem with Avy is mainly that I have to think too much when using it. At the same time, Avy is supposed to speed up things, so I doubt that a Transient will help with that. But maybe it could help with learning.

kickingvegas commented 3 months ago

@minad Give casual-avy a try! In this case I’ve globally bound it to M-g and have made it mirror a number of the default global map bindings for M-g. Before building this menu, I too rarely used Avy. Now it's embedded in my usage of Emacs.

kickingvegas commented 2 months ago

@minad per discussion #28, concerned that any implementation here would violate lazy-loading which would break use-package. Would appreciate your thoughts here.

minad commented 2 months ago

No, the implementation you've given in https://github.com/kickingvegas/casual/issues/26 wouldn't violate lazy loading. I only proposed this, given the possibility of lazy loading. I am generally averse to patterns which do not work lazily and I also avoid such patterns in my packages.

But anyway, it is okay to not implement this, if it doesn't fit into your overall design. Thanks for consideration.