I use helm-buffers as a kind of window manager. Its killer-feature is the ability to select some windows the Helm-Wayâ„¢ and to pop them all at once with C-c o.
This can serve as a powerful Exposé-like feature as seen on macOS: M-a C-c o to display all windows, then pick up the window you want to use.
The only itch is that the window layout generated by M-o does not seem to be configurable, and the default is not very convenient with more than, say, 4 windows.
If I'm not mistaken, this is all defined in helm-switch-to-buffers.
Some suggestions:
Modify the current layout so that it alternates between horizontal and vertical split: instead of generating increasingly flatter rectangles, this would yield more "square" windows.
Make this a customizable function: the user could go rock'n'roll with this!
This would obviously allow for arbitrary layouts. But also much more:
Say, change the layout depending on the count of the windows, or even better, _depending on their content!_
Cool or what?
EDIT: Changed M-o to C-c o.
Pierre Neidhardt notifications@github.com writes:
I use helm-buffers as a kind of window manager. Its killer-feature is the ability to select some windows the Helm-Wayâ„¢ and to pop them all at one with M-o.
This can serve as a powerful Exposé-like feature as seen on macOS: M-a M-o, then pick up the window you want to use.
The only itch is that the window layout generated by M-o does not seem to be configurable, and the default is not very convenient with more than, say, 4 windows.
If I'm not mistaken, this is all defined in helm-switch-to-buffers.Some suggestions:
Modifify the current layout so that it alternates between horizontal and vertical split: instead of generating increasingly flatter rectangles, this would yield
more "square" windows.Make this a customizable function: the user could go rock'n'roll with this!
Say, change the layout depending on the count of the windows, or better, depending on the content!Cool or what?
Yes, this is a good idea, don't know how to do this yet though.
Not sure what's M-a and M-o but I guess M-o is C-c o => Switch other window?
--
Thierry
Damn! Another binding I forgot it was mine!
So yes, M-o is C-c o (helm-<module>-switch-other-window).
M-a is standard though: helm-mark-all.
Seems to be easy to do, no? Untested code:
(defun helm-switch-to-buffers (buffer-or-name &optional other-window)
"Switch to buffer BUFFER-OR-NAME.
If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is specified keep current-buffer and switch to others buffers
in separate windows."
(let* ((mkds (helm-marked-candidates))
(size (/ (window-height) (length mkds))))
(or (<= window-min-height size)
(error "Too many buffers to visit simultaneously."))
(funcall helm-switch-to-buffers-function mkds other-window)))
(defcustom helm-switch-to-buffers-function
(lambda (mkds & optional other-window)
(helm-aif (cdr mkds)
(progn
(if other-window
(switch-to-buffer-other-window (car mkds))
(switch-to-buffer (car mkds)))
(let (right-split)
(save-selected-window
(cl-loop for b in it
do (progn
(select-window (split-window (get-buffer-window) right-split))
(setq right-split (not right-split))
(switch-to-buffer b)))))
(if other-window
(switch-to-buffer-other-window buffer-or-name)
(switch-to-buffer buffer-or-name)))))
mkds needs to be renamed to something more explicit.
Pierre Neidhardt notifications@github.com writes:
Damn! Another binding I forgot it was mine!
So yes, M-o is C-c o (helm--switch-other-window).
M-a is standard though: helm-mark-all.Seems to be easy to do, no?
It seems maybe, but it is much more complex than this as each window
will split on itself until too small for splitting, IOW the layout you
asked for is really hard to make.
Untested code:
Not working.
--
Thierry
It is not what you want, but I made some improvements to allow displaying files/buffers vertically or not.
I'll work on it, but the main improvement I'd like is trivial: move the windowing part to a defcustom as I've shown in my not-working example.
Pierre Neidhardt notifications@github.com writes:
I'll work on it,
Thanks.
but the main improvement I'd like is trivial: move the windowing part
to a defcustom as I've shown in my not-working example.
+-----------------+
| |
| A |
| |
+--------+--------+
| | |
| B | C |
| | |
+--------+--------+
| |
| D |
| |
+--------+--------+
| | |
| E | F |
| | |
+--------+--------+
To achieve what you want to do (like above IIUC), you have to start at
window A (current), create E/F, back to A and split to create D, back to
A again to create B/C and so on, otherwise if you try like in your
example you will go to B, split, then to C and split C (D will be in C)
and so on.
The work I did recently will ease implementing this.
Think that one may not start from a whole window but an already splitted
layout.
The defcustom is not so trivial as you have to decide of the behavior
you will provide in different customizations, and this not with a single
lambda.
HTH.
--
Thierry
No, this is not the behaviour I intended. My suggestion is much simpler to implement:
|---+-----------|
| A | B |
| |---+-------|
| | C | D |
| | | |
| | |---+---|
| | | E | F |
| | | | |
|---+---+---+---|
The defcustom is not so trivial as you have to decide of the behavior you will provide in different customizations, and this not with a single lambda.
What behaviour? I don't see the problem here.
I'll implement it and send a PR.
See #1871.
Pierre Neidhardt notifications@github.com writes:
See #1871.
Thanks, there is some things wrong however:
1) fix error about unused argument
2) you defeat the change I did previously that avoid failing when there
is too much windows.
3) There is no way to configure the previous behavior, users should have
to choose between tags, not functions, and the alternate functions should be
provided.
4) The same behavior should be provided for files for consistency.
--
Thierry
1) OK
2) Sorry, I realized too late that I forgot to base my work on your updated function.
3) I don't understand.
4) You mean helm-find-files? Sure!
Tell me if this is the right way to go about function composition:
(defun helm-switch-to-buffers (buffer-or-name &optional other-window)
"Switch to buffer BUFFER-OR-NAME.
If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is specified keep current-buffer and switch to others buffers
in separate windows."
(let* ((mkds (helm-marked-candidates))
(size (/ (window-height) (length mkds))))
(or (<= window-min-height size)
(error "Too many buffers to visit simultaneously."))
(helm-aif (cdr mkds)
(funcall helm-switch-to-buffers-function mkds other-window)
(if other-window
(switch-to-buffer-other-window buffer-or-name)
(switch-to-buffer buffer-or-name)))))
(defun helm-window-alternate-split (candidates &optional other-window)
"Alternatively split last window left and right."
(if other-window
(switch-to-buffer-other-window (car candidates))
(switch-to-buffer (car candidates)))
...
Using the above composition, I also suggest the following windowing function:
(defun helm-window-mosaic (candidates &optional other-window)
"Make an as-square-as-possible window mosaic of the CANDIDATES buffers.
Get the square root R of the total window count, make a RxR
mosaic with the last N remaining windows dispatched evenly over the
last rows/columns.
If OTHER-WINDOW is non-nil, current windows are part of the mosaic too."
(when other-window
(setq candidates (append (mapcar 'window-buffer (window-list)) candidates)))
(delete-other-windows)
(let ((square-width (ceiling (sqrt (length candidates))))
;; TODO: Parameterize direction.
(right-split t)
next-window)
(while candidates
(when (> (length candidates) square-width)
(setq next-window (split-window (get-buffer-window) nil right-split)))
(switch-to-buffer (car candidates))
(setq candidates (cdr candidates))
(dotimes (_ (min (- square-width 1) (length candidates)))
(select-window (split-window (get-buffer-window) nil (not right-split)))
(switch-to-buffer (car candidates))
(setq candidates (cdr candidates)))
(when next-window
(select-window next-window))))
(balance-windows))
It dispatches the windows on an as-square-as-possible tiling. Pretty cool I think. Could you test it and tell me what you think?
Pierre Neidhardt notifications@github.com writes:
Tell me if this is the right way to go about function composition:
(defun helm-switch-to-buffers (buffer-or-name &optional other-window)
"Switch to buffer BUFFER-OR-NAME.
If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is specified keep current-buffer and switch to others buffers
in separate windows."
(let* ((mkds (helm-marked-candidates))
(size (/ (window-height) (length mkds))))
(or (<= window-min-height size)
(error "Too many buffers to visit simultaneously."))
No, this is wrong, it was the previous behavior (perhaps you didn't
rebase), with this if you select too much buffers (or files) nothing
will happen, i.e helm-switch-to-buffers-function will never be called,
you should handle this in your helm-switch-to-buffers-function with a
condition case so that the loop return when window splitting reaches the
limit the size of screen can accept.
It dispatches the windows on an as-square-as-possible tiling. Pretty
cool I think. Could you test it and tell me what you think?
Looks nice, I will have a look ASAP.
--
Thierry
Pierre Neidhardt notifications@github.com writes:
- OK
- Sorry, I realized too late that I forgot to base my work on your updated function.
- I don't understand.
Users have no idea what helm-window-mosaic or alternate-window will do,
tags in defcustom allow this, also in addition to your different
functions, the actual behavior should be wrapped in a function to be
reused as an helm-switch-to-buffers-function.
- You mean helm-find-files? Sure!
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.*
--
Thierry
Isn't the actual behaviour already wrapped in a function? I guess I'm misunderstanding you. Could you write an example?
Pierre Neidhardt notifications@github.com writes:
Isn't the actual behaviour already wrapped in a function?
No.
I guess I'm misunderstanding you. Could you write an example?
(defun helm-switch-to-buffers (buffer-or-name &optional other-window)
"Switch to buffer BUFFER-OR-NAME.
If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is specified keep current-buffer and switch to others buffers
in separate windows.
If a prefix arg is given split windows vertically."
(let ((mkds (helm-marked-candidates))
(initial-ow-fn (if (cdr (window-list))
#'switch-to-buffer-other-window
#'helm-switch-to-buffer-other-window)))
(helm-aif (cdr mkds)
(funcall helm-switch-to-buffers-function mkds other-window)
(if other-window
(funcall initial-ow-fn buffer-or-name)
(switch-to-buffer buffer-or-name)))))
;; helm-window-default-split-fn will be one function suitable for
;; helm-switch-to-buffers-function.
(defun helm-window-default-split-fn (candidates &optional other-window)
(if other-window
(funcall initial-ow-fn (car mkds))
(switch-to-buffer (car mkds)))
(save-selected-window
(cl-loop with nosplit
for b in it
when nosplit return
(message "Too many buffers to visit simultaneously")
do (condition-case _err
(helm-switch-to-buffer-other-window b 'balance)
(error (setq nosplit t) nil)))))
--
Thierry
Err... beside the rebasing upon your recent changes, this seems to precisely be what I've done, with helm-window-default-split-fn instead of helm-window-alternate-split.
No, the last commit I have seen was different, look carefully, or did I miss something ?
I think we are confusing each other with the rebasing shift... My apology. I'll just resubmit an updated PR, then we will sort it out.
Sorry, but I had to work on this at the same time to fix #1874 .
BTW I tried the mosaic function and I liked it, very nice.
Thanks.
I pushed a branch that can help you as a starting point, not fully tested though.
OTH.
Just to add here re:customization, standard window.el already provides a split-window-preferred-function that's used via display-buffers (and therefore other functions that can create other windows, such as switch-to-buffer-other-window) to allow users to customize their preferred window layout. Recent changes surprised me a bit by not honoring this customization anymore, so perhaps something could be worked out from here.
Zak B. Elep notifications@github.com writes:
Just to add here re:customization, standard window.el already provides
a split-window-preferred-function that's used via display-buffers (and
therefore other functions that can create other windows, such as
switch-to-buffer-other-window) to allow users to customize their
preferred window layout. Recent changes surprised me a bit by not
honoring this customization anymore, so perhaps something could be
worked out from here.
split-window-preferred-function is split-window-sensibly which split
windows in an unpredictable way i.e it tries to detect for you the best
side where to split your window.
For splitting one window it still usable, but when used to split several
windows it is unusable.
So actually it is simple, user can split window vertically or
horizontally according to prefix arg, what I can do if you prefer C-c o
splitting vertically is providing a variable that inverse the behavior
of prefix arg:
helm-switch-to-buffer-ow-vertically == t
C-c o => split vertically
C-u C-c o => split horizontally
helm-switch-to-buffer-ow-vertically == nil
C-c o => split horizontally
C-u C-c o => split vertically
For the case of more than one window, windows are actually splitted
vertically or horizontally, but it will change to allow user choosing
different layouts and this is not possible if we use an unpredictable
way to split windows (split-window-sensibly).
Is the alternative of using helm-switch-to-buffer-ow-vertically (see
above) would be acceptable for you?
Thanks.
--
Thierry
Thanks @thierryvolpiatto, I think it could work, though I might have to update my setup.
I point to split-window-preferred-function because it can be customized
by the user, e.g. in my init.el:
;; make window splits much smarter especially when on widescreen
(defun zakame/split-window-prefer-side-by-side (window)
"Split WINDOW, preferably side by side."
(let ((split-height-threshold (and (< (window-width window)
split-width-threshold)
split-height-threshold)))
(split-window-sensibly window)))
(setq split-window-preferred-function
#'zakame/split-window-prefer-side-by-side)
Perhaps it might be something that Helm can document/point the user at doing (and/or suggesting customization that would lead them to their preferred splitting/other-window layout,) instead of implementing directly.
Zak B. Elep notifications@github.com writes:
Thanks @thierryvolpiatto, I think it could work, though I might have to update my setup.
I point to split-window-preferred-function because it can be customized
by the user, e.g. in my init.el:;; make window splits much smarter especially when on widescreen
(defun zakame/split-window-prefer-side-by-side (window)
"Split WINDOW, preferably side by side."
(let ((split-height-threshold (and (< (window-width window)
split-width-threshold)
split-height-threshold)))
(split-window-sensibly window)))
(setq split-window-preferred-function
#'zakame/split-window-prefer-side-by-side)Perhaps it might be something that Helm can document/point the user at
doing (and/or suggesting customization that would lead them to their
preferred splitting/ other-window layout,) instead of implementing
directly.
Yes probably, but anyway such setting relaying on split-window-sensibly
will work as long as you split to one window, but will be become erratic
when switching to many windows, especially if we want to provide
different customization like @ambrevar wants (alternate, mosaic, maybe
others).
But maybe something using same behavior as before i.e allowing using a
configuration such as yours could be added.
--
Thierry
OTOH, this is basically window management already, which seems to stray far from Helm's core purpose as a candidate selection framework. Perhaps such a system could be built on top of Helm as an add-on, but I don't think it should be in helm-core or helm-utils.
That sounds reasonable, but would that mean we need an addon for just pressing RET in helm-buffers? What is RET supposed to do when multiple buffers are selected? I'd tend to say it's fundamental enough to _not_ be an extension.
The cost of window management is 1 setting and 1 function of a dozen of lines.
That sounds reasonable, but would that mean we need an addon for just pressing RET in helm-buffers? What is RET supposed to do when multiple buffers are selected? I'd tend to say it's fundamental enough to not be an extension.
Perhaps there's a mismatch of expectation here:
Putting it that way, perhaps we can agree for Helm to be able to accommodate both expectations?
The cost of window management is 1 setting and 1 function of a dozen of lines.
I suppose so; I looked at #1871 and it seems that perhaps I can make a function in the same fashion as your helm-window-alternate-split that would honor split-window-preferred-function, though OTOH this PR might also be duplicating most functionality in standard window.el.
Thanks for pointing that out. I'll investigate further.
Pierre Neidhardt notifications@github.com writes:
What is RET supposed to do when multiple buffers are selected?
As a remember:
RET with only one candidate:
Display candidate in current window.
RET with more than one candidate:
Display these candidates in separate windows, reusing the current window to
display the first candidate.
C-c o with only one candidate:
Display candidate in other window creating it if necessary.
C-c o with more than one candidate:
Switch to other-window and display all candidates in this window by
splitting it in different windows, each window displaying a candidate.
--
Thierry
Zak B. Elep notifications@github.com writes:
Thanks @thierryvolpiatto, I think it could work, though I might have
to update my setup.
>
I point to split-window-preferred-function because it can be customized
by the user, e.g. in my init.el:;; make window splits much smarter especially when on widescreen
(defun zakame/split-window-prefer-side-by-side (window)
"Split WINDOW, preferably side by side."
(let ((split-height-threshold (and (< (window-width window)
split-width-threshold)
split-height-threshold)))
Perhaps we could decide to set the value of
helm-switch-to-buffer-ow-vertically according to the value of
split-height-threshold calculated in the same fashion as you are doing
here?
--
Thierry
Something like this:
(defun helm-switch-to-buffer-other-window (buffer-or-name &optional balance)
"Switch to buffer-or-name in other window.
If a prefix arg is detected split vertically.
When argument balance is provided `balance-windows'."
(let* ((helm-switch-to-buffer-ow-vertically
(if (eq helm-switch-to-buffer-ow-vertically 'decide)
(and (numberp split-width-threshold)
(<= (window-width (selected-window))
split-width-threshold))
helm-switch-to-buffer-ow-vertically))
(right-side (if helm-switch-to-buffer-ow-vertically
(not helm-current-prefix-arg)
helm-current-prefix-arg)))
(select-window (split-window nil nil right-side))
(and balance (balance-windows))
(switch-to-buffer buffer-or-name)))
Maybe it is >= we want to use here, will check later, no more time now.
I have installed a patch allowing to use decide as value for helm-switch-to-buffer-ow-vertically.
With such setting helm will split its windows according to the value of split-width-threshold.
@zakame let me know if that fit your needs.
Thanks.
Hi @thierryvolpiatto, thanks for accommodating.
(setq helm-switch-to-buffer-ow-vertically 'decide) seems to fit my needs for now, and also seems to be sensible enough for providing the Expose-like functionality (M-a C-c o and similar) that @Ambrevar wants, though in my case, it does equal h-splits on the other (v-split) window.
How about encapsulating the current logic for decide in a customizable function, making it behave like #1871's helm-switch-to-buffers-function so the user can further implement e.g. mosaic splits?
Regarding the logic of decide: I've commited 2 more windowing functions and with the little experience it gave me, it did not seem obvious why the user would possibly want to change the logic of decide.
That being said, the code is repeated for every windowing callback, so we should rather have a dedicated function. Would I happen to be wrong and some users would need to change the behaviour, they would be able to override the function. Sounds good?
it did not obvious why the user would possibly want to change the logic of
decide.
I can offer a couple off the top of my head:
split-window-preferred-function)*compilation* or log windows, or keeping a preset layout in place (like Gnus or ECB windows,) and possibly keeping in line with desired behaviors from the first item aboveI believe those 2 examples can _only_ be achieved by writing an helm-switch-to-buffers-function.
helm-switch-to-buffer-ow-vertically is just about the direction, really.
@zakame: The changes have been merged. Let me know if you cannot achieve what you want with it.
Otherwise this issue can be closed.
@Ambrevar @thierryvolpiatto sorry for the late reply.
No, this doesn't achieve what I want, at all; helm-window-alternate-split-fn doesn't split left/right at expected, nor does helm-window-mosaic-fn splits in the expected mosaic fashion.
Tested with helm-20171010.2317/helm-core-20171014.947 via emacs-helm.sh.
helm-window-alternate-split-fn does not split left/right, it's supposed to split _alternatively_ horizontaly and vertically.
Can you provide more details on your expectations? Maybe some screenshots?
helm-window-alternate-split-fn needs a doc clarification then:
helm-window-alternate-split-fn is a compiled Lisp function in
‘helm-utils.el’.
(helm-window-alternate-split-fn CANDIDATES &optional OTHER-WINDOW-FN)
Alternatively split last window left and right.
This function is suitable for ‘helm-window-show-buffers-function’.
Perhaps "Split window left and right, _then up and down, then left and right, in alternate fashion..._"
I noticed helm-window-prefer-horizontal-split just now and setting it to 'decide along with leaving the rest default results in my desired behavior.
For the mosaic behavior, I was expecting the original one you displayed in https://github.com/emacs-helm/helm/issues/1860#issuecomment-330054028 but currently I'm getting only 4 windows of equal size in my fullscreen Emacs, rather than the one you illustrated. Furthermore, it would seem it shares the same behavior as helm-window-alternate-split-fn.
Thanks for the report, indeed the documentation is wrong.
The ASCII scheme you've linked is precisely helm-window-alternate-split-fn, not mosaic! :)
So if you are getting 4 windows of equal size, then perfect, that was the intended behaviour.
With 4 windows you should already see a difference between the various functions. Try with more windows to make it more apparent.
Ah, there we go! Thanks for clarifying.
Just to confirm, this is alternate-split:
(setq helm-window-show-buffers-function 'helm-window-default-split-fn)

while this is mosaic-split:
(setq helm-window-show-buffers-function 'helm-window-alternate-split-fn)

And just for the record, my preferred layout (default layout of one big/main window and smaller windows on the side) is like this:
(setq helm-window-show-buffers-function 'helm-window-default-split-fn
helm-window-prefer-horizontal-split 'decide)

Thanks for accommodating these styles @Ambrevar @thierryvolpiatto ! :tada:
@thierryvolpiatto: Can you please fix the doc? Then I believe you're free to close this issue. Pffew! :)
Wait, after further testing it seems that helm-window-prefer-horizontal-split is not properly respected by alternate-split and mosaic. I'm on it.
@thierryvolpiatto I need some help here: why does initial-ow-fn behaviour depends on the number of windows?
(defun helm-window-show-buffers (buffers &optional other-window)
"Show BUFFERS.
If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is non-nil, keep current buffer and switch to others buffers
in separate windows.
If a prefix arg is given split windows vertically."
(let ((initial-ow-fn (if (cdr (window-list))
#'switch-to-buffer-other-window
#'helm-window-other-window)))
Considering initial-ow-fn is called for one split only, it seems that both switch-to-buffer-other-window and helm-window-other-window are achieving the same thing.
I suggest we remove initial-ow-fn altogether and replace it with calls to helm-window-other-window (which depends on helm-window-prefer-horizontal-split, so it gives more leverage to the user).
Pierre Neidhardt notifications@github.com writes:
@thierryvolpiatto I need some help here: why does initial-ow-fn behaviour depends on the number of windows?
To reuse the existing other window.
(defun helm-window-show-buffers (buffers &optional other-window)
"Show BUFFERS.If more than one buffer marked switch to these buffers in separate windows.
If OTHER-WINDOW is non-nil, keep current buffer and switch to others buffers
in separate windows.
If a prefix arg is given split windows vertically."
(let ((initial-ow-fn (if (cdr (window-list))
#'switch-to-buffer-other-window
#'helm-window-other-window)))Considering initial-ow-fn is called for one split only, it seems that
both switch-to-buffer-other-window and helm-window-other-window are
achieving the same thing.
We have to split vertically or horizontally depending on prefix
arg and/or reuse other window.
I suggest we remove initial-ow-fn altogether and replace it with calls
to helm-window-other-window (which depends on
helm-window-prefer-horizontal-split, so it gives more leverage to the
user).
No, I just fixed this recently, see related issue #1893.
--
Thierry
OK, I get it: the difference is that helm-window-other-window always splits, while switch-to-buffer-other-window reuses the other window. Forgot about that.
Alright, see my fix in #1898.
Good, I think we can close this now.
Most helpful comment
Good, I think we can close this now.