diff options
Diffstat (limited to '.emacs')
| -rwxr-xr-x | .emacs | 647 |
1 files changed, 647 insertions, 0 deletions
@@ -0,0 +1,647 @@ +;;; init.el --- Personal Emacs config -*- lexical-binding: t; -*- + +;; ------------------------- +;; Basic UI +;; ------------------------- +(setq inhibit-startup-message t + confirm-kill-processes nil + ring-bell-function 'ignore) + +(tool-bar-mode -1) +(menu-bar-mode -1) +(scroll-bar-mode -1) +(setq backup-directory-alist `(("." . ,(expand-file-name "/tmp/backups/" user-emacs-directory)))) + +(setq auto-save-hook nil) +(push '(fullscreen . maximized) default-frame-alist) + +(setq-default + indent-tabs-mode t + tab-width 8 + window-combination-resize t + history-delete-duplicates t) + +;; ------------------------- +;; Package setup +;; ------------------------- +(require 'package) + +(setq package-archives + '(("melpa" . "https://melpa.org/packages/") + ("gnu" . "https://elpa.gnu.org/packages/") + ("nongnu" . "https://elpa.nongnu.org/nongnu/"))) + +(package-initialize) + +(unless package-archive-contents + (package-refresh-contents)) + +(unless (package-installed-p 'use-package) + (package-install 'use-package)) + +(require 'use-package) +(setq use-package-always-ensure t) + +;; ------------------------- +;; Theme +;; ------------------------- +(use-package gruvbox-theme) +(load-theme 'gruvbox t) + +;; ------------------------- +;; Completion stack +;; ------------------------- +(use-package vertico + :init + (vertico-mode)) + +(use-package orderless + :init + (setq completion-styles '(orderless basic))) + +(use-package marginalia + :init + (marginalia-mode)) + +(use-package consult + :bind (("C-x b" . consult-buffer) + ("C-x p f" . project-find-file))) + +(use-package savehist + :ensure nil + :init + (savehist-mode)) + +(use-package embark + :bind (("C-." . embark-act) + ("C-;" . embark-dwim))) + +(use-package embark-consult) + +(use-package corfu + :init + (global-corfu-mode) + :custom + (corfu-auto t) + (corfu-cycle t) + (corfu-auto-delay 0.1) + (corfu-auto-prefix 1) + :bind (:map corfu-map + ("C-t" . corfu-next) + ("C-n" . corfu-previous) + ("<escape>" . corfu-quit))) + +(use-package cape + :init + (add-to-list 'completion-at-point-functions #'cape-file) + (add-to-list 'completion-at-point-functions #'cape-dabbrev)) + +;; ------------------------- +;; Icons + UI polish +;; ------------------------- +(use-package nerd-icons) + +(use-package nerd-icons-completion + :after marginalia + :init + (nerd-icons-completion-mode) + (add-hook 'marginalia-mode-hook + #'nerd-icons-completion-marginalia-setup)) + +;; ------------------------- +;; File browser: Dirvish +;; ------------------------- +(use-package dirvish + :init + ;; Makes ordinary `dired' buffers use Dirvish automatically. + (dirvish-override-dired-mode) + :custom + (dirvish-quick-access-entries + '(("h" "~/" "Home") + ("d" "~/Downloads/" "Downloads") + ("c" "~/Code/" "Code") + ("e" "~/.emacs.d/" "Emacs"))) + (dirvish-mode-line-format + '(:left (sort symlink) :right (omit yank index))) + (dirvish-attributes + '(nerd-icons file-time file-size collapse subtree-state vc-state git-msg)) + (dirvish-side-attributes + '(vc-state file-size nerd-icons collapse)) + :config + ;; Preview images, GIFs, PDFs, archives, audio, and video thumbnails. + ;; + ;; Recommended system packages: + ;; Arch: sudo pacman -S ffmpegthumbnailer libvips poppler mpv imv + ;; Debian/Ubuntu: sudo apt install ffmpegthumbnailer libvips-tools poppler-utils mpv imv + ;; + ;; Notes: + ;; - Images preview automatically at point. + ;; - Videos preview as thumbnails; open them in mpv for playback. + (setq dirvish-preview-dispatchers + '(image gif video audio epub archive font pdf fallback)) + + ;; Open media asynchronously so Emacs/EXWM does not block while mpv/imv is open. + (defun my/open-file-externally (&optional file) + "Open FILE asynchronously using a suitable external program." + (interactive) + (let* ((file (or file (dired-get-file-for-visit))) + (lower (downcase file)) + (program + (cond + ((string-match-p "\\.\\(mp4\\|mkv\\|webm\\|mov\\|avi\\)\\'" lower) "mpv") + ((string-match-p "\\.\\(png\\|jpg\\|jpeg\\|gif\\|webp\\|svg\\)\\'" lower) "imv") + (t "xdg-open")))) + (start-process "open-file-externally" nil program file))) + + ;; Dired's `!' runs a foreground shell command unless you add `&'. + ;; This keeps `o' as a non-blocking media opener. + + :bind + (("C-c f d" . dirvish) + ("C-c f j" . dirvish-side) + :map dirvish-mode-map + ("q" . quit-window) + ("TAB" . dirvish-subtree-toggle) + ("M-t" . dired-next-line) + ("M-n" . dired-previous-line) + ("RET" . dired-find-file) + ("o" . my/open-file-externally) + ("SPC" . dirvish-show-history) + ("a" . dirvish-quick-access) + ("f" . dirvish-file-info-menu) + ("y" . dirvish-yank-menu) + ("N" . dirvish-narrow) + ("^" . dired-up-directory))) + +;; ------------------------- +;; Which-key +;; ------------------------- +(use-package which-key + :config + (which-key-mode) + (setq which-key-idle-delay 0.5)) + +;; ------------------------- +;; Git +;; ------------------------- +(use-package magit + :bind (("C-x g" . magit-status))) + +(use-package diff-hl + :init + (global-diff-hl-mode) + (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh) + (add-hook 'prog-mode-hook #'diff-hl-mode)) + +;; ------------------------- +;; Project +;; ------------------------- +(use-package project + :ensure nil) + +;; ------------------------- +;; Browser / URL handling +;; ------------------------- +(defvar my/browser "firefox") + +(defun my/open-browser () + "Open the external browser." + (interactive) + (start-process-shell-command "browser" nil my/browser)) + +(defun my/browse-url (url &optional _new-window) + "Open URL in the external browser." + (start-process-shell-command + "browser-url" nil + (format "%s %s" my/browser (shell-quote-argument url)))) + +(setq browse-url-browser-function #'my/browse-url) + +;; ------------------------- +;; Dvorak-friendly movement fallbacks +;; +;; C-s and C-r are left as normal Emacs search keys: +;; C-s = isearch-forward +;; C-r = isearch-backward +;; +;; Dvorak movement is available on M-h/M-t/M-n/M-s: +;; M-h = left +;; M-t = down +;; M-n = up +;; M-s = right +;; ------------------------- +(global-set-key (kbd "C-s") #'isearch-forward) +(global-set-key (kbd "C-r") #'isearch-backward) + +(global-set-key (kbd "M-h") #'backward-char) +(global-set-key (kbd "M-t") #'next-line) +(global-set-key (kbd "M-n") #'previous-line) +(global-set-key (kbd "M-s") #'forward-char) + +(use-package avy + :bind (("C-:" . avy-goto-char) + ("C-'" . avy-goto-word-1))) + +(use-package repeat + :ensure nil + :init + (repeat-mode)) + +;; ------------------------- +;; Typst +;; ------------------------- +;; Requires system binaries: +;; typst - Typst compiler +;; tinymist - Typst language server +;; +;; Arch/Artix: +;; paru -S typst tinymist +;; +;; Then in Emacs: +;; M-x treesit-install-language-grammar RET typst RET + +(use-package treesit + :ensure nil + :init + (add-to-list 'treesit-language-source-alist + '(typst "https://github.com/uben0/tree-sitter-typst"))) + +(use-package typst-ts-mode + :mode ("\\.typ\\'" . typst-ts-mode) + :hook ((typst-ts-mode . eglot-ensure) + (typst-ts-mode . visual-line-mode)) + :custom + (typst-ts-mode-watch-options "--open") + :bind (:map typst-ts-mode-map + ("C-c C-c" . typst-ts-compile) + ("C-c C-w" . typst-ts-watch-mode) + ("C-c C-p" . typst-ts-preview))) +;; ------------------------- +;; Leader-like keybindings using C-c +;; ------------------------- + +;; GPT +(global-set-key (kbd "C-c a a") #'gptel) +(global-set-key (kbd "C-c a s") #'gptel-send) +(global-set-key (kbd "C-c a r") #'gptel-rewrite) + +;; Buffers +(global-set-key (kbd "C-c b b") #'consult-buffer) +(global-set-key (kbd "C-c b d") #'kill-current-buffer) +(global-set-key (kbd "C-c b n") #'next-buffer) +(global-set-key (kbd "C-c b p") #'previous-buffer) +(global-set-key (kbd "C-c b s") #'save-buffer) + +;; Files +(global-set-key (kbd "C-c f f") #'find-file) +(global-set-key (kbd "C-c f d") #'dirvish) +(global-set-key (kbd "C-c f j") #'dirvish-side) +(global-set-key (kbd "C-c f r") #'consult-recent-file) +(global-set-key (kbd "C-c f s") #'save-buffer) + +;; Open external browser +(global-set-key (kbd "C-c o b") #'my/open-browser) + +;; Windows — h/t/n/s mirrors your Dvorak movement +(global-set-key (kbd "C-c w h") #'windmove-left) +(global-set-key (kbd "C-c w t") #'windmove-down) +(global-set-key (kbd "C-c w n") #'windmove-up) +(global-set-key (kbd "C-c w s") #'windmove-right) +(global-set-key (kbd "C-c w d") #'delete-window) +(global-set-key (kbd "C-c w v") #'split-window-right) +(global-set-key (kbd "C-c w -") #'split-window-below) +(global-set-key (kbd "C-c w H") #'shrink-window-horizontally) +(global-set-key (kbd "C-c w S") #'enlarge-window-horizontally) +(global-set-key (kbd "C-c w N") #'enlarge-window) +(global-set-key (kbd "C-c w T") #'shrink-window) + +;; Project +(global-set-key (kbd "C-c p p") #'project-switch-project) +(global-set-key (kbd "C-c p f") #'project-find-file) +(global-set-key (kbd "C-c p g") #'consult-ripgrep) +(global-set-key (kbd "C-c p b") #'consult-project-buffer) + +;; Git +(global-set-key (kbd "C-c g g") #'magit-status) +(global-set-key (kbd "C-c g b") #'magit-blame) +(global-set-key (kbd "C-c g l") #'magit-log-current) +(global-set-key (kbd "C-c g d") #'magit-diff-buffer-file) + +;; LSP / Code +(global-set-key (kbd "C-c c a") #'eglot-code-actions) +(global-set-key (kbd "C-c c r") #'eglot-rename) +(global-set-key (kbd "C-c c f") #'eglot-format-buffer) +(global-set-key (kbd "C-c c d") #'xref-find-definitions) +(global-set-key (kbd "C-c c D") #'xref-find-references) +(global-set-key (kbd "C-c c i") #'consult-imenu) + +;; Typst +(global-set-key (kbd "C-c y c") #'typst-ts-compile) +(global-set-key (kbd "C-c y w") #'typst-ts-watch-mode) +(global-set-key (kbd "C-c y p") #'typst-ts-preview) + +;; Errors +(global-set-key (kbd "C-c e n") #'flymake-goto-next-error) +(global-set-key (kbd "C-c e p") #'flymake-goto-prev-error) +(global-set-key (kbd "C-c e l") #'flymake-show-buffer-diagnostics) + +;; Search +(global-set-key (kbd "C-c s s") #'consult-line) +(global-set-key (kbd "C-c s g") #'consult-ripgrep) +(global-set-key (kbd "C-c s i") #'consult-imenu) + +;; Toggles +(global-set-key (kbd "C-c t s") #'flyspell-mode) +(global-set-key (kbd "C-c t w") #'whitespace-mode) + +;; Spell language +;; C-c l is already used by xkbmap-switcher, so use C-c d for dictionaries. +(global-set-key (kbd "C-c d e") #'my/ispell-en) +(global-set-key (kbd "C-c d c") #'my/ispell-cs) + +;; Quit +(global-set-key (kbd "C-c q q") #'save-buffers-kill-emacs) + +;; ------------------------- +;; Eglot (LSP) +;; ------------------------- +(use-package eglot + :ensure nil + :hook ((c-mode . eglot-ensure) + (c++-mode . eglot-ensure) + (c-ts-mode . eglot-ensure) + (c++-ts-mode . eglot-ensure) + (nasm-mode . eglot-ensure) + (python-mode . eglot-ensure) + (python-ts-mode . eglot-ensure) + (typst-ts-mode . eglot-ensure)) + :custom + (eglot-autoshutdown t) + (eglot-confirm-server-initiated-edits nil) + :config + (defun my/eglot-format-on-save () + (add-hook 'before-save-hook #'eglot-format-buffer nil t)) + + (add-hook 'c-mode-hook #'my/eglot-format-on-save) + (add-hook 'c++-mode-hook #'my/eglot-format-on-save) + (add-hook 'c-ts-mode-hook #'my/eglot-format-on-save) + (add-hook 'c++-ts-mode-hook #'my/eglot-format-on-save) + + (setq c-ts-mode-indent-offset 8 + c-ts-mode-indent-style 'bsd) + + (add-to-list 'eglot-server-programs + '((c-mode c++-mode c-ts-mode c++-ts-mode) + . ("clangd" + "--background-index" + "--clang-tidy" + "--completion-style=detailed" + "--header-insertion=never" + "--fallback-style=none"))) + + (add-to-list 'eglot-server-programs + '(python-mode . ("pylsp"))) + + (add-to-list 'eglot-server-programs + '(typst-ts-mode . ("tinymist")))) + +;; ------------------------- +;; Flymake (used by Eglot) +;; ------------------------- +(use-package flymake + :ensure nil + :custom + (flymake-no-changes-timeout 1.0) + :bind (:map flymake-mode-map + ("M-n" . flymake-goto-next-error) + ("M-p" . flymake-goto-prev-error))) + +;; ------------------------- +;; NASM mode +;; ------------------------- +(use-package nasm-mode + :hook (asm-mode . nasm-mode)) + +;; ------------------------- +;; EXWM +;; ------------------------- +(use-package exwm + :config + (setq exwm-workspace-number 5) + + (add-hook 'exwm-update-title-hook + (lambda () + (exwm-workspace-rename-buffer + (format "%s: %s" exwm-class-name exwm-title)))) + + (setq mouse-autoselect-window t + focus-follows-mouse t) + + (setq exwm-layout-show-all-buffers t + exwm-workspace-show-all-buffers t) + + (defvar my/terminal "kitty") + + (setq exwm-input-global-keys + `((,(kbd "s-r") . exwm-reset) + + (,(kbd "s-<return>") + . (lambda () + (interactive) + (start-process-shell-command "term" nil my/terminal))) + + (,(kbd "s-b") . my/open-browser) + + (,(kbd "s-d") . dirvish) + + (,(kbd "s-x") + . (lambda () + (interactive) + (start-process-shell-command "lock" nil "betterscreenlock -l"))) + + (,(kbd "s-q") . kill-buffer) + (,(kbd "s-f") . exwm-floating-toggle-floating) + + ;; Window resize — uppercase H/T/N/S mirrors movement + (,(kbd "s-H") . shrink-window-horizontally) + (,(kbd "s-S") . enlarge-window-horizontally) + (,(kbd "s-N") . enlarge-window) + (,(kbd "s-T") . shrink-window) + + ;; Window focus — lowercase mirrors movement + (,(kbd "s-h") . windmove-left) + (,(kbd "s-s") . windmove-right) + (,(kbd "s-n") . windmove-up) + (,(kbd "s-t") . windmove-down) + + ,@(mapcar + (lambda (i) + `(,(kbd (format "s-%d" i)) + . (lambda () + (interactive) + (exwm-workspace-switch ,i)))) + (number-sequence 0 4)) + + (,(kbd "s-<tab>") . exwm-workspace-switch-next) + (,(kbd "s-S-<tab>") . exwm-workspace-switch-previous) + (,(kbd "s-w") . exwm-workspace-move-window) + + (,(kbd "s-&") + . (lambda (command) + (interactive (list (read-shell-command "$ "))) + (start-process-shell-command command nil command))))) + + (setq exwm-manage-configurations + '(((string-match-p "dialog" exwm-class-name) floating t) + ((string-match-p "confirm" exwm-title) floating t))) + + ;; NOTE: Your monitor names differ here from the xrandr command below. + ;; Keep whichever names are correct on your machine. + (setq exwm-randr-workspace-monitor-plist + '(0 "DP-4" + 1 "HDMI-0")) + + (add-hook 'exwm-randr-screen-change-hook + (lambda () + (start-process-shell-command + "xrandr" nil + "xrandr --output DP-3 --primary --auto \ + --output HDMI-1 --auto --left-of DP-3"))) + + (exwm-randr-mode 1) + + (defun my/exwm-init () + (start-process-shell-command "pipewire" nil "pipewire") + (start-process-shell-command "pipewire-pulse" nil "pipewire-pulse") + (start-process-shell-command "wireplumber" nil "wireplumber")) + + (add-hook 'exwm-init-hook #'my/exwm-init) + + (when (and (getenv "DISPLAY") + (not (getenv "EXWM_SKIP"))) + (exwm-enable))) + +;; ------------------------- +;; Audio keys +;; ------------------------- +(global-set-key (kbd "<XF86AudioRaiseVolume>") + (lambda () + (interactive) + (start-process-shell-command + "vol-up" nil "amixer set Master 5%+"))) + +(global-set-key (kbd "<XF86AudioLowerVolume>") + (lambda () + (interactive) + (start-process-shell-command + "vol-down" nil "amixer set Master 5%-"))) + +(global-set-key (kbd "<XF86AudioMute>") + (lambda () + (interactive) + (start-process-shell-command + "mute" nil "amixer set Master toggle"))) + +;; ------------------------- +;; Navidrome +;; ------------------------- +(use-package naviel + :load-path "~/Code/naviel/" + :config + (setq naviel-url "http://navi.cdatgoose.org") + (setq naviel-eq-backend 'lavfi)) + +(use-package tasker + :load-path "~/Code/tasker" + :commands (tasker-list tasker-new tasker-open tasker-add-code-link) + :bind (:map tasker-list-mode-map + ("q" . quit-window))) + + + + +;; ------------------------- +;; EAF +;; ------------------------- +;; EAF is kept available, but it no longer overrides `browse-url'. +;; Use `eaf-open-browser' manually when you specifically want EAF. +(use-package eaf + :load-path "~/.emacs.d/site-lisp/emacs-application-framework" + :custom + (eaf-browser-continue-where-left-off t) + (eaf-browser-enable-adblocker t) + :config + (defalias 'browse-web #'eaf-open-browser)) + +;; ------------------------- +;; XKB switcher (Dvorak-friendly) +;; ------------------------- +(use-package xkbmap-switcher + :load-path "~/Code/keyswtch/" + :config + (global-set-key (kbd "C-c l") #'xkbmap-switcher-cycle) + (global-set-key (kbd "C-c L") #'xkbmap-switcher-set)) + +;; ------------------------- +;; Spell checking (Hunspell EN + CZ) +;; ------------------------- +(use-package flyspell + :ensure nil + :hook ((text-mode . flyspell-mode) + (prog-mode . flyspell-prog-mode)) + :init + (setq ispell-program-name "hunspell") + (setq ispell-dictionary "en_US") + (setq ispell-local-dictionary-alist + '(("en_US" + "[A-Za-z]" "[^A-Za-z]" "[']" nil + ("-d" "en_US") nil utf-8) + ("cs_CZ" + "[A-Za-zÁ-ž]" "[^A-Za-zÁ-ž]" "[']" nil + ("-d" "cs_CZ") nil utf-8)))) + +;; ------------------------- +;; Quick language switching +;; ------------------------- +(defun my/ispell-en () + "Switch spell check to English." + (interactive) + (ispell-change-dictionary "en_US") + (message "Switched to English (en_US)")) + +(defun my/ispell-cs () + "Switch spell check to Czech." + (interactive) + (ispell-change-dictionary "cs_CZ") + (message "Switched to Czech (cs_CZ)")) + +(global-set-key (kbd "C-c s e") #'my/ispell-en) +(global-set-key (kbd "C-c s c") #'my/ispell-cs) + +;; ------------------------- +;; Custom +;; ------------------------- +(custom-set-variables + ;; custom-set-variables 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. + '(org-safe-remote-resources + '("\\`/ssh:apps@5\\.189\\.176\\.18:/home/apps/portfolio/blog/org/thoughts\\.org\\'" + "\\`/ssh:apps@cdatgoose\\.org:/home/apps/portfolio/blog/org/thoughts\\.org\\'" + "\\`https://fniessen\\.github\\.io/org-html-themes/org/html-theme-readtheorg\\.setup\\'" + "\\`https://fniessen\\.github\\.io/org-html-themes/org/html-theme-bigblow\\.setup\\'")) + '(package-selected-packages + '(avy cape corfu diff-hl dirvish embark-consult empv exwm gptel + gruvbox-theme ibrowse magit marginalia nasm-mode naviel + nerd-icons-completion orderless tidal typst-ts-mode vertico + websocket xkbmap-switcher xkcd))) + +(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. + ) + +;;; init.el ends here |
