diff options
Diffstat (limited to 'users/tazjin/emacs')
-rw-r--r-- | users/tazjin/emacs/config/bindings.el | 63 | ||||
-rw-r--r-- | users/tazjin/emacs/config/custom.el | 27 | ||||
-rw-r--r-- | users/tazjin/emacs/config/desktop.el | 331 | ||||
-rw-r--r-- | users/tazjin/emacs/config/functions.el | 248 | ||||
-rw-r--r-- | users/tazjin/emacs/config/init.el | 186 | ||||
-rw-r--r-- | users/tazjin/emacs/config/look-and-feel.el | 102 | ||||
-rw-r--r-- | users/tazjin/emacs/config/mail-setup.el | 16 | ||||
-rw-r--r-- | users/tazjin/emacs/config/modes.el | 37 | ||||
-rw-r--r-- | users/tazjin/emacs/config/settings.el | 44 | ||||
-rw-r--r-- | users/tazjin/emacs/default.nix | 304 |
10 files changed, 719 insertions, 639 deletions
diff --git a/users/tazjin/emacs/config/bindings.el b/users/tazjin/emacs/config/bindings.el index 4e1f341e32..d8b63e33e4 100644 --- a/users/tazjin/emacs/config/bindings.el +++ b/users/tazjin/emacs/config/bindings.el @@ -1,11 +1,11 @@ +;; Switch buffers reliably in the face of spurious renames. +(global-set-key (kbd "C-x b") #'reliably-switch-buffer) + ;; Font size (define-key global-map (kbd "C-=") 'increase-default-text-scale) ;; '=' because there lies '+' (define-key global-map (kbd "C--") 'decrease-default-text-scale) (define-key global-map (kbd "C-x C-0") 'set-default-text-scale) -;; What does <tab> do? Well, it depends ... -(define-key prog-mode-map (kbd "<tab>") #'company-indent-or-complete-common) - ;; imenu instead of insert-file (global-set-key (kbd "C-x i") 'imenu) @@ -15,8 +15,6 @@ ;; Start eshell or switch to it if it's active. (global-set-key (kbd "C-x m") 'eshell) -;; Start a new eshell even if one is active. -(global-set-key (kbd "C-x C-p") 'ivy-browse-repositories) (global-set-key (kbd "M-g M-g") 'goto-line-with-feedback) ;; Miscellaneous editing commands @@ -24,7 +22,7 @@ (global-set-key (kbd "C-c a") 'align-regexp) (global-set-key (kbd "C-c m") 'mc/mark-dwim) -;; Browse URLs (very useful for Gitlab's SSH output!) +;; Browse URLs (very useful for Gerrit's push output, etc!) (global-set-key (kbd "C-c b p") 'browse-url-at-point) (global-set-key (kbd "C-c b b") 'browse-url) @@ -41,8 +39,11 @@ ;; Insert TODO comments (global-set-key (kbd "C-c t") 'insert-todo-comment) -;; Make sharing music easier -(global-set-key (kbd "s-s w") #'songwhip-lookup-url) +;; Open the depot +(global-set-key (kbd "s-s d") #'tvl-depot-status) + +;; Open any project through zoxide +(global-set-key (kbd "s-s r") #'zoxide-open-project) ;; Add subthread collapsing to notmuch-show. ;; @@ -54,4 +55,50 @@ (interactive) (notmuch-show-open-or-close-subthread t))) ;; open +;; Get rid of the annoying `save-some-buffers' shortcut which I +;; *NEVER* use intentionally. +(unbind-key (kbd "C-x s") 'global-map) + +;; German keyboard layout with Y and Z in the correct place. + +(quail-define-package + "german-qwerty" "German" "DE@" t + "German (Deutsch) input method with QWERTY keys" + nil t t t t nil nil nil nil nil t) + +;; 1! 2" 3§ 4$ 5% 6& 7/ 8( 9) 0= ß? [{ ]} +;; qQ wW eE rR tT yY uU iI oO pP üÜ +* +;; aA sS dD fF gG hH jJ kK lL öÖ äÄ #^ +;; zZ xX cC vV bB nN mM ,; .: -_ + +(quail-define-rules + ("-" ?ß) + ("=" ?\[) + ("`" ?\]) + ("[" ?ü) + ("]" ?+) + (";" ?ö) + ("'" ?ä) + ("\\" ?#) + ("/" ?-) + + ("@" ?\") + ("#" ?§) + ("^" ?&) + ("&" ?/) + ("*" ?\() + ("(" ?\)) + (")" ?=) + ("_" ??) + ("+" ?{) + ("~" ?}) + ("{" ?Ü) + ("}" ?*) + (":" ?Ö) + ("\"" ?Ä) + ("|" ?^) + ("<" ?\;) + (">" ?:) + ("?" ?_)) + (provide 'bindings) diff --git a/users/tazjin/emacs/config/custom.el b/users/tazjin/emacs/config/custom.el index a157c7a5fa..3e9a9dcd06 100644 --- a/users/tazjin/emacs/config/custom.el +++ b/users/tazjin/emacs/config/custom.el @@ -6,11 +6,7 @@ '(ac-auto-show-menu 0.8) '(ac-delay 0.2) '(avy-background t) - '(cargo-process--custom-path-to-bin "env CARGO_INCREMENTAL=1 cargo") '(cargo-process--enable-rust-backtrace 1) - '(company-auto-complete (quote (quote company-explicit-action-p))) - '(company-idle-delay 0.5) - '(custom-enabled-themes (quote (gruber-darker))) '(custom-safe-themes (quote ("d61fc0e6409f0c2a22e97162d7d151dee9e192a90fa623f8d6a071dbf49229c6" "3c83b3676d796422704082049fc38b6966bcad960f896669dfc21a7a37a748fa" "89336ca71dae5068c165d932418a368a394848c3b8881b2f96807405d8c6b5b6" default))) @@ -27,26 +23,3 @@ '(ns-right-command-modifier (quote meta)) '(require-final-newline (quote visit-save)) '(tls-program (quote ("gnutls-cli --x509cafile %t -p %p %h")))) -(custom-set-faces - ;; custom-set-faces was added by Custom. - ;; If you edit it by hand, you could mess it up, so be careful. - ;; Your init file should contain only one such instance. - ;; If there is more than one, they won't work right. - '(default ((t (:foreground "#e4e4ef" :background "#181818")))) - '(rainbow-delimiters-depth-1-face ((t (:foreground "#2aa198")))) - '(rainbow-delimiters-depth-2-face ((t (:foreground "#b58900")))) - '(rainbow-delimiters-depth-3-face ((t (:foreground "#268bd2")))) - '(rainbow-delimiters-depth-4-face ((t (:foreground "#dc322f")))) - '(rainbow-delimiters-depth-5-face ((t (:foreground "#859900")))) - '(rainbow-delimiters-depth-6-face ((t (:foreground "#268bd2")))) - '(rainbow-delimiters-depth-7-face ((t (:foreground "#cb4b16")))) - '(rainbow-delimiters-depth-8-face ((t (:foreground "#d33682")))) - '(rainbow-delimiters-depth-9-face ((t (:foreground "#839496")))) - '(term-color-black ((t (:background "#282828" :foreground "#282828")))) - '(term-color-blue ((t (:background "#96a6c8" :foreground "#96a6c8")))) - '(term-color-cyan ((t (:background "#1fad83" :foreground "#1fad83")))) - '(term-color-green ((t (:background "#73c936" :foreground "#73c936")))) - '(term-color-magenta ((t (:background "#9e95c7" :foreground "#9e95c7")))) - '(term-color-red ((t (:background "#f43841" :foreground "#f43841")))) - '(term-color-white ((t (:background "#f5f5f5" :foreground "#f5f5f5")))) - '(term-color-yellow ((t (:background "#ffdd33" :foreground "#ffdd33"))))) diff --git a/users/tazjin/emacs/config/desktop.el b/users/tazjin/emacs/config/desktop.el index 38da8f75bc..aa232fec2f 100644 --- a/users/tazjin/emacs/config/desktop.el +++ b/users/tazjin/emacs/config/desktop.el @@ -4,13 +4,27 @@ ;; window-management (EXWM) as well as additional system-wide ;; commands. -(require 's) -(require 'f) -(require 'dash) (require 'exwm) (require 'exwm-config) (require 'exwm-randr) (require 'exwm-systemtray) +(require 'exwm-xim ) +(require 'f) +(require 'ring) +(require 's) +(require 'seq) + +(defcustom tazjin--screen-lock-command "tazjin-screen-lock" + "Command to execute for locking the screen." + :group 'tazjin) + +(defcustom tazjin--backlight-increase-command "light -A 4" + "Command to increase screen brightness." + :group 'tazjin) + +(defcustom tazjin--backlight-decrease-command "light -U 4" + "Command to decrease screen brightness." + :group 'tazjin) (defun pactl (cmd) (shell-command (concat "pactl " cmd)) @@ -22,24 +36,14 @@ (defun brightness-up () (interactive) - (shell-command "xbacklight -inc 5") + (shell-command tazjin--backlight-increase-command) (message "Brightness increased")) (defun brightness-down () (interactive) - (shell-command "xbacklight -dec 5") + (shell-command tazjin--backlight-decrease-command) (message "Brightness decreased")) -(defun lock-screen () - (interactive) - ;; A sudoers configuration is in place that lets me execute this - ;; particular command without having to enter a password. - ;; - ;; The reason for things being set up this way is that I want - ;; xsecurelock.service to be started as a system-wide service that - ;; is tied to suspend.target. - (shell-command "/usr/bin/sudo /usr/bin/systemctl start xsecurelock.service")) - (defun set-xkb-layout (layout) "Set the current X keyboard layout." @@ -47,6 +51,12 @@ (shell-command "setxkbmap -option caps:super") (message "Set X11 keyboard layout to '%s'" layout)) +(defun lock-screen () + (interactive) + (set-xkb-layout "us") + (deactivate-input-method) + (shell-command tazjin--screen-lock-command)) + (defun create-window-name () "Construct window names to be used for EXWM buffers by inspecting the window's X11 class and title. @@ -60,39 +70,22 @@ human-accessible titles." (pcase (list (or exwm-class-name "unknown") (or exwm-title "unknown")) - ;; In Cider windows, rename the class and keep the workspace/file - ;; as the title. - (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider" title))) title)) - (format "Cider<%s>" (s-chop-suffix " - Cider" title))) + ;; Yandex.Music -> `Я.Music<... stuff ...>' + (`("Chromium-browser" ,(and (pred (lambda (title) (s-starts-with? "Yandex.Music - " title))) title)) + (format "Я.Music<%s>" (s-chop-prefix "Yandex.Music - " title))) - ;; Attempt to detect IRCCloud windows via their title, which is a - ;; combination of the channel name and network. - ;; - ;; This is what would often be referred to as a "hack". The regexp - ;; will not work if a network connection buffer is selected in - ;; IRCCloud, but since the title contains no other indication that - ;; we're dealing with an IRCCloud window - (`("Google-chrome" - ,(and (pred (lambda (title) - (s-matches? "^[\*\+]\s#[a-zA-Z0-9/\-]+\s\|\s[a-zA-Z\.]+$" title))) - title)) - (format "IRCCloud<%s>" title)) - - ;; For other Chrome windows, make the title shorter. - (`("Google-chrome" ,title) - (format "Chrome<%s>" (s-truncate 42 (s-chop-suffix " - Google Chrome" title)))) - - ;; Gnome-terminal -> Term - (`("Gnome-terminal" ,title) - ;; fish-shell buffers contain some unnecessary whitespace and - ;; such before the current working directory. This can be - ;; stripped since most of my terminals are fish shells anyways. - (format "Term<%s>" (s-trim-left (s-chop-prefix "fish" title)))) + ;; For other Chromium windows, make the title shorter. + (`("Chromium-browser" ,title) + (format "Chromium<%s>" (s-truncate 42 (s-chop-suffix " - Chromium" title)))) + + ;; similarly for Firefox + (`("firefox" ,title) + (format "FF<%s>" title)) ;; Quassel buffers ;; ;; These have a title format that looks like: - ;; "Quassel IRC - ##tvl (Freenode) — Quassel IRC" + ;; "Quassel IRC - #tvl (hackint) — Quassel IRC" (`("quassel" ,title) (progn (if (string-match @@ -109,9 +102,6 @@ (`(,class ,title) (format "%s<%s>" class (s-truncate 12 title))))) ;; EXWM launch configuration -;; -;; This used to use use-package, but when something breaks use-package -;; it doesn't exactly make debugging any easier. (let ((titlef (lambda () (exwm-workspace-rename-buffer (create-window-name))))) @@ -119,23 +109,57 @@ (add-hook 'exwm-update-title-hook titlef)) (fringe-mode 3) -(exwm-enable) -;; 's-N': Switch to certain workspace -(setq exwm-workspace-number 10) -(dotimes (i 10) - (exwm-input-set-key (kbd (format "s-%d" i)) - `(lambda () - (interactive) - (exwm-workspace-switch-create ,i)))) +;; tab-bar related config +(setq tab-bar-show 1) +(setq tab-bar-tab-hints t) -;; Launch applications / any command with completion (dmenu style!) -(exwm-input-set-key (kbd "s-d") #'counsel-linux-app) -(exwm-input-set-key (kbd "s-x") #'ivy-run-external-command) -(exwm-input-set-key (kbd "s-p") #'ivy-password-store) +(setq tab-bar-format + '(tab-bar-format-history + tab-bar-format-tabs tab-bar-separator + tab-bar-format-align-right tab-bar-format-global)) -;; Add X11 terminal selector to a key -(exwm-input-set-key (kbd "C-x t") #'ts/switch-to-terminal) +(setq tab-bar-new-tab-choice + (lambda () (get-buffer-create "*scratch*"))) + +(tab-bar-mode 1) + +(setq x-no-window-manager t) ;; TODO(tazjin): figure out when to remove this +(exwm-enable) +(exwm-randr-enable) + +;; Tab-management shortcuts + +(defun tab-bar-select-or-return () + "This function behaves like `tab-bar-select-tab', except it calls +`tab-recent' if asked to jump to the current tab. This simulates +the back&forth behaviour of i3." + (interactive) + (let* ((key (event-basic-type last-command-event)) + (tab (if (and (characterp key) (>= key ?1) (<= key ?9)) + (- key ?0) + 0)) + (current (1+ (tab-bar--current-tab-index)))) + (if (eq tab current) + (tab-recent) + (tab-bar-select-tab tab)))) + +(dotimes (i 8) + (exwm-input-set-key (kbd (format "s-%d" (+ 1 i))) #'tab-bar-select-or-return)) + +(exwm-input-set-key (kbd "s-9") #'tab-last) +(exwm-input-set-key (kbd "s-f") #'tab-next) +(exwm-input-set-key (kbd "s-b") #'tab-recent) +(exwm-input-set-key (kbd "s-w") #'tab-close) +(exwm-input-set-key (kbd "s-n") #'tab-new) + +;; Launch applications / any command with completion (dmenu style!) +(exwm-input-set-key (kbd "s-d") #'run-xdg-app) +(exwm-input-set-key (kbd "s-x") #'run-external-command) +(exwm-input-set-key (kbd "s-p") #'password-store-lookup) + +;; Add vterm selector to a key +(exwm-input-set-key (kbd "s-v") #'ts/switch-to-terminal) ;; Toggle between line-mode / char-mode (exwm-input-set-key (kbd "C-c C-t C-t") #'exwm-input-toggle-keyboard) @@ -162,19 +186,19 @@ (bind-xkb "no" "k n") (bind-xkb "ru" "k r") (bind-xkb "se" "k s") +(bind-xkb "us" "л г") +(bind-xkb "de" "л в") +(bind-xkb "no" "л т") +(bind-xkb "ru" "л к") -;; These are commented out because Emacs no longer starts (??) if -;; they're set at launch. -;; -;; (bind-xkb "us" "л г") -;; (bind-xkb "de" "л в") -;; (bind-xkb "no" "л т") -;; (bind-xkb "ru" "л к") +;; Configuration of EXWM input method handling for X applications +(exwm-xim-enable) +(setq default-input-method "russian-computer") +(push ?\C-\\ exwm-input-prefix-keys) ;; Line-editing shortcuts -(exwm-input-set-simulation-keys - '(([?\C-d] . delete) - ([?\C-w] . ?\C-c))) +(exwm-input-set-simulation-key (kbd "C-d") (kbd "DEL")) +(exwm-input-set-simulation-key (kbd "C-w") (kbd "C-c")) ;; Show time & battery status in the mode line (display-time-mode) @@ -183,80 +207,123 @@ ;; enable display of X11 system tray within Emacs (exwm-systemtray-enable) -;; Configure xrandr (multi-monitor setup). +;; Multi-monitor configuration. ;; -;; This makes some assumptions about how my machines are connected to -;; my home setup during the COVID19 isolation period. - -(defun set-randr-config (screens) - (setq exwm-randr-workspace-monitor-plist - (-flatten (-map (lambda (screen) - (-map (lambda (screen-id) (list screen-id (car screen))) (cdr screen))) - screens)))) - -;; Layouts for Vauxhall (laptop) +;; With tab-bar-mode, each monitor only displays at most one +;; workspace. Workspaces are only created, never deleted, meaning that +;; the number of workspaces will be equivalent to the maximum number +;; of displays that were connected during a session. +;; +;; The first workspace is special: It is kept on the primary monitor. -(defun randr-vauxhall-layout-single () - "Laptop screen only!" +(defun exwm-assign-workspaces () + "Assigns workspaces to the currently existing monitors, putting +the first one on the primary display and allocating the others +dynamically if needed in no particular order." (interactive) - (set-randr-config '(("eDP1" (number-sequence 0 9)))) - (shell-command "xrandr --output eDP1 --auto --primary") - (shell-command "xrandr --output HDMI1 --off") - (shell-command "xrandr --output DP2 --off") - (exwm-randr-refresh)) - -(defun randr-vauxhall-layout-all () - "Use all screens at home." + (let* ((randr-monitors (exwm-randr--get-monitors)) + (primary (car randr-monitors)) + (all-monitors (seq-map #'car (cadr randr-monitors))) + (sorted-primary-first (seq-sort (lambda (a b) + (or (equal a primary) + (< a b))) + all-monitors)) + ;; assign workspace numbers to each monitor ... + (workspace-assignments + (flatten-list (seq-map-indexed (lambda (monitor idx) + (list idx monitor)) + sorted-primary-first)))) + ;; ensure that the required workspaces exist + (exwm-workspace-switch-create (- (seq-length all-monitors) 1)) + + ;; update randr config + (setq exwm-randr-workspace-monitor-plist workspace-assignments) + (exwm-randr-refresh) + + ;; leave focus on primary workspace + (exwm-workspace-switch 0))) + +(defun list-available-monitors () + "List connected, but unused monitors." + (let* ((all-connected + (seq-map (lambda (line) (car (s-split " " line))) + (s-lines (s-trim (shell-command-to-string "xrandr | grep connected | grep -v disconnected"))))) + (all-active (seq-map #'car (cadr (exwm-randr--get-monitors))))) + (seq-filter (lambda (s) (not (seq-contains-p all-active s))) + all-connected))) + +(defun exwm-enable-monitor () + "Interactively construct an EXWM invocation that enable the +given monitor and assigns a workspace to it." (interactive) - (set-randr-config - '(("eDP1" 0) - ("HDMI1" 1 2 3 4 5) - ("DP2" 6 7 8 9))) - - (shell-command "xrandr --output HDMI1 --right-of eDP1 --auto --primary") - (shell-command "xrandr --output DP2 --right-of HDMI1 --auto --rotate left") - (exwm-randr-refresh)) -(defun randr-vauxhall-layout-wide-only () - "Use only the wide screen at home." + (let* ((monitors (list-available-monitors)) + (primary (car (exwm-randr--get-monitors))) + (monitor (pcase (seq-length monitors) + (0 (error "No available monitors.")) + (1 (car monitors)) + (_ + (completing-read "Which monitor? " (list-available-monitors) nil t)))) + + (configurations `(("secondary (left)" . ,(format "--left-of %s" primary)) + ("secondary (right)" . ,(format "--right-of %s" primary)) + ("primary (left)" . ,(format "--left-of %s --primary" primary)) + ("primary (right)" . ,(format "--right-of %s --primary" primary)) + ("mirror" . ,(format "--same-as %s" primary)))) + + (where (completing-read (format "%s should be " monitor) + (seq-map #'car configurations) + nil t)) + (xrandr-pos (cdr (assoc where configurations))) + (xrandr-cmd (format "xrandr --output %s --auto %s" monitor xrandr-pos))) + (message "Invoking '%s'" xrandr-cmd) + (shell-command xrandr-cmd) + (exwm-assign-workspaces))) + +(defun exwm-disable-monitor () + "Interactively choose a monitor to disable." (interactive) - (set-randr-config - '(("eDP1" 8 9 0) - ("HDMI1" 1 2 4 5 6 7))) - (shell-command "xrandr --output DP2 --off") - (shell-command "xrandr --output HDMI1 --right-of eDP1 --auto --primary") - (exwm-randr-refresh)) + (let* ((all (exwm-randr--get-monitors)) + (active (seq-map #'car (cadr all))) + (monitor (if (> (seq-length active) 1) + (completing-read "Disable which monitor? " active nil t) + (error "Only one monitor is active!"))) -;; Layouts for frog (desktop) + ;; If this monitor was primary, pick another active one instead. + (remaining (seq-filter (lambda (s) (not (equal s monitor))) active)) + (new-primary + (when (equal monitor (car all)) + (pcase (seq-length remaining) + (1 (car remaining)) + (_ (completing-read "New primary? " remaining nil t)))))) -(defun randr-frog-layout-right-only () - "Use only the right screen on frog." - (interactive) - (set-randr-config `(("DisplayPort-0" ,(number-sequence 0 9)))) - (shell-command "xrandr --output DisplayPort-0 --off") - (shell-command "xrandr --output DisplayPort-1 --auto --primary")) + (when new-primary + (shell-command (format "xrandr --output %s --primary" new-primary))) -(defun randr-frog-layout-both () - "Use the left and right screen on frog." - (interactive) - (set-randr-config `(("DisplayPort-0" 1 2 3 4 5) - ("DisplayPort-1" 6 7 8 9 0))) + (shell-command (format "xrandr --output %s --off" monitor)) + (exwm-assign-workspaces))) - (shell-command "xrandr --output DisplayPort-0 --auto --primary --left-of DisplayPort-1") - (shell-command "xrandr --output DisplayPort-1 --auto --right-of DisplayPort-0 --rotate left")) - -(pcase (s-trim (shell-command-to-string "hostname")) - ("vauxhall" - (exwm-input-set-key (kbd "s-m s") #'randr-vauxhall-layout-single) - (exwm-input-set-key (kbd "s-m a") #'randr-vauxhall-layout-all) - (exwm-input-set-key (kbd "s-m w") #'randr-vauxhall-layout-wide-only)) - - ("frog" - (exwm-input-set-key (kbd "s-m b") #'randr-frog-layout-both) - (exwm-input-set-key (kbd "s-m r") #'randr-frog-layout-right-only))) +(defun exwm-switch-monitor () + "Switch focus to another monitor by name." + (interactive) -(exwm-randr-enable) + ;; TODO: Filter out currently active? How to determine it? + (let* ((target (completing-read "Switch to monitor: " + (seq-map #'car (cadr (exwm-randr--get-monitors))) + nil t)) + (target-workspace + (cl-loop for (workspace screen) on exwm-randr-workspace-monitor-plist by #'cddr + when (equal screen target) return workspace))) + (exwm-workspace-switch target-workspace))) + +(exwm-input-set-key (kbd "s-m e") #'exwm-enable-monitor) +(exwm-input-set-key (kbd "s-m d") #'exwm-disable-monitor) +(exwm-input-set-key (kbd "s-m o") #'exwm-switch-monitor) + +;; Notmuch shortcuts as EXWM globals +;; (g m => gmail) +(exwm-input-set-key (kbd "s-g m") #'notmuch) ;; Let buffers move seamlessly between workspaces by making them ;; accessible in selectors on all frames. diff --git a/users/tazjin/emacs/config/functions.el b/users/tazjin/emacs/config/functions.el index 9bb6772a27..68a384d20f 100644 --- a/users/tazjin/emacs/config/functions.el +++ b/users/tazjin/emacs/config/functions.el @@ -2,9 +2,7 @@ (require 'dash) (require 'map) -(defun load-file-if-exists (filename) - (if (file-exists-p filename) - (load filename))) +(require 'gio-list-apps) ;; native module! (defun goto-line-with-feedback () "Show line numbers temporarily, while prompting for the line number input" @@ -17,29 +15,19 @@ (goto-line target))) (setq-local display-line-numbers nil))) -;; These come from the emacs starter kit - (defun esk-add-watchwords () (font-lock-add-keywords nil '(("\\<\\(FIX\\(ME\\)?\\|TODO\\|DEBUG\\|HACK\\|REFACTOR\\|NOCOMMIT\\)" 1 font-lock-warning-face t)))) +(add-hook 'prog-mode-hook 'esk-add-watchwords) + (defun esk-sudo-edit (&optional arg) (interactive "p") (if (or arg (not buffer-file-name)) (find-file (concat "/sudo:root@localhost:" (read-file-name "File: "))) (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name)))) -;; Open Fefes blog -(defun fefes-blog () - (interactive) - (eww "https://blog.fefe.de/")) - -;; Open the NixOS man page -(defun nixos-man () - (interactive) - (man "configuration.nix")) - ;; Get the nix store path for a given derivation. ;; If the derivation has not been built before, this will trigger a build. (defun nix-store-path (derivation) @@ -59,9 +47,6 @@ (buffer-name) require-final-newline)) -;; Helm includes a command to run external applications, which does -;; not seem to exist in ivy. This implementation uses some of the -;; logic from Helm to provide similar functionality using ivy. (defun list-external-commands () "Creates a list of all external commands available on $PATH while filtering NixOS wrappers." @@ -82,9 +67,9 @@ '(("google-chrome" . "--force-device-scale-factor=1.4")) "This setting lets me add additional flags to specific commands - that are run interactively via `ivy-run-external-command'.") + that are run interactively via `run-external-command'.") -(defun run-external-command (cmd) +(defun run-external-command--handler (cmd) "Execute the specified command and notify the user when it finishes." (let* ((extra-flags (cdr (assoc cmd external-command-flag-overrides))) @@ -96,59 +81,46 @@ (when (string= event "finished\n") (message "%s process finished." process)))))) -(defun ivy-run-external-command () +(defun run-external-command () "Prompts the user with a list of all installed applications and lets them select one to launch." (interactive) (let ((external-commands-list (list-external-commands))) - (ivy-read "Command:" external-commands-list - :require-match t - :history 'external-commands-history - :action #'run-external-command))) - -(defun ivy-password-store (&optional password-store-dir) - "Custom version of password-store integration with ivy that - actually uses the GPG agent correctly." + (run-external-command--handler + (completing-read "Command: " external-commands-list + nil ;; predicate + t ;; require-match + nil ;; initial-input + ;; hist + 'external-commands-history)))) + +(defun password-store-lookup (&optional password-store-dir) + "Interactive password-store lookup function that actually uses +the GPG agent correctly." (interactive) - (ivy-read "Copy password of entry: " - (password-store-list (or password-store-dir (password-store-dir))) - :require-match t - :keymap ivy-pass-map - :action (lambda (entry) - (let ((password (auth-source-pass-get 'secret entry))) - (password-store-clear) - (kill-new password) - (setq password-store-kill-ring-pointer kill-ring-yank-pointer) - (message "Copied %s to the kill ring. Will clear in %s seconds." - entry (password-store-timeout)) - (setq password-store-timeout-timer - (run-at-time (password-store-timeout) - nil 'password-store-clear)))))) - -(defun ivy-browse-repositories () - "Select a git repository and open its associated magit buffer." - (interactive) - (ivy-read "Repository: " - (magit-list-repos) - :require-match t - :sort t - :action #'magit-status)) - -(defun bottom-right-window-p () - "Determines whether the last (i.e. bottom-right) window of the - active frame is showing the buffer in which this function is - executed." - (let* ((frame (selected-frame)) - (right-windows (window-at-side-list frame 'right)) - (bottom-windows (window-at-side-list frame 'bottom)) - (last-window (car (seq-intersection right-windows bottom-windows)))) - (eq (current-buffer) (window-buffer last-window)))) + (let* ((entry (completing-read "Copy password of entry: " + (password-store-list (or password-store-dir + (password-store-dir))) + nil ;; predicate + t ;; require-match + )) + (password (or (let ((epa-suppress-error-buffer t)) + (auth-source-pass-get 'secret entry)) + (error "failed to decrypt '%s', wrong password?" entry)))) + (password-store-clear) + (kill-new password) + (setq password-store-kill-ring-pointer kill-ring-yank-pointer) + (message "Copied %s to the kill ring. Will clear in %s seconds." + entry (password-store-timeout)) + (setq password-store-timeout-timer + (run-at-time (password-store-timeout) + nil 'password-store-clear)))) (defhydra mc/mark-more-hydra (:color pink) - ("<up>" mmlte--up "Mark previous like this") + ("<up>" mc/mmlte--up "Mark previous like this") ("<down>" mc/mmlte--down "Mark next like this") ("<left>" mc/mmlte--left (if (eq mc/mark-more-like-this-extended-direction 'up) "Skip past the cursor furthest up" @@ -174,26 +146,15 @@ (mc/mmlte--down) (mc/mark-more-hydra/body)))) -(defun memespace-region () - "Make a meme out of it." +(setq mc/cmds-to-run-for-all '(kill-region paredit-newline)) - (interactive) - (let* ((start (region-beginning)) - (end (region-end)) - (memed - (message - (s-trim-right - (apply #'string - (-flatten - (nreverse - (-reduce-from (lambda (acc x) - (cons (cons x (-repeat (+ 1 (length acc)) 32)) acc)) - '() - (string-to-list (buffer-substring-no-properties start end)))))))))) - - (save-excursion (delete-region start end) - (goto-char start) - (insert memed)))) +(setq mc/cmds-to-run-once '(mc/mark-dwim + mc/mark-more-hydra/mc/mmlte--down + mc/mark-more-hydra/mc/mmlte--left + mc/mark-more-hydra/mc/mmlte--right + mc/mark-more-hydra/mc/mmlte--up + mc/mark-more-hydra/mmlte--up + mc/mark-more-hydra/nil)) (defun insert-todo-comment (prefix todo) "Insert a comment at point with something for me to do." @@ -237,11 +198,16 @@ (if prefix (text-scale-adjust 0) (set-face-attribute 'default nil :height (or to 120)))) -(defun scrot-select () +(defun screenshot-select (filename) "Take a screenshot based on a mouse-selection and save it to ~/screenshots." - (interactive) - (shell-command "scrot '$a_%Y-%m-%d_%s.png' -s -e 'mv $f ~/screenshots/'")) + (interactive "sScreenshot filename: ") + (let* ((path (f-join "~/screenshots" + (format "%s-%d.png" + (if (string-empty-p filename) "shot" filename) + (time-convert nil 'integer))))) + (shell-command (format "maim --select %s" path)) + (message "Wrote screenshot to %s" path))) (defun graph-unread-mails () "Create a bar chart of unread mails based on notmuch tags. @@ -289,6 +255,17 @@ (add-to-list 'project-find-functions #'find-depot-project) +(defun find-cargo-project (dir) + "Attempt to find the current project in `project-find-functions' +by looking for a `Cargo.toml' file." + (when dir + (unless (equal "/" dir) + (if (f-exists-p (f-join dir "Cargo.toml")) + (cons 'transient dir) + (find-cargo-project (f-parent dir)))))) + +(add-to-list 'project-find-functions #'find-cargo-project) + (defun magit-find-file-worktree () (interactive) "Find a file in the current (ma)git worktree." @@ -296,31 +273,80 @@ (magit-read-file-from-rev "HEAD" "Find file") #'pop-to-buffer-same-window)) -(defun songwhip--handle-result (status &optional cbargs) - ;; TODO(tazjin): Inspect status, which looks different in practice - ;; than the manual claims. - (if-let* ((response (json-parse-string - (buffer-substring url-http-end-of-headers (point-max)))) - (sw-path (ht-get* response "data" "path")) - (link (format "https://songwhip.com/%s" sw-path)) - (select-enable-clipboard t)) - (progn - (kill-new link) - (message "Copied Songwhip link (%s)" link)) - (warn "Something went wrong while retrieving Songwhip link!") - ;; For debug purposes, the buffer is persisted in this case. - (setq songwhip--debug-buffer (current-buffer)))) - -(defun songwhip-lookup-url (url) - "Look up URL on Songwhip and copy the resulting link to the clipboard." - (interactive "sEnter source URL: ") - (let ((songwhip-url "https://songwhip.com/api/") - (url-request-method "POST") - (url-request-extra-headers '(("Content-Type" . "application/json"))) - (url-request-data - (json-serialize `((country . "GB") - (url . ,url))))) - (url-retrieve "https://songwhip.com/api/" #'songwhip--handle-result nil t t) - (message "Requesting Songwhip URL ... please hold the line."))) +(defun zoxide-open-project () + "Query Zoxide for paths, and open the result as appropriate (magit or dired)." + (interactive) + (zoxide-open-with + nil + (lambda (path) + (condition-case err (magit-status-setup-buffer path) + (magit-outside-git-repo (dired path)))))) + +(defun toggle-nix-test-and-exp () + "Switch between the .nix and .exp file in a Tvix/Nix test." + (interactive) + (let* ((file (buffer-file-name)) + (other (if (s-suffix? ".nix" file) + (s-replace-regexp ".nix$" ".exp" file) + (if (s-suffix? ".exp" file) + (s-replace-regexp ".exp$" ".nix" file) + (error "Not a .nix/.exp file!"))))) + (find-file other))) + +(defun reliably-switch-buffer () + "Reliably and interactively switch buffers, without ending up in a +situation where the buffer was renamed during selection and an +empty new buffer is created. + +This is done by, in contrast to most buffer-switching functions, +retaining a list of the buffer *objects* and their associated +names, instead of only their names (which might change)." + + (interactive) + (let* ((buffers (seq-map (lambda (b) (cons (buffer-name b) b)) + (seq-filter (lambda (b) (not (string-prefix-p " " (buffer-name b)))) + (buffer-list)))) + + ;; Annotate buffers that display remote files. I frequently + ;; want to see it, because I might have identically named + ;; files open locally and remotely at the same time, and it + ;; helps with differentiating them. + (completion-extra-properties + '(:annotation-function + (lambda (name) + (if-let* ((file (buffer-file-name (cdr (assoc name buffers)))) + (remote (file-remote-p file))) + (format " [%s]" remote))))) + + (name (completing-read "Switch to buffer: " (seq-map #'car buffers))) + (selected (or (cdr (assoc name buffers)) + ;; Allow users to manually select invisible buffers ... + (get-buffer name)))) + (switch-to-buffer (or selected name) nil 't))) + +(defun run-xdg-app () + "Use `//users/tazjin/gio-list-apps' to retrieve a list of +installed (and visible) XDG apps, and let users launch them." + (interactive) + (let* ((apps (taz-list-xdg-apps)) + + ;; Display the command that will be run as an annotation + (completion-extra-properties + '(:annotation-function (lambda (app) (format " [%s]" (cdr (assoc app apps))))))) + + (run-external-command--handler (cdr (assoc (completing-read "App: " apps nil t) apps))))) + +(defun advice-remove-all (sym) + "Remove all advices from symbol SYM." + (interactive "aFunction symbol: ") + (advice-mapc (lambda (advice _props) (advice-remove sym advice)) sym)) + +(defun M-x-always-same-window () + "Run `execute-extended-command', but ensure that whatever it does +always opens in the same window in which the command was invoked." + (interactive) + (let ((display-buffer-overriding-action + '((display-buffer-same-window) . ((inhibit-same-window . nil))))) + (call-interactively #'execute-extended-command))) (provide 'functions) diff --git a/users/tazjin/emacs/config/init.el b/users/tazjin/emacs/config/init.el index 9f34c60a8d..ced3bf2ff8 100644 --- a/users/tazjin/emacs/config/init.el +++ b/users/tazjin/emacs/config/init.el @@ -1,29 +1,18 @@ ;;; init.el --- Package bootstrapping. -*- lexical-binding: t; -*- +;; Disable annoying warnings from native compilation. +(setq native-comp-async-report-warnings-errors nil + warning-suppress-log-types '((comp))) + ;; Packages are installed via Nix configuration, this file only ;; initialises the newly loaded packages. (require 'use-package) (require 'seq) - -;; TODO(tazjin): Figure out what's up with vc. -;; -;; Leaving vc enabled breaks all find-file operations with messages -;; about .git folders being absent, but in random places. -(require 'vc) -(setq vc-handled-backends nil) - (package-initialize) ;; Initialise all packages installed via Nix. -;; -;; TODO: Generate this section in Nix for all packages that do not -;; require special configuration. - -;; -;; Packages providing generic functionality. -;; (use-package ace-window :bind (("C-x o" . ace-window)) @@ -40,23 +29,12 @@ (use-package browse-kill-ring) -(use-package company - :hook ((prog-mode . company-mode)) - :config (setq company-tooltip-align-annotations t)) - -(use-package counsel - :after (ivy) - :config (counsel-mode 1) - :bind (("C-c r g" . counsel-rg))) +(use-package consult + :bind + ("C-c r g" . consult-ripgrep) + ("C-s" . consult-line)) (use-package dash) -(use-package dash-functional) - -(use-package dottime - :demand - :after (notmuch telega) - :config (dottime-display-mode t)) - (use-package gruber-darker-theme) (use-package eglot @@ -69,32 +47,13 @@ (use-package hydra) (use-package idle-highlight-mode :hook ((prog-mode . idle-highlight-mode))) -(use-package ivy - :config - (ivy-mode 1) - (setq enable-recursive-minibuffers t) - (setq ivy-use-virtual-buffers t)) - -(use-package ivy-pass :after (ivy)) - -(use-package ivy-prescient - :after (ivy prescient) - :config - (ivy-prescient-mode) - ;; Fixes an issue with how regexes are passed to ripgrep from counsel, - ;; see raxod502/prescient.el#43 - (setf (alist-get 'counsel-rg ivy-re-builders-alist) #'ivy--regex-plus)) - (use-package multiple-cursors) (use-package notmuch - :bind (:map global-map - ("s-g m" . notmuch) - ("s-g M" . counsel-notmuch)) ;; g m -> gmail - :config - (setq notmuch-search-oldest-first nil) - (setq notmuch-show-all-tags-list t) - (setq notmuch-hello-tag-list-make-query "tag:unread")) + :custom + (notmuch-search-oldest-first nil) + (notmuch-show-all-tags-list t) + (notmuch-hello-tag-list-make-query "tag:unread")) (use-package paredit :hook ((lisp-mode . paredit-mode) (emacs-lisp-mode . paredit-mode))) @@ -105,21 +64,20 @@ (pinentry-start)) (use-package prescient - :after (ivy counsel) - :config (prescient-persist-mode)) + :config + (prescient-persist-mode) + (setq completion-styles '(basic prescient))) (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) (use-package rainbow-mode) (use-package s) -(use-package string-edit) +(use-package string-edit-at-point) +(use-package term-switcher) -(use-package swiper - :after (counsel ivy) - :bind (("C-s" . swiper))) +(use-package undo-tree + :config (global-undo-tree-mode) + :custom (undo-tree-auto-save-history nil)) -(use-package telephone-line) ;; configuration happens outside of use-package -(use-package term-switcher) -(use-package undo-tree :config (global-undo-tree-mode)) (use-package uuidgen) (use-package which-key :config (which-key-mode t)) @@ -132,41 +90,13 @@ :config (setq magit-repository-directories '(("/home/tazjin/projects" . 2) ("/home/tazjin" . 1)))) -(use-package org-journal - ;; Always use my own key to encrypt files. There seems to be no - ;; global way to set this, as `epa-file-encrypt-to' only has an - ;; effect as a file-local variable (?!) - :hook ((org-journal-mode . (lambda () - (setq-local epa-file-encrypt-to - "DCF34CFAC1AC44B87E26333136EE34814F6D294A")))) - - :config - (setq org-journal-dir "/ssh:camden.tazj.in:/home/tazjin/journal" - org-journal-encrypt-journal t - org-journal-file-type 'weekly - org-journal-date-format "%A, %Y-%m-%d" - org-journal-file-format "%Y%m%d-weekly" - - ;; Saturday, because reasons. - org-journal-start-on-weekday 6) - - ;; org-journal doesn't actually enter its mode automatically if - ;; encryption is used (I'm not sure why), so this teaches Emacs to - ;; recognise the files. - (add-to-list 'auto-mode-alist '("[0-9]-weekly\\.gpg\\'" . org-journal-mode))) - -(use-package org-ql) - (use-package password-store) -(use-package pg) (use-package restclient) (use-package vterm - :config (progn - (setq vterm-shell "fish") - (setq vterm-exit-functions - (lambda (&rest _) (kill-buffer (current-buffer)))) - (setq vterm-kill-buffer-on-exit t))) + :custom + (vterm-shell "fish") + (vterm-kill-buffer-on-exit t)) ;; vterm removed the ability to set a custom title generator function ;; via the public API, so this overrides its private title generation @@ -185,9 +115,9 @@ (use-package cargo :hook ((rust-mode . cargo-minor-mode) (cargo-process-mode . visual-line-mode)) - :bind (:map cargo-minor-mode-map ("C-c C-c C-l" . ignore))) + :bind (:map cargo-mode-map ("C-c C-c C-l" . ignore))) -(use-package dockerfile-mode) +(use-package dockerfile-ts-mode) (use-package erlang :hook ((erlang-mode . (lambda () @@ -195,7 +125,6 @@ (local-set-key ">" 'self-insert-command))))) (use-package f) -(use-package geiser) (use-package go-mode :bind (:map go-mode-map ("C-c C-r" . recompile)) @@ -208,9 +137,7 @@ (use-package ielm :hook ((inferior-emacs-lisp-mode . (lambda () - (paredit-mode) - (rainbow-delimiters-mode-enable) - (company-mode))))) + (rainbow-delimiters-mode-enable))))) (use-package jq-mode :config (add-to-list 'auto-mode-alist '("\\.jq\\'" . jq-mode))) @@ -219,11 +146,8 @@ :hook ((kotlin-mode . (lambda () (setq indent-line-function #'indent-relative))))) -(use-package lsp-mode) - (use-package markdown-mode :config - (add-to-list 'auto-mode-alist '("\\.txt\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))) @@ -240,24 +164,58 @@ (use-package sly :hook ((sly-mrepl-mode . (lambda () (paredit-mode) - (rainbow-delimiters-mode-enable) - (company-mode)))) + (rainbow-delimiters-mode-enable)))) :config (setq common-lisp-hyperspec-root "file:///home/tazjin/docs/lisp/")) (use-package telega - :bind (:map global-map ("s-t" . telega)) - :config (telega-mode-line-mode 1)) + :bind (:map global-map ("s-c" . (lambda (p) (interactive "P") + (if p (call-interactively #'telega-chat-with) + (telega)))) + :map telega-chat-button-map ("a" . ignore)) + :config (telega-mode-line-mode 1) + :custom + (telega-emoji-use-images nil) + (telega-completing-read-function #'completing-read)) (use-package terraform-mode) -(use-package toml-mode) +(use-package toml-ts-mode) -(use-package tvl - :custom - (tvl-gerrit-remote "gerrit")) +(use-package treecrumbs + :hook ((yaml-ts-mode . treecrumbs-mode))) + +(use-package tvl) + +(use-package vertico + :config + (vertico-mode)) (use-package web-mode) -(use-package yaml-mode) +(use-package yaml-ts-mode) +(use-package zoxide) + +(use-package passively + :custom + (passively-store-state "/persist/tazjin/known-russian-words.el")) + +;; Note taking configuration for deft. +(use-package deft + :custom + (deft-directory "/persist/tazjin/deft/") + (deft-extensions '("md" "org" "txt")) + (deft-default-extension "md")) + +(use-package zetteldeft + :custom + ;; Configure for Markdown + (zetteldeft-link-indicator "[[") + (zetteldeft-link-suffix "]]") + (zetteldeft-title-prefix "# ") + (zetteldeft-list-prefix "* ")) + +;; Initialise midnight.el, which by default automatically cleans up +;; unused buffers at midnight. +(require 'midnight) (defgroup tazjin nil "Settings related to my configuration") @@ -273,7 +231,7 @@ ;; The way this will work for now is that Emacs will *write* ;; configuration to the file tracked in my repository, while not ;; actually *reading* it from there (unless Emacs is rebuilt). -(setq custom-file (expand-file-name "~/depot/tools/emacs/config/custom.el")) +(setq custom-file (f-join depot-path "users" "tazjin" "emacs" "config" "custom.el")) (load-library "custom") (defvar home-dir (expand-file-name "~")) @@ -288,10 +246,8 @@ look-and-feel functions settings - modes bindings eshell-setup)) -(telephone-line-setup) (ace-window-display-mode) ;; If a local configuration library exists, it should be loaded. @@ -301,4 +257,6 @@ (if-let (local-file (locate-library "local")) (load local-file)) +(require 'dottime) + (provide 'init) diff --git a/users/tazjin/emacs/config/look-and-feel.el b/users/tazjin/emacs/config/look-and-feel.el index 8cca6e1bf0..b771b4cd03 100644 --- a/users/tazjin/emacs/config/look-and-feel.el +++ b/users/tazjin/emacs/config/look-and-feel.el @@ -11,9 +11,6 @@ (setq ring-bell-function 'ignore) (setq initial-scratch-message "") -;; Remember layout changes -(winner-mode 1) - ;; Usually emacs will run as a proper GUI application, in which case a few ;; extra settings are nice-to-have: (when window-system @@ -22,69 +19,39 @@ (blink-cursor-mode -1)) ;; Configure Emacs fonts. -(let ((font (if (equal "frog" (s-trim (shell-command-to-string "hostname"))) - ;; For unclear reasons, frog refuses to render the - ;; regular font weight - everything ends up bold, - ;; which makes it hard to distinguish e.g. read/unread - ;; emails. - ;; - ;; Semi-bold looks a little different than on vauxhall - ;; and other machines, but it's alright. - (format "JetBrains Mono Semi Light-%d" 12) - (format "JetBrains Mono-%d" 12)))) +(let ((font (format "JetBrains Mono-%d" 12))) (setq default-frame-alist `((font . ,font))) (set-frame-font font t t)) -;; Configure telephone-line -(defun telephone-misc-if-last-window () - "Renders the mode-line-misc-info string for display in the - mode-line if the currently active window is the last one in the - frame. - - The idea is to not display information like the current time, - load, battery levels on all buffers." - - (when (bottom-right-window-p) - (telephone-line-raw mode-line-misc-info t))) - -(defun telephone-line-setup () - (telephone-line-defsegment telephone-line-last-window-segment () - (telephone-misc-if-last-window)) - - ;; Display the current EXWM workspace index in the mode-line - (telephone-line-defsegment telephone-line-exwm-workspace-index () - (when (bottom-right-window-p) - (format "[%s]" exwm-workspace-current-index))) - - ;; Define a highlight font for ~ important ~ information in the last - ;; window. - (defface special-highlight '((t (:foreground "white" :background "#5f627f"))) "") - (add-to-list 'telephone-line-faces - '(highlight . (special-highlight . special-highlight))) - - (setq telephone-line-lhs - '((nil . (telephone-line-position-segment)) - (accent . (telephone-line-buffer-segment)))) - - (setq telephone-line-rhs - '((accent . (telephone-line-major-mode-segment)) - (nil . (telephone-line-last-window-segment - telephone-line-exwm-workspace-index)) - - ;; TODO(tazjin): lets not do this particular thing while I - ;; don't actually run notmuch, there are too many things - ;; that have a dependency on the modeline drawing correctly - ;; (including randr operations!) - ;; - ;; (highlight . (telephone-line-notmuch-counts)) - )) - - (setq telephone-line-primary-left-separator 'telephone-line-tan-left - telephone-line-primary-right-separator 'telephone-line-tan-right - telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left - telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) - - (telephone-line-mode 1)) +;; Configure the modeline + +;; Implements a mode-line warning if there are any logged in TTY +;; sessions apart from the graphical one. +;; +;; The status is only updated once every 30 seconds, as it requires +;; shelling out to some commands (for now). +(defun list-tty-sessions () + "List all logged in tty sessions, except tty7 (graphical)" + (let ((command "who | awk '{print $2}' | grep -v tty7")) + (-filter (lambda (s) (not (string-empty-p s))) + (s-lines + (s-trim (let ((default-directory "/")) + (shell-command-to-string command))))))) + +(defvar cached-tty-sessions (cons (time-convert nil 'integer) (list-tty-sessions)) + "Cached TTY session value to avoid running the command too often.") + +;; TODO(tazjin): add this to the modeline + +(defun get-cached-tty-sessions () + (let ((time )) + (when (< 30 + (- (time-convert nil 'integer) + (car cached-tty-sessions))) + (setq cached-tty-sessions + (cons (time-convert nil 'integer) (list-tty-sessions))))) + + (cdr cached-tty-sessions)) ;; Auto refresh buffers (global-auto-revert-mode 1) @@ -119,4 +86,13 @@ ;; Don't wrap around when moving between buffers (setq windmove-wrap-around nil) +;; Don't show me all emacs warnings immediately. Unfortunately this is +;; not very granular, as emacs displays most of its warnings in the +;; `emacs' "category", but without it every time I +;; fullscreen/unfullscreen the warning buffer destroys my layout. +;; +;; Warnings suppressed by this are still logged to the warnings +;; buffer. +(setq warning-suppress-types '((emacs))) + (provide 'look-and-feel) diff --git a/users/tazjin/emacs/config/mail-setup.el b/users/tazjin/emacs/config/mail-setup.el index 1167bcadd3..7352c8ba10 100644 --- a/users/tazjin/emacs/config/mail-setup.el +++ b/users/tazjin/emacs/config/mail-setup.el @@ -1,8 +1,6 @@ (require 'notmuch) -(require 'counsel-notmuch) ;; (global-set-key (kbd "C-c m") 'notmuch-hello) -;; (global-set-key (kbd "C-c C-m") 'counsel-notmuch) ;; (global-set-key (kbd "C-c C-e n") 'notmuch-mua-new-mail) (setq notmuch-cache-dir (format "%s/.cache/notmuch" (getenv "HOME"))) @@ -25,8 +23,10 @@ (setq notmuch-show-empty-saved-searches t) ;; Mail sending configuration -(setq send-mail-function 'sendmail-send-it) ;; sendmail provided by MSMTP -(setq notmuch-always-prompt-for-sender t) +(setq sendmail-program "gmi") ;; lieer binary supports sendmail emulation +(setq message-sendmail-extra-arguments + '("send" "--quiet" "-t" "-C" "~/mail/account.tazjin")) +(setq send-mail-function 'sendmail-send-it) (setq notmuch-mua-user-agent-function (lambda () (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) (setq mail-host-address (system-name)) @@ -49,7 +49,7 @@ ;; handle that gracefully. (define-key notmuch-message-mode-map (kbd "C-x C-s") #'ignore) -;; Define a telephone-line segment for displaying the count of unread, +;; Define a mode-line segment for displaying the count of unread, ;; important mails in the last window's mode-line: (defvar *last-notmuch-count-redraw* 0) (defvar *current-notmuch-count* nil) @@ -74,10 +74,6 @@ (not (equal *current-notmuch-count* "I: 0; D: 0"))) *current-notmuch-count*)) -(telephone-line-defsegment telephone-line-notmuch-counts () - "This segment displays the count of unread notmuch messages in - the last window's mode-line (if unread messages are present)." - - (update-display-notmuch-counts)) +;; TODO(tazjin): re-add this segment to the modeline (provide 'mail-setup) diff --git a/users/tazjin/emacs/config/modes.el b/users/tazjin/emacs/config/modes.el deleted file mode 100644 index 69fb523d0d..0000000000 --- a/users/tazjin/emacs/config/modes.el +++ /dev/null @@ -1,37 +0,0 @@ -;; Initializes modes I use. - -(add-hook 'prog-mode-hook 'esk-add-watchwords) -(add-hook 'prog-mode-hook 'hl-line-mode) - -;; Use auto-complete as completion at point -(defun set-auto-complete-as-completion-at-point-function () - (setq completion-at-point-functions '(auto-complete))) - -(add-hook 'auto-complete-mode-hook - 'set-auto-complete-as-completion-at-point-function) - -;; Enable rainbow-delimiters for all things programming -(add-hook 'prog-mode-hook 'rainbow-delimiters-mode) - -;; Enable Paredit & Company in Emacs Lisp mode -(add-hook 'emacs-lisp-mode-hook 'company-mode) - -;; Always highlight matching brackets -(show-paren-mode 1) - -;; Always auto-close parantheses and other pairs -(electric-pair-mode) - -;; Keep track of recent files -(recentf-mode) - -;; Easily navigate sillycased words -(global-subword-mode 1) - -;; Transparently open compressed files -(auto-compression-mode t) - -;; Configure go-mode for Go2 Alpha -(add-to-list 'auto-mode-alist '("\\.go2$" . go-mode)) - -(provide 'modes) diff --git a/users/tazjin/emacs/config/settings.el b/users/tazjin/emacs/config/settings.el index b895d5e406..6c66ca608d 100644 --- a/users/tazjin/emacs/config/settings.el +++ b/users/tazjin/emacs/config/settings.el @@ -1,8 +1,5 @@ (require 'uniquify) -;; Move files to trash when deleting -(setq delete-by-moving-to-trash t) - ;; We don't live in the 80s, but we're also not a shitty web app. (setq gc-cons-threshold 20000000) @@ -48,4 +45,45 @@ ;; Show time in 24h format (setq display-time-24hr-format t) +;; Use python-mode for Starlark files. +(add-to-list 'auto-mode-alist '("\\.star\\'" . python-mode)) + +;; Use cmake-mode for relevant files. +(add-to-list 'auto-mode-alist '("ya\\.make\\'" . cmake-ts-mode)) + +;; Use tree-sitter modes for various languages. +(setq major-mode-remap-alist + '((bash-mode . bash-ts-mode) + (c++-mode . c++-ts-mode) + (c-mode . c-ts-mode) + (c-or-c++-mode . c-or-c++-ts-mode) + (json-mode . json-ts-mode) + (python-mode . python-ts-mode) + (rust-mode . rust-ts-mode) + (toml-mode . toml-ts-mode) + (yaml-mode . yaml-ts-mode) + (go-mode . go-ts-mode) + (cmake-mode . cmake-ts-mode))) + +;; Visually highlight current line in programming buffers +(add-hook 'prog-mode-hook 'hl-line-mode) + +;; Enable rainbow-delimiters for all things programming +(add-hook 'prog-mode-hook 'rainbow-delimiters-mode) + +;; Always highlight matching brackets +(show-paren-mode 1) + +;; Always auto-close parantheses and other pairs +(electric-pair-mode) + +;; Keep track of recent files +(recentf-mode) + +;; Easily navigate sillycased words +(global-subword-mode 1) + +;; Transparently open compressed files +(auto-compression-mode t) + (provide 'settings) diff --git a/users/tazjin/emacs/default.nix b/users/tazjin/emacs/default.nix index df73c05ffb..46843432f1 100644 --- a/users/tazjin/emacs/default.nix +++ b/users/tazjin/emacs/default.nix @@ -1,153 +1,189 @@ # This file builds an Emacs pre-configured with the packages I need # and my personal Emacs configuration. -# -# On NixOS machines, this Emacs currently does not support -# Imagemagick, see https://github.com/NixOS/nixpkgs/issues/70631. -# -# Forcing Emacs to link against Imagemagick currently causes libvterm -# to segfault, which is a lot less desirable than not having telega -# render images correctly. -{ depot, lib, ... }: +{ depot, lib, pkgs, ... }: -let - inherit (depot) third_party; +pkgs.makeOverridable + ({ emacs ? pkgs.emacs29 }: + let + emacsPackages = (pkgs.emacsPackagesFor emacs); + emacsWithPackages = emacsPackages.emacsWithPackages; - emacsWithPackages = (third_party.emacsPackagesGen third_party.emacs27).emacsWithPackages; + # If switching telega versions, use this variable because it will + # keep the version check, binary path and so on in sync. + currentTelega = epkgs: epkgs.melpaPackages.telega; - # Pick telega from unstable channel for recent fixes. - unstable = import third_party.nixpkgsSrc {}; - telegaUnstable = (unstable.emacsPackagesGen third_party.emacs27).telega; + # $PATH for binaries that need to be available to Emacs + emacsBinPath = lib.makeBinPath [ + (currentTelega pkgs.emacsPackages) + pkgs.libwebp # for dwebp, required by telega + ]; - # $PATH for binaries that need to be available to Emacs - emacsBinPath = lib.makeBinPath [ telegaUnstable ]; + identity = x: x; - identity = x: x; + # tree-sitter grammars for various ts-modes + customTreesitGrammars = emacs.pkgs.treesit-grammars.with-grammars (g: with g; [ + tree-sitter-bash + tree-sitter-c + tree-sitter-cmake + tree-sitter-cpp + tree-sitter-css + tree-sitter-dockerfile + tree-sitter-go + tree-sitter-gomod + tree-sitter-hcl + tree-sitter-html + tree-sitter-java + tree-sitter-json + tree-sitter-latex + tree-sitter-make + tree-sitter-nix + tree-sitter-python + tree-sitter-rust + tree-sitter-sql + tree-sitter-toml + tree-sitter-yaml + ]); - tazjinsEmacs = pkgfun: (emacsWithPackages(epkgs: pkgfun( - # Actual ELPA packages (the enlightened!) - (with epkgs.elpaPackages; [ - ace-window - avy - flymake - pinentry - rainbow-mode - undo-tree - xelb - ]) ++ + tazjinsEmacs = pkgfun: (emacsWithPackages (epkgs: pkgfun (with epkgs; [ + ace-link + ace-window + avy + bazel + browse-kill-ring + cargo + clojure-mode + consult + deft + direnv + elixir-mode + elm-mode + erlang + depotExwm + go-mode + google-c-style + gruber-darker-theme + haskell-mode + ht + hydra + idle-highlight-mode + inspector + jq-mode + kotlin-mode + kubernetes + magit + markdown-toc + multiple-cursors + nginx-mode + nix-mode + notmuch + paredit + password-store + pinentry + prescient + protobuf-mode + rainbow-delimiters + rainbow-mode + request + restclient + rust-mode + sly + string-edit-at-point + terraform-mode + undo-tree + uuidgen + vertico + vterm + web-mode + websocket + which-key + xelb + yasnippet + zetteldeft + zoxide - # MELPA packages: - (with epkgs.melpaPackages; [ - ace-link - bazel-mode - browse-kill-ring - cargo - clojure-mode - cmake-mode - counsel - counsel-notmuch - dash-functional - direnv - dockerfile-mode - eglot - elixir-mode - elm-mode - erlang - geiser - go-mode - gruber-darker-theme - haskell-mode - ht - hydra - idle-highlight-mode - intero - ivy - ivy-pass - ivy-prescient - jq-mode - kotlin-mode - lispy - lsp-mode - magit - markdown-toc - meson-mode - multi-term - multiple-cursors - nginx-mode - nix-mode - notmuch # this comes from pkgs.third_party - org-journal - org-ql - paredit - password-store - pg - polymode - prescient - protobuf-mode - racket-mode - rainbow-delimiters - refine - request - restclient - sly - string-edit - swiper - telegaUnstable - telephone-line - terraform-mode - toml-mode - transient - use-package - uuidgen - web-mode - websocket - which-key - yaml-mode - yasnippet - ]) ++ + # experimental (not otherwise embedded in config yet) + orderless + corfu + eat - # Custom packages - (with depot.tools.emacs-pkgs; [ - dottime - nix-util - term-switcher - tvl + # Wonky stuff + (currentTelega epkgs) + customTreesitGrammars # TODO(tazjin): how is this *supposed* to work?! - # patched / overridden versions of packages - depot.third_party.emacs.exwm - depot.third_party.emacs.rcirc - depot.third_party.emacs.vterm - depot.third_party.emacs.explain-pause-mode - ])))); -in lib.fix(self: l: f: third_party.writeShellScriptBin "tazjins-emacs" '' - export PATH="${emacsBinPath}:$PATH" - exec ${tazjinsEmacs f}/bin/emacs \ - --debug-init \ - --no-site-file \ - --no-site-lisp \ - --no-init-file \ - --directory ${./config} ${if l != null then "--directory ${l}" else ""} \ - --eval "(require 'init)" $@ - '' // { - # Call overrideEmacs with a function (pkgs -> pkgs) to modify the - # packages that should be included in this Emacs distribution. - overrideEmacs = f': self l f'; + # Custom depot packages (either ours, or overridden ones) + tvlPackages.dottime + tvlPackages.nix-util + tvlPackages.passively + tvlPackages.rcirc + tvlPackages.term-switcher + tvlPackages.treecrumbs + tvlPackages.tvl - # Call withLocalConfig with the path to a *folder* containing a - # `local.el` which provides local system configuration. - withLocalConfig = confDir: self confDir f; + # Dynamic/native modules + depot.users.tazjin.gio-list-apps + ]))); - # Build a derivation that uses the specified local Emacs (i.e. - # built outside of Nix) instead - withLocalEmacs = emacsBin: third_party.writeShellScriptBin "tazjins-emacs" '' + # Tired of telega.el runtime breakages through tdlib + # incompatibility. Target to make that a build failure instead. + tdlibCheck = + let + tgEmacs = emacsWithPackages (epkgs: [ (currentTelega epkgs) ]); + verifyTdlibVersion = builtins.toFile "verify-tdlib-version.el" '' + (require 'telega) + (defvar tdlib-version "${pkgs.tdlib.version}") + (when (or (version< tdlib-version + telega-tdlib-min-version) + (and telega-tdlib-max-version + (version< telega-tdlib-max-version + tdlib-version))) + (message "Found TDLib version %s, but require %s to %s" + tdlib-version telega-tdlib-min-version telega-tdlib-max-version) + (kill-emacs 1)) + ''; + in + pkgs.runCommand "tdlibCheck" { } '' + export PATH="${emacsBinPath}:$PATH" + ${tgEmacs}/bin/emacs --script ${verifyTdlibVersion} && touch $out + ''; + in + lib.fix + (self: l: f: (pkgs.writeShellScriptBin "tazjins-emacs" '' export PATH="${emacsBinPath}:$PATH" - export EMACSLOADPATH="${(tazjinsEmacs f).deps}/share/emacs/site-lisp:" - exec ${emacsBin} \ + exec ${tazjinsEmacs f}/bin/emacs \ --debug-init \ --no-site-file \ --no-site-lisp \ --no-init-file \ - --directory ${./config} \ - ${if l != null then "--directory ${l}" else ""} \ + --directory ${./config} ${if l != null then "--directory ${l}" else ""} \ + --eval "(add-to-list 'treesit-extra-load-path \"${customTreesitGrammars}/lib\")" \ --eval "(require 'init)" $@ - ''; - }) null identity + '').overrideAttrs + (_: { + passthru = { + # Expose original Emacs used for my configuration. + inherit emacs; + + # Expose the pure emacs with all packages. + inherit emacsPackages; + emacsWithPackages = tazjinsEmacs f; + + # Call overrideEmacs with a function (pkgs -> pkgs) to modify the + # packages that should be included in this Emacs distribution. + overrideEmacs = f': self l f'; + + # Call withLocalConfig with the path to a *folder* containing a + # `local.el` which provides local system configuration. + withLocalConfig = confDir: self confDir f; + + # Expose telega/tdlib version check as a target that is built in + # CI. + # + # TODO(tazjin): uncomment when telega works again + inherit tdlibCheck; + meta.ci.targets = [ "tdlibCheck" ]; + }; + })) + null + identity + ) +{ } |