mirror of
https://github.com/SqrtMinusOne/sqrtminusone.github.io.git
synced 2025-12-10 15:53:03 +03:00
feat(emacs-i3): done
This commit is contained in:
parent
d1b197945d
commit
d2a41a0a76
2 changed files with 65 additions and 35 deletions
|
|
@ -1,20 +1,18 @@
|
|||
+++
|
||||
title = "Getting a consistent set of keybindings between i3 and Emacs"
|
||||
author = ["Pavel Korytov"]
|
||||
date = 2021-10-04
|
||||
date = 2021-10-06
|
||||
tags = ["emacs", "i3wm"]
|
||||
draft = true
|
||||
draft = false
|
||||
+++
|
||||
|
||||
## Intro {#intro}
|
||||
|
||||
I got a bit jealous of EXWM having a consistent set of keybindings to manage both Emacs windows and X windows, so I figured I could try to implement something like this with i3.
|
||||
One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred [i3wm](https://i3wm.org), two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3.
|
||||
|
||||
Why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn't feel snappy enough. Also, I really like i3's tree-based layout structure; I feel like it fits my workflow much better than master/stack paradigm of [XMonad](https://xmonad.org/), for instance. Although to be clear, I haven't tried WMs other than these.
|
||||
But why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn't feel snappy enough. Also, I really like i3's tree-based layout structure; I feel like it fits my workflow much better than anything else I tried, including the master/stack paradigm of [XMonad](https://xmonad.org/), for instance.
|
||||
|
||||
One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs which are configured in an actual programing language, like the mentioned XMonad, [Qtile](http://www.qtile.org/), [Awesome](https://awesomewm.org/), etc. But I think i3's extensibility is underappreciated.
|
||||
|
||||
Indeed, i3 may not give as much freedom as the former three do, but it is still possible can execute arbitrary logic with `i3-msg` and interact with i3 in arbitrary way using its IPC.
|
||||
One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs that are configured in an actual programing language, like the mentioned XMonad, [Qtile](http://www.qtile.org/), [Awesome](https://awesomewm.org/), etc. But I think i3's extensibility is underappreciated, although the contents of this article may lie closer to the limits of how far one can go there.
|
||||
|
||||
|
||||
## Emacs integration {#emacs-integration}
|
||||
|
|
@ -23,7 +21,9 @@ What I'm trying to do is actually quite simple, so I'm somewhat surprised I didn
|
|||
|
||||
The basic idea is to launch a normal i3 command with `i3-msg` in case the current window is not Emacs, otherwise pass that command to Emacs with `emacsclient`. In Emacs, execute the command if possible, otherwise pass the command back to i3.
|
||||
|
||||
This may seem like a lot of overhead, but I didn't feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard the interaction feels seamless. The only concern is that this command flow is vunerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.
|
||||
This may seem like a lot of overhead, but I didn't feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard, the interaction feels seamless. The only concern is that this command flow is vulnerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.
|
||||
|
||||
One interesting observation here is that Emacs windows and X windows are sort of one-level entities, so I can talk just about "windows".
|
||||
|
||||
At any rate, we need a script to do the i3 -> Emacs part:
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ else
|
|||
fi
|
||||
```
|
||||
|
||||
My Emacs window title is set to `emacs[:<projectile-project-name>]@<hostname>`, hence the regex. The script is saved to an executable called `emacs-i3-integration`.
|
||||
My [Emacs frame title is set](https://sqrtminusone.xyz/configs/emacs/#custom-frame-title) to `emacs[:<projectile-project-name>]@<hostname>`, hence the regex. The script is saved to an executable called `emacs-i3-integration`.
|
||||
|
||||
For this to work, we need to make sure that Emacs starts a server, so here is an expression to do just that:
|
||||
|
||||
|
|
@ -56,10 +56,14 @@ And here is a simple macro to do the Emacs -> i3 part:
|
|||
|
||||
## Handling i3 commands {#handling-i3-commands}
|
||||
|
||||
Now we have to handle the required set of i3 commands. It is worth noting here that I'm not trying to implement a general mechanism to apply i3 commands to Emacs, rather I'm implementing a small subset that I use in my i3 configuration and that maps reasonably to the Emacs concepts.
|
||||
|
||||
Also, I use [evil-mode](https://github.com/emacs-evil/evil) and generally configure the software to have vim-style bindings where possible. So if you don't use evil-mode you'd have to detangle the given functions from evil, but then I guess you do not use super+hjkl to manage windows either.
|
||||
|
||||
|
||||
### `focus` {#focus}
|
||||
|
||||
Now we have to handle the required i3 commands. First, for the `focus` command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and `windmove` have the same names for directions, so the function is rather straightforward.
|
||||
First, for the `focus` command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and `windmove` have the same names for directions, so the function is rather straightforward.
|
||||
|
||||
One caveat here is that the minibuffer is always the bottom-most Emacs window, so it is necessary to check for that as well.
|
||||
|
||||
|
|
@ -85,18 +89,24 @@ bindsym $mod+Up exec emacs-i3-integration focus up
|
|||
bindsym $mod+Right exec emacs-i3-integration focus right
|
||||
```
|
||||
|
||||
The Emacs function has to be called like that:
|
||||
|
||||
```emacs-lisp
|
||||
(my/emacs-i3-windmove 'right)
|
||||
```
|
||||
|
||||
|
||||
### `move` {#move}
|
||||
|
||||
For the `move` I want the following behavior:
|
||||
For the `move` command I want the following behavior:
|
||||
|
||||
- if there is space in the required directon, move the Emacs window there;
|
||||
- if there is no space in the required direction, but space in two orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;
|
||||
- otherwise, move an X window (Emacs frame).
|
||||
- if there is space in the required direction, move the Emacs window there;
|
||||
- if there is no space in the required direction, but space in the orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;
|
||||
- otherwise, move an X window (which has to be an Emacs frame).
|
||||
|
||||
For the first part, `window-swap-states` with `windmove-find-other-window` do well enough.
|
||||
|
||||
`evil-move-window` works well for the second part. By itself it doesn't behave quite like i3, for instance, `(evil-move-window 'right)` in a tree-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described above.
|
||||
`evil-move-window` works well for the second part. By itself it doesn't behave quite like i3, for instance, `(evil-move-window 'right)` in a three-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described here.
|
||||
|
||||
So here is a simple predicate which checks whether there is space in the given direction.
|
||||
|
||||
|
|
@ -206,7 +216,7 @@ mode "resize" {
|
|||
}
|
||||
```
|
||||
|
||||
One note here is that Emacs has a built-in function called `balance-windows`, but i3 doesn't. Fortunately, there is a Python package called [i3-balance-workspace](https://github.com/atreyasha/i3-balance-workspace), which performs a similar operation with i3's IPC. If you use Guix like I do, I've written a [package definition](https://github.com/SqrtMinusOne/channel-q/blob/master/i3-balance-workspace.scm).
|
||||
Next, Emacs has a built-in function called `balance-windows`, but i3 doesn't. Fortunately, there is a Python package called [i3-balance-workspace](https://github.com/atreyasha/i3-balance-workspace), which performs a similar operation with i3's IPC. If you use Guix as I do, I've written a [package definition](https://github.com/SqrtMinusOne/channel-q/blob/master/i3-balance-workspace.scm).
|
||||
|
||||
So here is a small wrapper which calls `i3_balance_workspace` and `M-x balance-windows` if the current window is Emacs.
|
||||
|
||||
|
|
@ -220,7 +230,7 @@ i3_balance_workspace
|
|||
|
||||
### `layout toggle split` {#layout-toggle-split}
|
||||
|
||||
[transpose-frame](https://github.com/emacsorphanage/transpose-frame) is a package to "transpose" the current frame layout, which behaves somewhat similar to the `layout toggle split` command in i3, so I'll use it as well.
|
||||
[transpose-frame](https://github.com/emacsorphanage/transpose-frame) is a package to "transpose" the current Emacs windows layout, which behaves somewhat similar to the `layout toggle split` command in i3, so I'll use it as well.
|
||||
|
||||
```emacs-lisp
|
||||
(use-package transpose-frame
|
||||
|
|
@ -283,4 +293,11 @@ As a workaround, I found a small Rust program called [i3-switch-tabs](https://gi
|
|||
```conf-space
|
||||
bindsym $mod+period exec i3-switch-tabs right
|
||||
bindsym $mod+comma exec i3-switch-tabs left
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
## Conclusion {#conclusion}
|
||||
|
||||
So, how does all of that feel? Actually, I got used to that setup pretty quickly. Using `<s-Q>` to quit windows and the `<s-r>` submode to resize them is particularly nice. I've seen people making hydras in Emacs to do the latter.
|
||||
|
||||
All of that would probably be easier to do in a WM which is configured in a programming language rather than a self-cooked DSL, so I may try to replicate that somewhere else in an unknown time in the future. Meanwhile, it's pretty good.
|
||||
|
|
@ -1,26 +1,25 @@
|
|||
#+HUGO_SECTION: posts
|
||||
#+HUGO_BASE_DIR: ../
|
||||
#+TITLE: Getting a consistent set of keybindings between i3 and Emacs
|
||||
#+DATE: 2021-10-04
|
||||
#+HUGO_DRAFT: true
|
||||
#+DATE: 2021-10-06
|
||||
#+HUGO_TAGS: emacs
|
||||
#+HUGO_TAGS: i3wm
|
||||
|
||||
* Intro
|
||||
I got a bit jealous of EXWM having a consistent set of keybindings to manage both Emacs windows and X windows, so I figured I could try to implement something like this with i3.
|
||||
One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred [[https://i3wm.org][i3wm]], two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3.
|
||||
|
||||
Why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn't feel snappy enough. Also, I really like i3's tree-based layout structure; I feel like it fits my workflow much better than master/stack paradigm of [[https://xmonad.org/][XMonad]], for instance. Although to be clear, I haven't tried WMs other than these.
|
||||
But why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn't feel snappy enough. Also, I really like i3's tree-based layout structure; I feel like it fits my workflow much better than anything else I tried, including the master/stack paradigm of [[https://xmonad.org/][XMonad]], for instance.
|
||||
|
||||
One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs which are configured in an actual programing language, like the mentioned XMonad, [[http://www.qtile.org/][Qtile]], [[https://awesomewm.org/][Awesome]], etc. But I think i3's extensibility is underappreciated.
|
||||
|
||||
Indeed, i3 may not give as much freedom as the former three do, but it is still possible can execute arbitrary logic with =i3-msg= and interact with i3 in arbitrary way using its IPC.
|
||||
One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs that are configured in an actual programing language, like the mentioned XMonad, [[http://www.qtile.org/][Qtile]], [[https://awesomewm.org/][Awesome]], etc. But I think i3's extensibility is underappreciated, although the contents of this article may lie closer to the limits of how far one can go there.
|
||||
|
||||
* Emacs integration
|
||||
What I'm trying to do is actually quite simple, so I'm somewhat surprised I didn't find anything similar on the Internet. But I didn't look too hard.
|
||||
|
||||
The basic idea is to launch a normal i3 command with =i3-msg= in case the current window is not Emacs, otherwise pass that command to Emacs with =emacsclient=. In Emacs, execute the command if possible, otherwise pass the command back to i3.
|
||||
|
||||
This may seem like a lot of overhead, but I didn't feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard the interaction feels seamless. The only concern is that this command flow is vunerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.
|
||||
This may seem like a lot of overhead, but I didn't feel it even in the worst case (i3 -> Emacs -> i3), so at least in that regard, the interaction feels seamless. The only concern is that this command flow is vulnerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.
|
||||
|
||||
One interesting observation here is that Emacs windows and X windows are sort of one-level entities, so I can talk just about "windows".
|
||||
|
||||
At any rate, we need a script to do the i3 -> Emacs part:
|
||||
#+BEGIN_SRC bash
|
||||
|
|
@ -32,7 +31,7 @@ else
|
|||
fi
|
||||
#+END_SRC
|
||||
|
||||
My Emacs window title is set to =emacs[:<projectile-project-name>]@<hostname>=, hence the regex. The script is saved to an executable called =emacs-i3-integration=.
|
||||
My [[https://sqrtminusone.xyz/configs/emacs/#custom-frame-title][Emacs frame title is set]] to =emacs[:<projectile-project-name>]@<hostname>=, hence the regex. The script is saved to an executable called =emacs-i3-integration=.
|
||||
|
||||
For this to work, we need to make sure that Emacs starts a server, so here is an expression to do just that:
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
|
|
@ -48,8 +47,12 @@ And here is a simple macro to do the Emacs -> i3 part:
|
|||
#+END_SRC
|
||||
|
||||
* Handling i3 commands
|
||||
Now we have to handle the required set of i3 commands. It is worth noting here that I'm not trying to implement a general mechanism to apply i3 commands to Emacs, rather I'm implementing a small subset that I use in my i3 configuration and that maps reasonably to the Emacs concepts.
|
||||
|
||||
Also, I use [[https://github.com/emacs-evil/evil][evil-mode]] and generally configure the software to have vim-style bindings where possible. So if you don't use evil-mode you'd have to detangle the given functions from evil, but then I guess you do not use super+hjkl to manage windows either.
|
||||
|
||||
** =focus=
|
||||
Now we have to handle the required i3 commands. First, for the =focus= command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and =windmove= have the same names for directions, so the function is rather straightforward.
|
||||
First, for the =focus= command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and =windmove= have the same names for directions, so the function is rather straightforward.
|
||||
|
||||
One caveat here is that the minibuffer is always the bottom-most Emacs window, so it is necessary to check for that as well.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
|
|
@ -72,15 +75,20 @@ bindsym $mod+Down exec emacs-i3-integration focus down
|
|||
bindsym $mod+Up exec emacs-i3-integration focus up
|
||||
bindsym $mod+Right exec emacs-i3-integration focus right
|
||||
#+END_SRC
|
||||
|
||||
The Emacs function has to be called like that:
|
||||
#+begin_src emacs-lisp
|
||||
(my/emacs-i3-windmove 'right)
|
||||
#+end_src
|
||||
** =move=
|
||||
For the =move= I want the following behavior:
|
||||
- if there is space in the required directon, move the Emacs window there;
|
||||
- if there is no space in the required direction, but space in two orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;
|
||||
- otherwise, move an X window (Emacs frame).
|
||||
For the =move= command I want the following behavior:
|
||||
- if there is space in the required direction, move the Emacs window there;
|
||||
- if there is no space in the required direction, but space in the orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;
|
||||
- otherwise, move an X window (which has to be an Emacs frame).
|
||||
|
||||
For the first part, =window-swap-states= with =windmove-find-other-window= do well enough.
|
||||
|
||||
=evil-move-window= works well for the second part. By itself it doesn't behave quite like i3, for instance, =(evil-move-window 'right)= in a tree-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described above.
|
||||
=evil-move-window= works well for the second part. By itself it doesn't behave quite like i3, for instance, =(evil-move-window 'right)= in a three-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described here.
|
||||
|
||||
So here is a simple predicate which checks whether there is space in the given direction.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
|
|
@ -183,7 +191,7 @@ mode "resize" {
|
|||
}
|
||||
#+END_SRC
|
||||
|
||||
One note here is that Emacs has a built-in function called =balance-windows=, but i3 doesn't. Fortunately, there is a Python package called [[https://github.com/atreyasha/i3-balance-workspace][i3-balance-workspace]], which performs a similar operation with i3's IPC. If you use Guix like I do, I've written a [[https://github.com/SqrtMinusOne/channel-q/blob/master/i3-balance-workspace.scm][package definition]].
|
||||
Next, Emacs has a built-in function called =balance-windows=, but i3 doesn't. Fortunately, there is a Python package called [[https://github.com/atreyasha/i3-balance-workspace][i3-balance-workspace]], which performs a similar operation with i3's IPC. If you use Guix as I do, I've written a [[https://github.com/SqrtMinusOne/channel-q/blob/master/i3-balance-workspace.scm][package definition]].
|
||||
|
||||
So here is a small wrapper which calls =i3_balance_workspace= and =M-x balance-windows= if the current window is Emacs.
|
||||
#+BEGIN_SRC bash
|
||||
|
|
@ -194,7 +202,7 @@ i3_balance_workspace
|
|||
#+END_SRC
|
||||
|
||||
** =layout toggle split=
|
||||
[[https://github.com/emacsorphanage/transpose-frame][transpose-frame]] is a package to "transpose" the current frame layout, which behaves somewhat similar to the =layout toggle split= command in i3, so I'll use it as well.
|
||||
[[https://github.com/emacsorphanage/transpose-frame][transpose-frame]] is a package to "transpose" the current Emacs windows layout, which behaves somewhat similar to the =layout toggle split= command in i3, so I'll use it as well.
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package transpose-frame
|
||||
:straight t
|
||||
|
|
@ -249,3 +257,8 @@ As a workaround, I found a small Rust program called [[https://github.com/nikola
|
|||
bindsym $mod+period exec i3-switch-tabs right
|
||||
bindsym $mod+comma exec i3-switch-tabs left
|
||||
#+END_SRC
|
||||
|
||||
* Conclusion
|
||||
So, how does all of that feel? Actually, I got used to that setup pretty quickly. Using =<s-Q>= to quit windows and the =<s-r>= submode to resize them is particularly nice. I've seen people making hydras in Emacs to do the latter.
|
||||
|
||||
All of that would probably be easier to do in a WM which is configured in a programming language rather than a self-cooked DSL, so I may try to replicate that somewhere else in an unknown time in the future. Meanwhile, it's pretty good.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue