diff options
Diffstat (limited to 'users/tazjin/emacs/config')
-rw-r--r-- | users/tazjin/emacs/config/bindings.el | 61 | ||||
-rw-r--r-- | users/tazjin/emacs/config/custom.el | 2 | ||||
-rw-r--r-- | users/tazjin/emacs/config/desktop.el | 347 | ||||
-rw-r--r-- | users/tazjin/emacs/config/functions.el | 176 | ||||
-rw-r--r-- | users/tazjin/emacs/config/init.el | 110 | ||||
-rw-r--r-- | users/tazjin/emacs/config/look-and-feel.el | 93 | ||||
-rw-r--r-- | users/tazjin/emacs/config/mail-setup.el | 10 | ||||
-rw-r--r-- | users/tazjin/emacs/config/modes.el | 37 | ||||
-rw-r--r-- | users/tazjin/emacs/config/settings.el | 44 |
9 files changed, 392 insertions, 488 deletions
diff --git a/users/tazjin/emacs/config/bindings.el b/users/tazjin/emacs/config/bindings.el index 7865046d3d..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,7 +15,6 @@ ;; Start eshell or switch to it if it's active. (global-set-key (kbd "C-x m") 'eshell) -(global-set-key (kbd "C-x C-p") 'browse-repositories) (global-set-key (kbd "M-g M-g") 'goto-line-with-feedback) ;; Miscellaneous editing commands @@ -23,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) @@ -34,23 +33,17 @@ ;; Open a file in project: (global-set-key (kbd "C-c f") 'project-find-file) -;; Search in a project -(global-set-key (kbd "C-c r g") 'rg-in-project) - ;; Open a file via magit: (global-set-key (kbd "C-c C-f") #'magit-find-file-worktree) ;; 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 repo through zoxide -(global-set-key (kbd "s-s r") #'zoxide-open-magit) +;; Open any project through zoxide +(global-set-key (kbd "s-s r") #'zoxide-open-project) ;; Add subthread collapsing to notmuch-show. ;; @@ -66,4 +59,46 @@ ;; *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 91eaf69ae5..3e9a9dcd06 100644 --- a/users/tazjin/emacs/config/custom.el +++ b/users/tazjin/emacs/config/custom.el @@ -7,8 +7,6 @@ '(ac-delay 0.2) '(avy-background t) '(cargo-process--enable-rust-backtrace 1) - '(company-auto-complete (quote (quote company-explicit-action-p))) - '(company-idle-delay 0.5) '(custom-safe-themes (quote ("d61fc0e6409f0c2a22e97162d7d151dee9e192a90fa623f8d6a071dbf49229c6" "3c83b3676d796422704082049fc38b6966bcad960f896669dfc21a7a37a748fa" "89336ca71dae5068c165d932418a368a394848c3b8881b2f96807405d8c6b5b6" default))) diff --git a/users/tazjin/emacs/config/desktop.el b/users/tazjin/emacs/config/desktop.el index 0ee53276a1..aa232fec2f 100644 --- a/users/tazjin/emacs/config/desktop.el +++ b/users/tazjin/emacs/config/desktop.el @@ -4,14 +4,15 @@ ;; window-management (EXWM) as well as additional system-wide ;; commands. -(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." @@ -69,36 +70,17 @@ 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))) - (`("Google-chrome" ,(and (pred (lambda (title) (s-ends-with? " - Cider V" title))) title)) - (format "Cider V<%s>" (s-chop-suffix " - Cider V" 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)))) + ;; 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))) + + ;; 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 ;; @@ -120,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))))) @@ -130,117 +109,57 @@ (add-hook 'exwm-update-title-hook titlef)) (fringe-mode 3) -(exwm-enable) - -;; Create 10 EXWM workspaces -(setq exwm-workspace-number 10) - -;; 's-N': Switch to certain workspace, but switch back to the previous -;; one when tapping twice (emulates i3's `back_and_forth' feature) -(defvar *exwm-workspace-from-to* '(-1 . -1)) -(defun exwm-workspace-switch-back-and-forth (target-idx) - ;; If the current workspace is the one we last jumped to, and we are - ;; asked to jump to it again, set the target back to the previous - ;; one. - (when (and (eq exwm-workspace-current-index (cdr *exwm-workspace-from-to*)) - (eq target-idx exwm-workspace-current-index)) - (setq target-idx (car *exwm-workspace-from-to*))) - - (setq *exwm-workspace-from-to* - (cons exwm-workspace-current-index target-idx)) - - (exwm-workspace-switch-create target-idx)) -(dotimes (i 10) - (exwm-input-set-key (kbd (format "s-%d" i)) - `(lambda () - (interactive) - (exwm-workspace-switch-back-and-forth ,i)))) - -;; Implement MRU functionality for EXWM workspaces, making it possible -;; to jump to the previous/next workspace very easily. -(defvar *recent-workspaces* nil - "List of the most recently used EXWM workspaces.") - -(defvar *workspace-jumping-to* nil - "What offset in the workspace history are we jumping to?") - -(defvar *workspace-history-position* 0 - "Where in the workspace history are we right now?") - -(defun update-recent-workspaces () - "Hook to run on every workspace switch which will prepend the new -workspace to the MRU list, unless we are already on that -workspace. Does not affect the MRU list if a jump is -in-progress." +;; tab-bar related config +(setq tab-bar-show 1) +(setq tab-bar-tab-hints t) - (if *workspace-jumping-to* - (setq *workspace-history-position* *workspace-jumping-to* - *workspace-jumping-to* nil) +(setq tab-bar-format + '(tab-bar-format-history + tab-bar-format-tabs tab-bar-separator + tab-bar-format-align-right tab-bar-format-global)) - ;; reset the history position to the front on a normal jump - (setq *workspace-history-position* 0) - - (unless (eq exwm-workspace-current-index (car *recent-workspaces*)) - (setq *recent-workspaces* (cons exwm-workspace-current-index - (-take 9 *recent-workspaces*)))))) - -(add-to-list 'exwm-workspace-switch-hook #'update-recent-workspaces) - -(defun switch-to-previous-workspace () - "Switch to the previous workspace in the MRU workspace list." - (interactive) +(setq tab-bar-new-tab-choice + (lambda () (get-buffer-create "*scratch*"))) - (let* (;; the previous workspace is one position further down in the - ;; workspace history - (position (+ *workspace-history-position* 1)) - (target-idx (elt *recent-workspaces* position))) - (if (not target-idx) - (message "No previous workspace in history!") +(tab-bar-mode 1) - (setq *workspace-jumping-to* position) - (exwm-workspace-switch target-idx)))) +(setq x-no-window-manager t) ;; TODO(tazjin): figure out when to remove this +(exwm-enable) +(exwm-randr-enable) -(exwm-input-set-key (kbd "s-b") #'switch-to-previous-workspace) +;; Tab-management shortcuts -(defun switch-to-next-workspace () - "Switch to the next workspace in the MRU workspace list." +(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) - - (if (= *workspace-history-position* 0) - (message "No next workspace in history!") - (let* (;; The next workspace is one position further up in the - ;; history. This always exists unless someone messed with - ;; it. - (position (- *workspace-history-position* 1)) - (target-idx (elt *recent-workspaces* position))) - (setq *workspace-jumping-to* position) - (exwm-workspace-switch target-idx)))) - -(exwm-input-set-key (kbd "s-f") #'switch-to-next-workspace) - -;; Provide a binding for jumping to a buffer on a workspace. -(defun exwm-jump-to-buffer () - "Jump to a workspace on which the target buffer is displayed." - (interactive) - (let ((exwm-layout-show-all-buffers nil) - (initial exwm-workspace-current-index)) - (call-interactively #'exwm-workspace-switch-to-buffer) - ;; After jumping, update the back-and-forth list like on a direct - ;; index jump. - (when (not (eq initial exwm-workspace-current-index)) - (setq *exwm-workspace-from-to* - (cons initial exwm-workspace-current-index))))) - -(exwm-input-set-key (kbd "C-c j") #'exwm-jump-to-buffer) + (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") #'counsel-linux-app) +(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 X11 terminal selector to a key -(exwm-input-set-key (kbd "C-x t") #'ts/switch-to-terminal) +;; 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) @@ -267,10 +186,6 @@ in-progress." (bind-xkb "no" "k n") (bind-xkb "ru" "k r") (bind-xkb "se" "k s") - -;; These are commented out because Emacs no longer starts (??) if -;; they're set at launch. -;; (bind-xkb "us" "л г") (bind-xkb "de" "л в") (bind-xkb "no" "л т") @@ -282,9 +197,8 @@ in-progress." (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) @@ -293,76 +207,123 @@ in-progress." ;; enable display of X11 system tray within Emacs (exwm-systemtray-enable) -;; Configure xrandr (multi-monitor setup). - -(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)))) +;; Multi-monitor configuration. +;; +;; 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. -;; Layouts for Tverskoy (X13 AMD laptop) -(defun randr-tverskoy-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 '(("eDP" (number-sequence 0 9)))) - (shell-command "xrandr --output eDP --auto --primary") - (shell-command "xrandr --output HDMI-A-0 --off") - (exwm-randr-refresh)) - -(defun randr-tverskoy-split-workspace () - "Split the workspace across two screens, assuming external to the left." + (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 - '(("HDMI-A-0" 1 2 3 4 5 6 7 8 9) - ("eDP" 0))) - (shell-command "xrandr --output HDMI-A-0 --left-of eDP --auto") - (exwm-randr-refresh)) - -(defun randr-tverskoy-tv () - "Split off a workspace to the TV over HDMI." + (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 - '(("eDP" 1 2 3 4 5 6 7 8 9) - ("HDMI-A-0" 0))) - (shell-command "xrandr --output HDMI-A-0 --left-of eDP --mode 1920x1080") - (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")) +(defun exwm-switch-monitor () + "Switch focus to another monitor by name." + (interactive) -(pcase (s-trim (shell-command-to-string "hostname")) - ("tverskoy" - (exwm-input-set-key (kbd "s-m s") #'randr-tverskoy-layout-single) - (exwm-input-set-key (kbd "s-m 2") #'randr-tverskoy-split-workspace)) + ;; 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))) - ("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))) +(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) -(exwm-input-set-key (kbd "s-g M") #'counsel-notmuch) - -(exwm-randr-enable) ;; 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 f64907a591..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,24 +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 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) @@ -114,7 +107,9 @@ the GPG agent correctly." nil ;; predicate t ;; require-match )) - (password (auth-source-pass-get 'secret entry))) + (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) @@ -124,23 +119,6 @@ the GPG agent correctly." (run-at-time (password-store-timeout) nil 'password-store-clear)))) -(defun browse-repositories () - "Select a git repository and open its associated magit buffer." - - (interactive) - (magit-status - (completing-read "Repository: " (magit-list-repos)))) - -(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)))) - (defhydra mc/mark-more-hydra (:color pink) ("<up>" mc/mmlte--up "Mark previous like this") ("<down>" mc/mmlte--down "Mark next like this") @@ -178,27 +156,6 @@ the GPG agent correctly." mc/mark-more-hydra/mmlte--up mc/mark-more-hydra/nil)) -(defun memespace-region () - "Make a meme out of it." - - (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)))) - (defun insert-todo-comment (prefix todo) "Insert a comment at point with something for me to do." @@ -241,11 +198,16 @@ the GPG agent correctly." (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. @@ -296,10 +258,11 @@ the GPG agent correctly." (defun find-cargo-project (dir) "Attempt to find the current project in `project-find-functions' by looking for a `Cargo.toml' file." - (unless (equal "/" dir) - (if (f-exists-p (f-join dir "Cargo.toml")) - (cons 'transient dir) - (find-cargo-project (f-parent dir))))) + (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) @@ -310,45 +273,14 @@ by looking for a `Cargo.toml' file." (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 rg-in-project (&optional prefix) - "Interactively call ripgrep in the current project, or fall - back to ripgrep default behaviour if prefix is set." - (interactive "P") - (counsel-rg nil (unless prefix - (if-let ((pr (project-current))) - (project-root pr))))) - -(defun zoxide-open-magit () - "Query Zoxide for paths and open magit in the result." +(defun zoxide-open-project () + "Query Zoxide for paths, and open the result as appropriate (magit or dired)." (interactive) - (zoxide-open-with nil #'magit-status-setup-buffer)) + (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." @@ -361,4 +293,60 @@ by looking for a `Cargo.toml' 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 b0e80998d6..ced3bf2ff8 100644 --- a/users/tazjin/emacs/config/init.el +++ b/users/tazjin/emacs/config/init.el @@ -10,23 +10,9 @@ (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)) @@ -43,13 +29,10 @@ (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)) +(use-package consult + :bind + ("C-c r g" . consult-ripgrep) + ("C-s" . consult-line)) (use-package dash) (use-package gruber-darker-theme) @@ -59,39 +42,11 @@ (eglot-autoshutdown t) (eglot-send-changes-idle-time 0.3)) -(use-package elfeed - :config - (setq elfeed-feeds - '("https://lobste.rs/rss" - "https://www.anti-spiegel.ru/feed/" - "https://www.reddit.com/r/lockdownskepticism/.rss" - "https://www.reddit.com/r/rust/.rss" - "https://news.ycombinator.com/rss" - ("https://xkcd.com/atom.xml" media) - - ;; vlogcreations - ("https://www.youtube.com/feeds/videos.xml?channel_id=UCR0VLWitB2xM4q7tjkoJUPw" media) - ))) - (use-package ht) (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-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 @@ -109,19 +64,14 @@ (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 swiper - :after (counsel ivy) - :bind (("C-s" . swiper))) - -(use-package telephone-line) ;; configuration happens outside of use-package +(use-package string-edit-at-point) (use-package term-switcher) (use-package undo-tree @@ -144,11 +94,9 @@ (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 @@ -169,7 +117,7 @@ (cargo-process-mode . visual-line-mode)) :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 () @@ -189,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))) @@ -200,8 +146,6 @@ :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 '("\\.markdown\\'" . markdown-mode)) @@ -220,22 +164,34 @@ (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 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 @@ -275,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 "~")) @@ -290,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. diff --git a/users/tazjin/emacs/config/look-and-feel.el b/users/tazjin/emacs/config/look-and-feel.el index 72665d00c6..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) diff --git a/users/tazjin/emacs/config/mail-setup.el b/users/tazjin/emacs/config/mail-setup.el index 7fbece1b10..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"))) @@ -51,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) @@ -76,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 8b15b6cda1..afe181b70b 100644 --- a/users/tazjin/emacs/config/settings.el +++ b/users/tazjin/emacs/config/settings.el @@ -19,6 +19,9 @@ ediff-split-window-function 'split-window-horizontally initial-major-mode 'emacs-lisp-mode) +(setq-default tab-width 4) +(setq-default fill-column 80) + (add-to-list 'safe-local-variable-values '(lexical-binding . t)) (add-to-list 'safe-local-variable-values '(whitespace-line-column . 80)) @@ -45,4 +48,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) |