shackle
gives you the means to put an end to popped up buffers not
behaving they way you'd like them to. By setting up simple rules you
can for instance make Emacs always select help buffers for you or make
everything reuse your currently selected window.
Install from Marmalade or MELPA
(stable) via M-x package-install RET shackle
or download shackle.el
, place it into a suitable location such as
~/.emacs.d/vendor/
and add the following to your init file:
(add-to-list 'load-path (expand-file-name "~/.emacs.d/vendor/"))
First you need to customize shackle-rules
, this can be done via
M-x customize-group RET shackle
or in your init file.
As the name of the variable suggests, it's a list of rules. Each rule consists of a condition and a set of key-value combinations that tell what to do with the buffer in question.
The condition can be either a symbol, a string or a list of either. A
symbol is interpreted as the major mode of the buffer to match, a
string as the name of the buffer (which can be turned into regexp
matching by using the :regexp
key with a value of t
in the
key-value part) and a list groups either symbols or strings (as
described earlier) while requiring at least one element to match.
It's possible to supply a custom predicate by using (:custom
function)
as condition. The predicate is called with the buffer to
be displayed and is interpreted as a match given a non-nil return
value.
The following key-value pairs are available:
:select
andt
:Select the popped up window. The
shackle-select-reused-windows
option makes this the default for windows already displaying the buffer.:inhibit-window-quit
andt
:Special buffers usually have
q
bound toquit-window
which commonly buries the buffer and deletes the window. This option inhibits the latter which is especially useful in combination with:same
(asq
deleting the reused window is weird behaviour for more than one visible window), but can also be used with other keys like:other
as well.:custom
and a function name or lambda expression:Override with a custom action. The specified function is called with
BUFFER-OR-NAME
,ALIST
andPLIST
as argument. It's possible to reuse existing actions as defined in the sources, but dispatch based on more specific conditions such as the currently opened windows or selected buffer.:ignore
andt
:Skip handling the display of the buffer in question. Keep in mind that while this avoids switching buffers, popping up windows and displaying frames, it does not inhibit what may have preceded this command, such as the creation or update of the buffer to be displayed.
:other
andt
:Reuse the window
other-window
would select if there's more than one window open, otherwise pop up a new window. When used in combination with the:frame
key, do the equivalent withother-frame
or a new frame.:same
andt
:Display buffer in the current window.
:popup
andt
:Pop up a new window instead of displaying the buffer in the current one.
:align
and'above
,'below
,'left
,'right
, ort
or a function:Align a new window at the respective side of the current frame or with the default alignment (customizable with
shackle-default-alignment
) by splitting the root window. If a function is specified, it is called with zero arguments and must return any of the above alignments.:size
and number greater than zero:Aligned window use a default ratio of 0.5 to split up the original window in half (customizable with
shackle-default-size
), the ratio can be changed on a per-case basis by providing a different floating point value between 0 and 1. A value of 0.33 for example would make it occupy a third of the original window's size. Alternatively you can use an integer value of 1 or greater to display a window of the specified width or height instead.:frame
andt
:Pop buffer to a frame instead of a window.
A default rule can be set up by customizing shackle-default-rule
.
Its format follows the plist as used by shackle-rules
and the
default rule is used in case none of the rules in shackle-rules
yield a match. To have an exception to the default rule, you can use
the condition of your choice and either don't list the key-value pair
at all, use a different value (like nil
for the keys taking
boolean values) or use a placeholder key with any value (like
:noselect
instead of :select
). This is merely done to clearly
indicate the purpose of the respective rule, not following this
recommendation is another fine option.
Once you're done customizing shackle-rules
, use M-x
shackle-mode
to enable shackle
interactively. To enable it
automatically on startup, add (shackle-mode)
to your init file.
The above section expressed as EBNF:
RULES = "(" , { RULE } , ")" . RULE = "(" , CONDITION , PLIST , ")" . DEFAULT_RULE = "(" , PLIST , ")" . CONDITION = SIMPLE_CONDITION | LIST_CONDITION | FUNCTION_CONDITION . SIMPLE_CONDITION = SYMBOL | STRING . LIST_CONDITION = "(" , { SIMPLE_CONDITION } , ")" . FUNCTION_CONDITION = "(:custom" , FUNCTION , ")" . T_OR_NIL = "t" | "nil" . PLIST = "(" , [ ":regexp" , T_OR_NIL ] , ACTIONS , ")" . ACTIONS = EXCLUSIVE_ACTION , [ OPTIONAL_ACTIONS ] . EXCLUSIVE_ACTION = CUSTOM_ACTION | IGNORE_ACTION | OTHER_ACTION | POPUP_ACTION | SAME_ACTION | ALIGN_ACTION | FRAME_ACTION . CUSTOM_ACTION = ":custom" , FUNCTION . IGNORE_ACTION = ":ignore" , T_OR_NIL . OTHER_ACTION = ":other" , T_OR_NIL , [":frame" , T_OR_NIL] . POPUP_ACTION = ":popup" , T_OR_NIL . SAME_ACTION = ":same" , T_OR_NIL . ALIGN_ACTION = ":align" , ALIGN_VALUE , [":size" , SIZE_VALUE] . ALIGN_VALUE = T_OR_NIL | "above" | "below" | "left" | "right" | FUNCTION . SIZE_VALUE = FLOAT | INT . FRAME_ACTION = ":frame" , T_OR_NIL . OPTIONAL_ACTIONS = { OPTIONAL_ACTION } . OPTIONAL_ACTION = SELECT_ACTION | INHIBIT_WINDOW_QUIT_ACTION . SELECT_ACTION = ":select" , T_OR_NIL . INHIBIT_WINDOW_QUIT_ACTION = ":inhibit-window-quit" , T_OR_NIL .
In case your rules don't have any effect on a package, you can enable
tracing of calls to display-buffer
and other functions using it
with M-x shackle-trace-functions
, perform the action displaying
the buffer and check the *shackle trace*
buffer for the displayed
buffer. If nothing shows up, the package isn't using
display-buffer
at all, there isn't much you can do in that case
other than asking its author to reconsider using it. If it does,
one of the following might be the case:
- Your rule fails matching the buffer. This might be due to a typo in
the buffer name, an erroneous regular expression when used with
:regex t
or in the case of a major mode, the major mode not being enabled at the time of the matching. The latter must be fixed for the package. - The package overrides
display-buffer-alist
. I believe this to be a fundamental misunderstanding on the behalf of package authors as this variable is a user customizable option, tampering with it is questionable and should ideally be resolved by themselves. - The package relies on the displayed buffer not being selected, therefore breaking once someone customizes their Emacs to select displayed buffers by default. I also believe this to be an error on behalf of package authors, albeit not as grave as the previous one. Please contact them so that the package gets fixed.
The following example configuration enables the rather radical behaviour of always reusing the current window in order to avoid unwanted window splitting:
(setq shackle-default-rule '(:same t))
This one on the other hand provides a less intrusive user experience
to select all windows by default unless they are spawned by
compilation-mode
and demonstrates how to use exceptions:
(setq shackle-rules '((compilation-mode :noselect t))
shackle-default-rule '(:select t))
My final example tames Helm windows by aligning them at the bottom with a ratio of 40%:
(setq helm-display-function 'pop-to-buffer) ; make helm play nice
(setq shackle-rules '(("\\`\\*helm.*?\\*\\'" :regexp t :align t :size 0.4)))
0.5.0:
:same
does no longer use:inhibit-window-quit
implicitly, you'll need to make explicitly use of it. So, to get the old behaviour for(condition :same t)
use(condition :same t :inhibit-window-quit t)
instead. Alternatively you can customize the 0.7.0shackle-inhibit-window-quit-on-same-windows
option to have it for all buffers.0.6.0:
As suggested by @Benaiah, explicitly customizing a default rule would be much less confusing for users than knowing about
t
being special-cased inshackle-rules
. Therefore, a rule witht
as condition should be removed fromshackle-rules
andshackle-default-rule
customized to hold its action instead. Here's a demonstration of what would change for the second example:(setq shackle-rules '((compilation-mode :noselect t)) shackle-default-rule '(:select t))
shackle
adds an extra entry to display-buffer-alist
, a
customizable variable in Emacs that specifies what to do with buffers
displayed with the display-buffer
function. It's used by quite a
lot of Emacs packages, including very essential ones like the built-in
help and compilation package.
This means other Emacs packages that neither use the
display-buffer
function directly nor indirectly won't be
influenced by shackle
. If you should ever come across a package
that ought to use it, but doesn't conform, chances are you'll have to
speak with upstream instead of me to have it fixed. Another thing to
be aware of is that if you've set up a fallback rule, it may take over
the Emacs defaults which can play less well with packages (such as
Magit or Helm). Once you find out what's
causing the problem, you can add an exception rule to fix it.
This package assumes that every case of altering the buffer display
rules can be caught by checking for the buffer name or major mode of
the respective buffer. While this is true in most cases, there are
obviously exceptions to this rule. For example
find-function-at-point
ends up displaying a file buffer containing
the function definition in another window, but you can't infer this
from that buffer alone. The simple workaround is just replacing
find-function-at-point
with something directly using your prefered
flavour of display-buffer
. If you're hell-bent on making it work
with shackle
though, you could check whether using custom
conditions/actions works for you. In case they aren't enough,
advise the function displaying the buffer to alter it so that it can
be detected by them.
If you find bugs, have suggestions or any other problems, feel free to
report an issue on the issue tracker or hit me up on IRC, I'm always on
#emacs
. Patches are welcome, too, just fork, work on a separate
branch and open a pull request with it.
This package is heavily inspired by popwin and was hacked together after
discovering it being hard to debug, creating overly many timers and
exposing rather baffling bugs. shackle
being intentionally
simpler and easier to understand is considered a debugging-friendly
feature, not a bug. However if you prefer less rough edges, a
sensible default configuration and having more options for
customizing, give popwin
a try.