;;; init.el --- Emacs configuration -*- lexical-binding: t; -*- (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (unless package-archive-contents (package-refresh-contents)) (require 'use-package) (setq use-package-always-ensure t) (scroll-bar-mode -1) (setq display-line-numbers-type 'relative) (global-display-line-numbers-mode 1) (setq-default indent-tabs-mode nil) ;; Translucent background (text stays opaque). Needs a pgtk build under a ;; compositing WM; provided via nixos-config's emacs-pgtk package. ;;(defvar my/frame-alpha 90 ;; "Background opacity percentage for Emacs frames (100 = opaque).") ;;(set-frame-parameter nil 'alpha-background my/frame-alpha) ;;(add-to-list 'default-frame-alist `(alpha-background . ,my/frame-alpha)) ;;(defun my/toggle-transparency () ;; "Toggle the selected frame between translucent and fully opaque." ;; (interactive) ;; (set-frame-parameter ;; nil 'alpha-background ;; (if (eql (frame-parameter nil 'alpha-background) 100) my/frame-alpha 100))) ;;(global-set-key (kbd "C-c t") #'my/toggle-transparency) (setq custom-file (make-temp-file "emacs-custom-")) ;; move temp files out of the cwd (let ((auto-save-dir (concat user-emacs-directory "auto-save/")) (backup-dir (concat user-emacs-directory "backups/"))) (make-directory auto-save-dir t) (make-directory backup-dir t) (setq auto-save-file-name-transforms `((".*" ,auto-save-dir t))) (setq backup-directory-alist `(("." . ,backup-dir)))) (use-package evil :init (setq evil-want-keybinding nil) (setq evil-want-C-u-scroll t) :config (evil-mode 1)) (use-package evil-collection :after evil :config (evil-collection-init)) (add-hook 'eshell-mode-hook (lambda () (evil-insert-state))) (use-package vs-dark-theme :config (load-theme 'vs-dark t)) (use-package apheleia :config (add-to-list 'apheleia-mode-alist '(scheme-mode . lisp-indent)) (apheleia-global-mode +1)) (use-package geiser-guile) (use-package markdown-mode :ensure t :mode ("\\.md\\'" . gfm-mode) :hook (markdown-mode . flyspell-mode)) (use-package nix-ts-mode :mode "\\.nix\\'") (use-package rainbow-delimiters :hook (scheme-mode . rainbow-delimiters-mode)) (add-hook 'scheme-mode-hook #'electric-pair-local-mode) (setq ispell-program-name "aspell" ispell-dictionary "en_US") (add-hook 'markdown-mode-hook #'flyspell-mode) (use-package magit :bind ("C-x g" . magit-status)) (use-package agent-shell ;; Talks to LLM coding agents over the Agent Client Protocol (ACP). ;; Pi needs the external `pi-acp` adapter on PATH (provided via nixos-config). ;; Pi authenticates via OAuth -- run `/login` inside the shell once; no API key. :bind ("C-c a p" . agent-shell-pi-start-agent) :config (setq agent-shell-pi-acp-command '("pi-acp")) ;; Start ready to type instead of in evil normal state. (add-hook 'agent-shell-mode-hook #'evil-insert-state) ;; Same GUI-Enter gotcha as mpdel: shell-maker binds submit via a ;; comint-send-input remap on RET/C-m, but a GUI frame's Enter sends ;; -- which evil-collection shadows -- so input never gets sent. ;; Point (and keypad Enter) straight at submit in insert state. (defun my/agent-shell-restore-ret () (evil-local-set-key 'insert (kbd "") #'shell-maker-submit) (evil-local-set-key 'insert (kbd "") #'shell-maker-submit)) (add-hook 'agent-shell-mode-hook #'my/agent-shell-restore-ret)) (use-package mpdel :after evil :config (mpdel-mode) ;; In a GUI frame the Enter key sends , which evil-collection binds to ;; play/pause -- shadowing tablist's "descend into entry" command (that command ;; only sits on RET / C-m). Point (and the keypad Enter) at descend ;; in all mpdel list buffers (browser, playlists, ...). (defun my/mpdel-restore-ret () (evil-local-set-key 'normal (kbd "") #'tablist-find-entry) (evil-local-set-key 'normal (kbd "") #'tablist-find-entry)) (add-hook 'mpdel-tablist-mode-hook #'my/mpdel-restore-ret)) ;; "Recently added" view. MPD has no such entity, so subclass mpdel's filter ;; (which already renders a browsable, addable song list) and issue a search ;; sorted by modification time, newest first. ;; ;; This lives in its own top-level `with-eval-after-load' rather than in the ;; mpdel `:config' above: the `cl-defstruct' below `:include's libmpdel-filter, ;; which must exist when the struct is *macro-expanded*. Emacs eager-expands ;; each top-level form before evaluating it, so inside :config the struct is ;; expanded before (mpdel-mode) has loaded libmpdel -- failing with ;; "libmpdel-filter is not a struct name". By the time this separate form is ;; loaded, the use-package above has already pulled in mpdel/libmpdel. (with-eval-after-load 'mpdel (require 'cl-lib) (require 'libmpdel) (defvar my/mpdel-recent-count 50 "How many recently added songs `my/mpdel-recent' lists.") (cl-defstruct (my/mpdel-recent-entity (:include libmpdel-filter))) (cl-defmethod libmpdel-entity-name ((_e my/mpdel-recent-entity)) "Recently added") (cl-defmethod libmpdel-list-songs ((_e my/mpdel-recent-entity) function) (libmpdel-send-command (format "search \"(file != \\\"\\\")\" sort -Last-Modified window 0:%d" my/mpdel-recent-count) (lambda (data) (funcall function (libmpdel--create-songs-from-data data))))) (defun my/mpdel-recent () "Browse the most recently added songs, newest first." (interactive) (mpdel-core-open (make-my/mpdel-recent-entity :text "recently added"))) (defun my/mpdel-recent-replace () "Replace the current playlist with the recently added songs." (interactive) (libmpdel-list-songs (make-my/mpdel-recent-entity :text "recently added") (lambda (songs) (libmpdel-current-playlist-replace songs)))) (global-set-key (kbd "C-c m r") #'my/mpdel-recent) (global-set-key (kbd "C-c m R") #'my/mpdel-recent-replace))