5

Q: how can I shift a window column left and right in a frame?

I use a single frame with multiple windows side-by-side. Usually each window takes up the full height of the frame, but occasionally, one of those windows will be split vertically (eg, when reading mail, the headers above and the message body below).

Now: I often swap buffers from window to window, which is easy enough to do. But: the vertically-split windows almost always contain buffers that logically go together (as in the email example), and I want to move their columns together, rather than just one buffer at a time. In other words, in involves manipulating the window layout itself, rather than just which buffer is visible in which window.

Visual example: I'd like to be able to move the column containing windows (A) and (D) from the left-most column to the middle column:

+-------+-------+-------+    +-------+-------+-------+
|       |       |       |    |       |       |       |
|  (A)  |       |       |    |       |  (A)  |       |
|       |  (B)  |  (C)  | => |  (B)  |       |  (C)  |
+-------+       |       |    |       +-------+       |
|  (D)  |       |       |    |       |  (D)  |       |
+-------+-------+-------+    +-------+-------+-------+

How can one accomplish this cleanly? I say "cleanly" because I've hacked together something that is pretty slow, pretty ugly, and pretty convoluted -- strong suggesting that I'm Doing It Wrong™.

3
  • Here is a link to a left/right display-buffer custom example -- you can modify them to suit your needs: stackoverflow.com/a/21544307/2112489 The below functions are already in window.el
    – lawlist
    Nov 11, 2014 at 9:11
  • Outline of function: Do everything from B (initial window selected); get info for A, B and D; using display-buffer family of functions display A in top of B and display D in bottom of B -- the split can be made by virtue of the display-buffer family of functions (see example in link mentioned in comment above for internal function split-window); delete A or D and using the delete-window function which permits specifying a specific window; and finally display-buffer B where A or D is.
    – lawlist
    Nov 11, 2014 at 9:18
  • @lawlist: thanks for the thoughts and the link. The material I've put together looks broadly like what you sketched, but I can't claim any elegance on its behalf. The use case doesn't strike me as all that esoteric, so I suppose I'm a little surprised that the functionality isn't already baked in somewhere.
    – Dan
    Nov 13, 2014 at 2:53

1 Answer 1

4

I don't know of any built-in way to do what you want. But if you want to roll your own implementation, perhaps you could use the window-state-get and window-state-put functions to get an abstract representation of the window splitting configuration, modify it, and then install the modified version as the new configuration.

Below is a tentative implementation of this concept to do a rotation of all columns (this is not exactly what you want since it performs a circular permutation of all columns instead of moving the current one left or right):

+-------+-------+-------+    +-------+-------+-------+
|       |       |       |    |       |       |       |
|  (A)  |       |       |    |       |  (A)  |       |
|       |  (B)  |  (C)  | => |  (C)  |       |  (B)  |
+-------+       |       |    |       +-------+       |
|  (D)  |       |       |    |       |  (D)  |       |
+-------+-------+-------+    +-------+-------+-------+


(defun rotate-columns ()
  "Perform a circular permutation of all columns in the current frame"
  (interactive)
  (let ((win-state (window-state-get (frame-root-window))))
    ;; Don't do anything if the root window is not split side-by-side
    (when (eq 'hc (nth 1 win-state))
      (let ((split-state (cddr win-state))
            header columns)

        ;; Isolate the list of columns from the window parameters
        (mapc (lambda (param)
                (if (memq (car param) '(vc leaf))
                    ;; Column: potentially remove the (last . t) parameter
                    (push (cons (car param)
                                (remove-if
                                 (lambda (p) (eq (car p) 'last))
                                 (cdr param)))
                          columns)
                  ;; Header
                  (push param header)))
              split-state)

        ;; Rotate the columns
        ;;   (this is where more complex behaviour should be implemented to
        ;;   detect the currently active column and move it)
        (let* ((first  (car columns))
               (rest   (cdr columns))
               (last   (car rest)))
          ;; Add the (last . t) parameter to the last column
          (setcdr last (cons '(last . t)
                             (cdr last)))
          (setq columns (cons
                         first
                         (nreverse rest))))

        ;; Update the window state data structure...
        (setf (cddr win-state)
              (concatenate 'list
                           (nreverse header)
                           columns)))
      ;; ...and actually install it in the root window
      (delete-other-windows)
      (window-state-put win-state nil 'safe))))

Update:

Building upon this stub, I started a more full-fledged library to do this: colshift. It should already implement correctly the shifting operation you asked for...

5
  • Thanks for your suggestion -- I'll look into the code a little deeper to see if I can make it work for me. However, when I drop in rotate-columns verbatim and try it, it throws a "#<window 5> is not a live window" error.
    – Dan
    Nov 15, 2014 at 13:54
  • I edited the end; maybe the new version works better in your setup. Nov 15, 2014 at 16:53
  • Yes, it's working now. Thank you! I'll adapt it to swap columns around, but this setup already improves the code I had by a great deal.
    – Dan
    Nov 15, 2014 at 22:10
  • The thing I'm not sure how to do cleanly is find which column contains the currently selected window. Maybe call window-parent until you get to the root window. The currently active column should be the last you get before actually ending up in the root window. Nov 15, 2014 at 23:00
  • @Dan I implemented this in colshift. Normally it should work and do the shifting you originally asked for. Please let me know if you have problems with it. Nov 17, 2014 at 12:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.