;;; -*- lexical-binding: t; -*- ;; Rebind C-x? (global-set-key (kbd "C-t") 'ctrl-x-map) (define-key global-map (kbd "C-t") ctl-x-map) ;; (define-key key-translation-map "\C-t" "\C-t") ;; (define-key key-translation-map "\C-x" "\C-t") ;; For editing grep buffers. (use-package wgrep) (use-package deadgrep :bind ("M-s g" . deadgrep)) ;; For recent files. (setq recentf-max-saved-items 1000) (setq recentf-auto-cleanup 'never) (defun kj/recentf-keep (file) (cond ((file-remote-p file) t) ((file-readable-p file)))) (setq recentf-keep '(kj/recentf-keep)) (setq recentf-save-file (expand-file-name "emacs-recentf" kj/cache-dir)) (recentf-mode 1) (run-at-time nil (* 5 60) 'recentf-save-list) (setq global-mark-ring-max 500 mark-ring-max 16) (use-package which-key :init (which-key-mode)) (use-package seq) (use-package transient :config (setq transient-levels-file (expand-file-name "transient/levels.el" kj/cache-dir) transient-values-file (expand-file-name "transient/values.el" kj/cache-dir) transient-history-file (expand-file-name "transient/history.el" kj/cache-dir))) (defconst kj/git-directory (expand-file-name "~/git")) (defun kj/read-repository () (let ((repos (thread-last (directory-files "/ssh:dragon.orbekk.com:/storage/projects") (seq-filter (lambda (f) (not (string-match (rx bol (or "." "..") eol) f))))))) (thread-last (completing-read "Repository: " repos) (concat "ssh://dragon.orbekk.com:/storage/projects/")))) (use-package magit :bind (:map magit-repolist-mode-map ("c" . magit-clone) ("i" . magit-init) ("C" . kj/clone-project)) :custom (magit-repository-directories `((,kj/git-directory . 1)) "My git repositories") (magit-repolist-columns '(("Name" 25 magit-repolist-column-ident nil) ("Version" 25 magit-repolist-column-version ((:sort magit-repolist-version<))) ("BU" 3 magit-repolist-column-unpushed-to-upstream ((:right-align t) (:sort <))) ("F" 3 magit-repolist-column-flag ((:right-align t) (:sort <))) ("Path" 99 magit-repolist-column-path nil))) :config (defun kj/clone-project () (interactive) (let ((magit-clone-set-remote.pushDefault t)) (magit-clone-regular (kj/read-repository) kj/git-directory nil)))) (use-package mixed-pitch :hook (text-mode . mixed-pitch-mode) (org-mode . mixed-pitch-mode) ) (when (string= (system-name) "fedora") (setenv "SSH_AUTH_SOCK" (string-trim (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setq kj/font-height (cond ((string= (system-name) "fedora") 100) (t 150))) (custom-set-faces `(variable-pitch ((t (:family "Noto Serif" :height ,kj/font-height)))) `(default ((t (:family "Iosevka" :height ,kj/font-height))))) ;; (unless (equal system-name "minideck") ;; (set-face-attribute 'fixed-pitch nil :font "Iosevka" :height 150) ;; (set-face-attribute 'variable-pitch nil :font "Noto Serif")) ;;; Compilation settings (use-package compile :elpaca nil :config (setq compilation-ask-about-save nil) (setq compilation-scroll-output 'first-error) ;; Allow longer output in compilation buffer. (add-to-list 'compilation-filter-hook #'comint-truncate-buffer) (setq comint-buffer-maximum-size 10000)) (use-package ob-async) (use-package direnv :config (setq direnv-always-show-summary nil) (direnv-mode)) (use-package projectile :ensure t :defer 1 :init (setq projectile-known-projects-file (expand-file-name "projectile-bookmarks.eld" kj/cache-dir)) (projectile-mode 1) ;; Don't run on remote directories. (advice-add 'editorconfig-apply :before-until 'kj/default-directory-remote-p) :bind (:map projectile-mode-map ("C-x p" . projectile-command-map))) (use-package emacs :elpaca nil :config (setq ediff-diff-options "-w" ; turn off whitespace checking ediff-split-window-function #'split-window-horizontally ediff-window-setup-function #'ediff-setup-windows-plain)) (use-package anzu :config (global-anzu-mode +1) (global-set-key [remap query-replace] 'anzu-query-replace) (global-set-key [remap query-replace-regexp] 'anzu-query-replace-regexp)) (defun meow-setup () (setq meow-replace-state-name-list '((normal . "N") (motion . "M") (keypad . "🖩") (insert . "I") (beacon . "📻"))) (setq meow-cheatsheet-layout meow-cheatsheet-layout-dvorak) (meow-leader-define-key '("1" . meow-digit-argument) '("2" . meow-digit-argument) '("3" . meow-digit-argument) '("4" . meow-digit-argument) '("5" . meow-digit-argument) '("6" . meow-digit-argument) '("7" . meow-digit-argument) '("8" . meow-digit-argument) '("9" . meow-digit-argument) '("0" . meow-digit-argument) '("/" . meow-keypad-describe-key) '("?" . meow-cheatsheet) '("b" . consult-buffer) ;;'("p" . projectile-command-map) ) (meow-motion-overwrite-define-key ;; custom keybinding for motion state '("" . ignore)) (meow-normal-define-key '("0" . meow-expand-0) '("9" . meow-expand-9) '("8" . meow-expand-8) '("7" . meow-expand-7) '("6" . meow-expand-6) '("5" . meow-expand-5) '("4" . meow-expand-4) '("3" . meow-expand-3) '("2" . meow-expand-2) '("1" . meow-expand-1) '("-" . negative-argument) '(";" . meow-reverse) '("," . meow-inner-of-thing) '("." . meow-bounds-of-thing) '("@" . er/expand-region) '("<" . meow-beginning-of-thing) '(">" . meow-end-of-thing) '("a" . meow-append) '("A" . meow-open-below) '("b" . meow-back-word) '("B" . meow-back-symbol) '("c" . meow-change) '("d" . meow-delete) '("D" . meow-backward-delete) '("e" . meow-line) '("E" . meow-goto-line) '("f" . meow-find) '("g" . meow-cancel-selection) '("G" . meow-grab) '("h" . meow-left) '("H" . meow-left-expand) '("i" . meow-insert) '("I" . meow-open-above) '("j" . meow-join) '("k" . meow-kill) '("l" . meow-till) '("m" . meow-mark-word) '("M" . meow-mark-symbol) '("n" . meow-next) '("N" . meow-next-expand) '("o" . meow-block) '("O" . meow-to-block) '("p" . meow-prev) '("P" . meow-prev-expand) '("q" . meow-quit) '("Q" . meow-goto-line) '("r" . meow-replace) '("R" . meow-swap-grab) '("s" . meow-search) '("/" . isearch-forward) '("?" . isearch-backward) '("t" . meow-right) '("T" . meow-right-expand) '("u" . meow-undo) '("U" . meow-undo-in-selection) '("v" . meow-visit) '("w" . meow-next-word) '("W" . meow-next-symbol) '("x" . meow-save) '("X" . meow-sync-grab) '("y" . meow-yank) '("z" . meow-pop-selection) '("P" . (lambda () (interactive) (forward-line -10))) '("N" . (lambda () (interactive) (forward-line 10))) '("'" . repeat) '("" . ignore)) (add-hook 'git-commit-mode-hook #'meow-insert)) ;; (use-package meow ;; :bind ;; :config ;; (meow-setup) ;; (meow-global-mode 1) ;; ;; (meow-setup-indicator) ; Not needed with doom-modeline. ;; (setq meow-use-clipboard t) ;; ;; (add-to-list 'meow-keypad-start-keys '(?t . ?x)) ;; ;; (add-to-list 'meow-keypad-start-keys '(?x . ?t)) ;; (define-key meow-insert-state-keymap "\C-[" #'meow-insert-exit) ;; ) (use-package emacs :elpaca nil :hook (dired-mode . dired-omit-mode) :init (setq dired-dwim-target t ; suggest a target for moving/copying intelligently dired-hide-details-hide-symlink-targets nil ;; don't prompt to revert, just do it dired-auto-revert-buffer #'dired-buffer-stale-p ;; Always copy/delete recursively dired-recursive-copies 'always dired-recursive-deletes 'top ;; Ask whether destination dirs should get created when copying/removing files. dired-create-destination-dirs 'ask)) ;; (use-package dirvish ;; :init ;; (setq dirvish-hide-details nil) ;; (dirvish-override-dired-mode)) (use-package diredfl :hook (dired-mode . diredfl-mode)) ;; Tramp settings (setq tramp-use-ssh-controlmaster-options nil tramp-default-method "ssh") ;; Need 'eval-after-load' of 'tramp-sh' here because nixos has a ;; startup file that messes with this variable. (with-eval-after-load 'tramp-sh (add-to-list 'tramp-remote-path 'tramp-own-remote-path)) ;; Calc settings. (setq calc-make-windows-dedicated t calc-kill-line-numbering nil calc-display-trail nil) (setq math-additional-units '((ZiB "1024 * EiB" "Zebibyte") (EiB "1024 * PiB" "Exbibyte") (PiB "1024 * TiB" "Pebibyte") (TiB "1024 * GiB" "Tebibyte") (GiB "1024 * MiB" "Gibibyte") (MiB "1024 * KiB" "Mebibyte") (KiB "1024 * B" "Kibibyte") (B nil "Byte") (Zib "1024 * ZiB" "Zebibit") (Eib "1024 * PiB" "Exbibit") (Pib "1024 * TiB" "Pebibit") (Tib "1024 * GiB" "Tebibit") (Gib "1024 * Mib" "Gibibit") (Mib "1024 * Kib" "Mebibit") (Kib "1024 * b" "Kibibit") (b "B / 8" "Bit"))) ;; Low menu delay. (setq which-key-idle-delay .5) ;; Replace values in an alist from a list of replacements. ;; ;; Example: ;; (kj/assq-replace '((:a . 1)) '((:a . 2))) (defun kj/assq-replace (replacements alist) (let ((replace1 (lambda (aelem alist) (cons aelem (assq-delete-all (car aelem) alist))))) (if replacements (kj/assq-replace (cdr replacements) (funcall replace1 (car replacements) alist)) alist))) (use-package idle-highlight-mode :config (setq idle-highlight-idle-time 0.2) (set-face-attribute 'idle-highlight nil :inherit 'underline) :hook ((prog-mode text-mode) . idle-highlight-mode)) (defun kj/smartparens-config () (require 'smartparens-config) (bind-keys :map smartparens-mode-map ("C-M-a" . sp-beginning-of-sexp) ("C-M-e" . sp-end-of-sexp) ("C-" . sp-down-sexp) ("C-" . sp-up-sexp) ("M-" . sp-backward-down-sexp) ("M-" . sp-backward-up-sexp) ("C-M-f" . sp-forward-sexp) ("C-M-b" . sp-backward-sexp) ("C-M-n" . sp-next-sexp) ("C-M-p" . sp-previous-sexp) ("C-S-f" . sp-forward-symbol) ("C-S-b" . sp-backward-symbol) ("C-" . sp-forward-slurp-sexp) ("M-" . sp-forward-barf-sexp) ("C-" . sp-backward-slurp-sexp) ("M-" . sp-backward-barf-sexp) ("C-M-t" . sp-transpose-sexp) ("C-M-k" . sp-kill-sexp) ("C-k" . sp-kill-hybrid-sexp) ("C-M-w" . sp-copy-sexp) ("C-M-d" . delete-sexp) ("M-" . backward-kill-word) ("C-" . sp-backward-kill-word) ([remap sp-backward-kill-word] . backward-kill-word) ("M-[" . sp-backward-unwrap-sexp) ("M-]" . sp-unwrap-sexp) ("C-x C-t" . sp-transpose-hybrid-sexp) ("C-c (" . wrap-with-parens) ("C-c [" . wrap-with-brackets) ("C-c {" . wrap-with-braces) ("C-c '" . wrap-with-single-quotes) ("C-c \"" . wrap-with-double-quotes) ("C-c _" . wrap-with-underscores) ("C-c `" . wrap-with-back-quotes) ("M-k" . sp-raise-sexp) ("M-S" . sp-split-sexp) ("M-I" . sp-splice-sexp)) ) (use-package smartparens :disabled :init (kj/smartparens-config) :hook ((prog-mode lisp-mode) . smartparens-strict-mode) ) ;; (use-package paredit ;; :hook ((lisp-mode emacs-lisp-mode) . paredit-mode) ;; :bind (:map paredit-mode-map ;; ("M-k" . paredit-raise-sexp) ;; ("M-I" . paredit-splice-sexp)) ;; :hook (paredit-mode ;; . (lambda () ;; (unbind-key "M-r" paredit-mode-map) ;; (unbind-key "M-s" paredit-mode-map)))) (define-key isearch-mode-map (kbd "C-RET") #'isearch-exit-other-end) ;; Why does C-RET not always work? (define-key isearch-mode-map (kbd "C-") #'isearch-exit-other-end) (defun isearch-exit-other-end () "Exit isearch, at the opposite end of the string." (interactive) (isearch-exit) (goto-char isearch-other-end)) (define-key isearch-mode-map (kbd "M-z") #'kj/isearch-zap) (defun kj/isearch-zap () "Zap to beginning of search" (interactive) (isearch-exit) (goto-char isearch-other-end) (kill-region (region-beginning) (region-end))) (setq case-fold-search t case-replace t char-fold-symmetric t isearch-lax-whitespace t search-default-mode 'char-fold-to-regexp) (use-package char-fold :elpaca nil :config (setopt char-fold-include (cons '(?- "_" "-") char-fold-include))) (use-package expand-region :bind (("C-@" . #'er/expand-region))) (use-package ace-mc :bind (:map kj/leader-map ("h" . ace-mc-add-multiple-cursors) ("M-h" . ace-mc-add-single-cursor))) ;; Disable VC over tramp. ;;(setq-default vc-handled-backends nil) (defun kj/vc-off-if-remote () (if (file-remote-p (buffer-file-name)) (setq-local vc-handled-backends nil))) (add-hook 'find-file-hook 'kj/vc-off-if-remote) (use-package selected :demand t :diminish selected-minor-mode :bind (:map selected-keymap ("[" . align-code) ("f" . fill-region) ("U" . unfill-region) ("d" . downcase-region) ("r" . reverse-region) ("S" . sort-lines)) :config (selected-global-mode 1)) (setq kj/some-lines 6) (defun kj/forward-some-lines () (interactive) (forward-line kj/some-lines)) (defun kj/previous-some-lines () (interactive) (previous-line kj/some-lines)) ;; Repeat commands (e.g., C-x o o o) (use-package repeat-help :custom (repeat-help-key "?" "The default `C-h' tends to interfere.") :init (repeat-help-mode 1)) (repeat-mode 1) ;; Disable the built-in repeat-mode hinting ;;(setq repeat-echo-function #'ignore) ;; Spawn or hide a which-key popup ;; (advice-add 'repeat-post-hook :after ;; (defun repeat-help--which-key-popup () ;; (if-let ((cmd (or this-command real-this-command)) ;; (keymap (or repeat-map ;; (repeat--command-property 'repeat-map)))) ;; (run-at-time ;; 0 nil ;; (lambda () ;; (which-key--create-buffer-and-show ;; nil (symbol-value keymap)))) ;; (which-key--hide-popup)))) ;; Calendar settings. (setq calendar-week-start-day 1) ;; Show calendar week numbers. (copy-face font-lock-constant-face 'calendar-iso-week-face) (set-face-attribute 'calendar-iso-week-face nil :height 0.7) (setq calendar-intermonth-text '(propertize (format "%2d" (car (calendar-iso-from-absolute (calendar-absolute-from-gregorian (list month day year))))) 'font-lock-face 'calendar-iso-week-face)) ;; (setq ispell-local-dictionary "en_US") ;; (add-hook 'text-mode-hook flyspell-mode) ;; (add-hook 'prog-mode-hook flyspell-prog-mode) ;;; Email settings (setq message-kill-buffer-on-exit t) ;; After sending a message. (let ((google-config-file (expand-file-name "google.el" user-emacs-directory))) (when (file-exists-p google-config-file) (load-file google-config-file))) ;; Special symbols (use-package emacs :elpaca nil :defer 1 :config (defvar kj/iso-transl-char-map '( ;; fractions that emacs doesn't include ("1/3" . [?⅓]) ("1/5" . [?⅕]) ("1/6" . [?⅙]) ("1/7" . [?⅐]) ("1/8" . [?⅛]) ("1/9" . [?⅑]) ("1/10" . [?⅒]) ("2/3" . [?⅔]) ("2/5" . [?⅖]) ("3/5" . [?⅗]) ("3/8" . [?⅜]) ("4/5" . [?⅘]) ("5/6" . [?⅚]) ("5/8" . [?⅝]) ("7/8" . [?⅞]) ;; add the emojis I commonly use (":c" . [?✅]) (":u" . [?👍]) (":d" . [?👎]) (":w" . [?🖐]) ;; card suits ("c" . [?♣]) ("h" . [?♥]) ("d" . [?♦]) ("s" . [?♠]))) (require 'iso-transl) (iso-transl-define-keys kj/iso-transl-char-map) :init (require 'iso-transl)) (use-package pcre2el) (use-package pdf-tools) (defvar kj/help-modes '(helpful-mode help-mode shortdoc-mode Man-mode woman-mode Info-mode)) (defun kj/buffer-help-p (buf act) "BUF is a help buffer, ignore ACT." (with-current-buffer buf (apply 'derived-mode-p kj/help-modes))) (add-to-list 'display-buffer-alist `(kj/buffer-help-p (display-buffer-reuse-window display-buffer-reuse-mode-window) (mode . ,kj/help-modes) (inhibit-same-window . nil))) (use-package helpful :bind (([remap describe-key] . 'helpful-key) ([remap describe-variable] . 'helpful-variable) ([remap describe-symbol] . 'helpful-symbol) ([remap describe-command] . 'helpful-command) ([remap describe-function] . 'helpful-callable))) ;; Spotify (use-package smudge :bind (("C-c a s" . 'smudge-command-map) ("C-c a s s" . 'kj/smudge-save-current-song)) :init (defvar kj/smudge-settings-file (expand-file-name ".smudge-credentials" kj/cache-dir)) (defvar kj/librespot-cache (expand-file-name "~/.cache/librespot")) (defun kj/smudge-configure (client-id client-secret) (interactive "sClient id: \nsClient secret: ") (with-temp-buffer (insert (pp `(client-id ,client-id client-secret ,client-secret))) (write-file kj/smudge-settings-file)) (unless (file-exists-p kj/librespot-cache) (async-shell-command (format "librespot -u orbekk -b 320 -C %s" kj/librespot-cache) "*librespot*"))) (defun kj/smudge-init () (interactive) (unless (and (file-exists-p kj/smudge-settings-file) (file-exists-p kj/librespot-cache)) (user-error "%s" (substitute-command-keys "Press \\[kj/smudge-configure] to set up spotify"))) (with-temp-buffer (insert-file kj/smudge-settings-file) (goto-char (point-min)) (let ((credentials (read (current-buffer)))) (setq smudge-oauth2-client-secret (plist-get credentials 'client-secret)) (setq smudge-oauth2-client-id (plist-get credentials 'client-id)))) (start-process "librespot" "*librespot*" "librespot" "-C" kj/librespot-cache) ;; Give librespot some time to start, then select it. (run-at-time 2 nil (lambda () (smudge-api-device-list (lambda (json) (when-let* ((devices (gethash 'devices json)) (device (seq-find (lambda (d) (equal (smudge-device-get-device-name d) "Librespot")) devices))) (smudge-api-transfer-player (smudge-device-get-device-id device) (lambda (json) (setq smudge-selected-device-id (smudge-device-get-device-id device)) (message "Device selected")))))))) ;;; Needed to save the current song. (setq smudge-api-oauth2-scopes "playlist-read-private playlist-read-collaborative playlist-modify-public playlist-modify-private user-read-private user-read-playback-state user-modify-playback-state user-read-playback-state user-read-recently-played user-library-read user-library-modify") (defun kj/smudge-save-current-song () (interactive) (smudge-api-get-player-status (lambda (status) (when-let* ((status status) (track (gethash 'item status)) (id (gethash 'id track))) (smudge-api-call-async "PUT" (concat "/me/tracks?" (url-build-query-string `((ids ,id)) nil t)) nil (lambda (json) (message "Saved to library: %s - %s" (gethash 'name (car (gethash 'artists track))) (gethash 'name track)))))))))) (provide 'kj-init) ;; Local Variables: ;; eval: (outline-minor-mode 1) ;; End: