about summary refs log tree commit diff
path: root/tools
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@tvl.su>2024-09-27T20·53+0300
committertazjin <mail@tazj.in>2024-09-29T00·50+0000
commit867b28bda197c27b6219bc2535aae3a2dd9a28e6 (patch)
tree0346a43c7e54070d3d13d785dd091a731157af88 /tools
parentc5e1fbb224507ce982046e0a595782c1ca922811 (diff)
feat(emacs-pkgs/niri): functions for seamless niri/emacs switching r/8728
Introduces a new buffer switching function which is also capable of switching to
existing Emacs frames that already display the target buffer, or to other
windows displayed in the same Niri session.

Not all behaviour is done yet, and there's an explanatory comment in the package
with more details.

Change-Id: I5a548931a681ba32fdb352ecec66845a75268c19
Reviewed-on: https://cl.tvl.fyi/c/depot/+/12535
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Diffstat (limited to 'tools')
-rw-r--r--tools/emacs-pkgs/niri/default.nix7
-rw-r--r--tools/emacs-pkgs/niri/niri.el89
2 files changed, 96 insertions, 0 deletions
diff --git a/tools/emacs-pkgs/niri/default.nix b/tools/emacs-pkgs/niri/default.nix
new file mode 100644
index 000000000000..995b1b6208ad
--- /dev/null
+++ b/tools/emacs-pkgs/niri/default.nix
@@ -0,0 +1,7 @@
+{ depot, ... }:
+
+depot.tools.emacs-pkgs.buildEmacsPackage rec {
+  pname = "niri";
+  version = "1.0";
+  src = ./niri.el;
+}
diff --git a/tools/emacs-pkgs/niri/niri.el b/tools/emacs-pkgs/niri/niri.el
new file mode 100644
index 000000000000..a677d093755d
--- /dev/null
+++ b/tools/emacs-pkgs/niri/niri.el
@@ -0,0 +1,89 @@
+;;; niri.el --- seamless niri/emacs integration. -*- lexical-binding: t; -*-
+;;
+;; Copyright (C) 2024 The TVL Contributors
+;;
+;; Author: Vincent Ambo <tazjin@tvl.su>
+;; Version: 1.0
+;; Package-Requires: ((emacs "27.1"))
+;;
+;;; Commentary:
+;;
+;; After having used EXWM for many years (7 or so?) it's become second nature
+;; that there is no difference between windows and Emacs buffers. This means
+;; that from any Emacs buffer (or, in the case of EXWM, from any X window) it's
+;; possible to switch to any of the others.
+;;
+;; This implements similar logic for Emacs running in Niri, consisting of two
+;; sides of the integration:
+;;
+;; # In Emacs
+;;
+;; Inside of Emacs, when switching buffers, populate the buffer-switching menu
+;; additionally with all open Niri windows. Selecting a Niri window moves the
+;; screen to that window.
+;;
+;; # Outside of Emacs
+;;
+;; Provides an interface for the same core functionality that can be used from
+;; shell scripts, and bound to selectors like dmenu or rofi.
+;;
+;; # Switching to Emacs buffers
+;;
+;; Some special logic exists for handling the case of switching to an Emacs
+;; buffer. There are several conditions that we can be in, that each have a
+;; predictable result:
+;;
+;; In a non-Emacs window, selecting an Emacs buffer will either switch to an
+;; Emacs frame already displaying this buffer, or launch a new frame for it.
+;;
+;; Inside of Emacs, if *another* frame is already displaying the buffer, switch
+;; to it. Otherwise the behaviour is the same as standard buffer switching.
+
+(require 'seq)
+(require 'map)
+
+(defun niri-list-windows ()
+  "List all currently open Niri windows."
+  (json-parse-string
+   (shell-command-to-string "niri msg -j windows")))
+
+(defun niri--list-selectables ()
+  "Lists all currently selectable things in a format that can work
+with completing-read. Selectable means all open Niri windows and
+all Emacs buffers."
+  (let* (;; all niri windows except for emacs frames
+         (windows (seq-filter (lambda (w) (not (equal (map-elt w "app_id") "emacs")))
+                              (niri-list-windows)))
+
+         ;; all non-hidden buffers
+         (buffers (seq-filter (lambda (b) (not (string-prefix-p " " (buffer-name b))))
+                              (buffer-list)))
+         (selectables (make-hash-table :test 'equal :size (+ (length windows)
+                                                             (length buffers)))))
+    (seq-do (lambda (window)
+              (map-put! selectables (map-elt window "title")
+                        (cons :niri window)))
+            windows)
+
+    (seq-do (lambda (buf)
+              (map-put! selectables (buffer-name buf)
+                        (cons :emacs buf)))
+            buffers)
+
+    selectables))
+
+(defun niri-go-anywhere ()
+  "Interactively select and switch to an open Niri window, or an
+  Emacs buffer."
+  (interactive)
+  (let* ((selectables (niri--list-selectables))
+         (target-key (completing-read "Switch to: " (map-keys selectables)))
+         (target (map-elt selectables target-key)))
+
+    (pcase (car target)
+      (:emacs (pop-to-buffer (cdr target) '((display-buffer-reuse-window
+                                             display-buffer-same-window)
+                                            (reusable-frames . 0))))
+      (:niri
+       (shell-command (format "niri msg action focus-window --id %d"
+                              (map-elt (cdr target) "id")))))))