From d2a41a0a7687671008d31fc0ac1e06044627785c Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Wed, 6 Oct 2021 12:11:53 +0300 Subject: [PATCH] feat(emacs-i3): done --- content/posts/2021-10-04-emacs-i3.md | 53 ++++++++++++++++++---------- org/2021-10-04-emacs-i3.org | 47 +++++++++++++++--------- 2 files changed, 65 insertions(+), 35 deletions(-) diff --git a/content/posts/2021-10-04-emacs-i3.md b/content/posts/2021-10-04-emacs-i3.md index 6cdce21..049d727 100644 --- a/content/posts/2021-10-04-emacs-i3.md +++ b/content/posts/2021-10-04-emacs-i3.md @@ -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[:]@`, 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[:]@`, 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 -``` \ No newline at end of file +``` + + +## Conclusion {#conclusion} + +So, how does all of that feel? Actually, I got used to that setup pretty quickly. Using `` to quit windows and the `` 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. \ No newline at end of file diff --git a/org/2021-10-04-emacs-i3.org b/org/2021-10-04-emacs-i3.org index 1adf146..d8fc8bb 100644 --- a/org/2021-10-04-emacs-i3.org +++ b/org/2021-10-04-emacs-i3.org @@ -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[:]@=, 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[:]@=, 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 == to quit windows and the == 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.