Closed nickserv closed 6 years ago
I was going to file a request for a related feature, which is that I can't find a way to combine autoloads and hooks safely, and I think this could potentially solve that too. (Unless it's already solvable - I only started using use-package
yesterday.)
If I want an autoload of a package that only activates via a hook (for me, this is flycheck-mode
and rainbow-mode
), and add-hook
within :init
, then I have message spam every time the mode is activated if the package is not installed. But if I add-hook
in :config
, then it won't hook until I run the autoloading command explicitly.
With a special :hooks
, use-package
would have the information it needs to make the unloaded stub command quieter, or unhook it completely after the package isn't found.
I set all my hooks under :config
, and I don't seem to have the same hook activation issue. However, I do use :ensure
to install all third party packages, I wonder if that's causing my hooks to work properly.
For example, here's my use-package
declaration for rainbow-mode
, which can successfully set up a hook even if the package wasn't installed previously:
(use-package rainbow-mode
:ensure
:config
(add-hook 'prog-mode-hook 'rainbow-mode))
Are you basically suggesting that the :hooks
keyword could set up hooks somewhere in the lifecycle similar to :config
, but only if the package has been installed at that point? Sorry, I'm a bit confused about here since I'm relatively new to Emacs Lisp and I'm still grasping how use-package
handles autoloads.
In other news, my add-hooks
package is now on MELPA and MELPA Stable.
I'm using it under :config
to set up some of my more complex hooks, which is especially useful when a package introduces a minor mode which I want in multiple major modes. That being said, it would still be useful to have additional syntax sugar and assumptions for hook setup built into use-package
(for example, using the package's name as the default FUNCTION
for add-hook
, similarly to :mode
's assumptions).
If I understand use-package
's model, that works for you because nothing in that state makes it deferred. So it works, but you are also loading rainbow mode even if you never use it. If instead it's using one of the deferring features, e.g. :commands
:
(use-package rainbow-mode
:commands rainbow-mode
:config
(add-hook 'prog-mode-hook 'rainbow-mode))
It won't ever load, because :config
won't run until one of the loading triggers - in this case, the command rainbow-mode
- runs. Which it doesn't, because the hook also isn't added until it runs. But if I hook during :init
, that's also no good because I don't know that the package will exist.
Giving higher-level control of hooking over to use-package
would let it hook something during the same time as :init
which reacts better if the package turns out not to exist once it first tries to trigger the load.
Thanks for the explanation, that clears up some of my confusion about lazy loading.
I think the :hooks
keyword should defer and autoload the package (similarly to :bind
), adding hooks immediately if the package exists or is demanded. Adding hooks ahead of time shouldn't be a problem because add-hook
can automatically initialize HOOK
to nil
when it doesn't exist yet. Hooks wouldn't be added when packages are missing, preventing missing package errors.
For syntax sugar, the FUNCTION
could be optional, defaulting to the package's name (with -mode
appended if it's not part of the package name and the variable exists), so in most cases you'd only have to define HOOK
names. HOOK
names could imply -mode
and FUNCTION
names could imply -hook
or -mode-hook
. HOOK
s and FUNCTION
s could be symbol lists to imply multiplication and multiple pairs of them could be given in a list as well (just like add-hooks
, if that's confusing please check its comments and docstrings). Depending on how fancy the syntax would be, it could depend on add-hooks
and I could upstream some syntax that isn't specific to use-package
(like the -hook
and -mode
stuff).
(use-package flyspell
:ensure
:hooks (prog . flyspell-prog))
(use-package linum
:hooks (prog))
(use-package rainbow-mode
:ensure
:hooks (prog))
Appending -hook
is fine; every normal hook should end in -hook
and virtually every one does. But there are a lot of useful hooks that aren't mode-specific, and also a few common multi-modal ones like c-mode-common-hook
, which don't end in -mode-hook
.
Other deferring keywords like :mode
, :interpreter
, and :commands
also require an explicit -mode
when it doesn't match the package name. I think fighting against the repetition of -mode
in Emacs code is probably a Sisyphean task, and would prefer it to stay explicit. It's always harder to disable implicit behavior when you don't want it, and harder to keep compatibility if you need to back it out because of other concerns.
In other words, I'd prefer
(use-package flyspell
:hooks (prog-mode . flyspell-prog-mode))
(use-package linum
:hooks (prog-mode . linum-mode))
(use-package rainbow-mode
:hooks prog-mode)
I agree, let's make only -hook
implied in terms of naming hooks and functions. It's also easier to read since hook naming has a stronger standard and the hooks are always first, while the other assumptions were more ambiguous and would have some complicated edge cases.
I would still like the syntax to allow for multiple hooks in a pair, multiple functions in a pair, and multiple pairs. I think multiple pairs should have another pair of parens around them to mirror :bind
's syntax for one or more bindings.
I plan on putting the implied -hook
syntax in add-hooks
, then I may tinker with with implementing a :hooks
keyword that uses it. I'd like it to be similar to the bind-keys
API which lets you use it in one large form that doesn't depend on use-package
or in use-package
's :bind
keywords.
This is really nice, thanks for the idea (and reference implementation)! Now I can say:
(use-package abbrev
:diminish
:hook
((text-mode prog-mode erc-mode LaTeX-mode) . abbrev-mode)
(expand-load
. (lambda ()
(add-hook 'expand-expand-hook 'indent-according-to-mode)
(add-hook 'expand-jump-hook 'indent-according-to-mode)))
:config
(if (file-exists-p abbrev-file-name)
(quietly-read-abbrev-file)))
Guess it's too late to contribute to the discussion, but maybe my case could be considered as a form of a future improvement. My config uses a very similar approach described here, but in an inverted manner. Having a modularized approach to emacs' configuration, I've added a keyword :hooks
that receives a list of functions to call when a given mode is loaded, i.e say there's a configuration to work with the language of your choice, which requires a list of packages to be loaded, hence i have:
(use-package scala-mode
:hooks (4lex1v/fix-scala-fonts
hs-minor-mode
hideshowvis-enable
yas-minor-mode
company-mode))
This approach looks cleaner to me, cause if there's a need to drop this config, there's only one place i need to change, rather then traverse all other configs removing the hook.
The exact syntax you quoted above now works, it's just called :hook
instead of :hooks
, for the same reason that it's :bind
instead of :binds
.
@jwiegley thanks for a fast response! Correct me if i'm wrong, but I believe given implementation (the :hook
keyword) has a different semantics, having the definition from the example:
(use-package ace-jump-mode
:hook scala-mode)
This would load ace-jump-mode
when we launch scala-mode
(desugared into (add-hook 'scala-mode-hook #'ace-jump-mode)
, while i believe the other way has its own convenience, it would look the other way:
(use-package scala-mode
:hook ace-jump-mode)
Given the current implementation this would be desugared into (add-hook 'ace-jump-mode-hook #'scala-mode)
, which is not the intended way. Maybe i'm missing something and my understanding is incorrect. Does it make more sense to give a list of functions on the "hook" side, rather the other way around, or it's just a matter of taste?
If :hook
had the reverse semantics, as you describe, how would you add to text-mode-hook
or prog-mode-hook
, which are far more common (at least, for me) than hooking one externally installed package into another.
Also, I tend to prefer that "more derived" packages reference the more basic ones, than the other way around. That is, counsel should refer to ivy, not ivy to counsel.
@jwiegley answering your question:
how would you add to text-mode-hook or prog-mode-hook, which are far more common
The approach i use - wrapping with a use-package
, e.g for eshell. Haven't seen issues with this approach.
Also, I tend to prefer that "more derived" packages reference the more basic ones, than the other way around. That is, counsel should refer to ivy, not ivy to counsel.
I see the rational behind this, that's what i had initially. Current configuration strives towards a more modular approach, i.e major mode configuration with all minor mode dependencies, but it's a personal preference.
Anyway, thanks for taking your time considering my case 👍
Adding a private keyword, such as :call
, would be trivial. If enough other people prefer that ordering, we could add it to use-package
.
(use-package text-mode
:call (flyspell-mode ace-jump-mode auto-fill-mode))
This would have the behavior that when text-mode
is loaded (:defer
is implicit here), those functions will be called.
The approach i use - wrapping with a use-package, e.g for eshell. Haven't seen issues with this approach.
eshell
is a package, and a feature, and a file, so everything "just works". package-install
, load
/ require
, and featurep
all want the same identifier.
prog-mode
is a feature and a file but not a package, and text-mode
is merely a file. The former has some annoying edge cases when mixed with :ensure
since package-install
doesn't quite do the right thing. The latter is unaddressable. You'd need to hang it off simple
(a feature) or emacs
(a feature and a package), and then the shorthand doesn't work anymore anyway.
(use-package "text-mode" ...)
should work.
It "works" but it might not do what you expect.
If you have use-package-always-ensure
set to t
(or set it to t
explicitly), you'll try to package-install
it, eventually producing an error about not finding a package named "text-mode-".
Because no feature is provided, text-mode.el
's contents will be re-evaluated every time.
If you have use-package-always-ensure set to t (or set it to t explicitly), you'll try to package-install it, eventually producing an error about not finding a package named "text-mode-".
Use :ensure nil
.
Because no feature is provided, text-mode.el's contents will be re-evaluated every time.
Use :defer t
.
@joewreschnig I'm not sure I follow. Eshell and text-mode are the same in this regard for me: both are features defined in some file that provide
s them (also can double check with (featurep text-mode)
, which returns t
). I'd need to make sure :ensure
is set to nil
for both, because neither is available as a package on melpa or gnu elpa.
(also can double check with (featurep text-mode), which returns t
That's new in Emacs 26 or so.
@npostavs ah, thanks for the clarification.
@jwiegley Thanks for all this work, I finally got around to migrating from my add-hooks
package to :hook
and I love the syntax!
On a related note about add-hooks
: When I originally created the package I was inspired by bind-key
, and I wanted a similar library that could be used to define hooks the same way with or without using use-package
(just like what bind-key
does for global-set-key
). I start implementing my own :hooks
keyword with a similar syntax but using add-hooks
internally (although I couldn't get it to work . at the time). My goal was to keep the syntax implementation in add-hooks
so that it would be agnostic of the use-package
codebase. This way any bugs or feature changes in use-package
:hook
syntax and the add-hooks
function could be made once together for easier maintenance, and another alternative configuration management package could use the same hook resolution as use-package
and add-hooks
for modularity, just like bind-key
. Do you think it would be worth it if I refactored the :hook
keyword to use the add-hooks
package internally (just like bind-key
) as long as I can keep it back compatible with your current syntax?
I find use-package's
bind-key
macro (and the related:bind
keyword) very helpful for cleaning up duplicate configuration. Hooks also tend to get out of hand on me, especially since I often need to add multiple functions to one hook, or add one function to multiple hooks. I think it would be great to have anadd-hooks
macro with ause-package
keyword, which could work similarly to the binding feature.I created a custom
add-hooks
macro (inspired bybind-keys
) before I switched touse-package
, which I'm still using to declare my hooks. It takes several cons pairs, where either the hook or function can be a single symbol or a list of symbols, in which case the pair will automatically apply to multiple functions and/or hooks. Here's an example:The
use-package
keyword could look something like this, taking either a cons cell or a list of cells: