;;; config.el -*- lexical-binding: t; -*- ;; ============================================================ ;; Custom ;; ============================================================ (let ((custom-dir (expand-file-name "custom/" user-emacs-directory))) (when (file-directory-p custom-dir) (mapc #'load (directory-files custom-dir t "\\.el$")))) (add-to-list 'custom-theme-load-path (expand-file-name "themes/" user-emacs-directory)) ;; ============================================================ ;; Keybindings ;; ============================================================ (use-package general :config (general-def :keymaps 'global "C-S-c" #'my/system-copy "C-S-x" #'my/system-cut "C-S-v" #'my/system-paste "C-z" #'undo "C-S-z" #'undo-redo ) (general-def :keymaps 'override "C-k" #'avy-kill-whole-line "C-s C-f" #'avy-goto-char-timer "C-s C-s C-f" #'my/avy-char-region "C-s C-S-s" #'my/avy-line-region "C-s C-l" #'consult-line "C-s C-e" #'er/expand-region "C-x C-b" #'buffer-table "M-g g" #'avy-goto-line "C-r" #'recenter-top-bottom "C-s w" #'my/vterm-sync-here "C-b" #'scroll-down-command "C-" #'my/delete-word-backward) (general-define-key :keymaps 'global "C-w" nil) ;; If you want certain keys to ONLY work in Meow Normal state, use :states. ) ;; ============================================================ ;; Settings, UI ;; ============================================================ (setq kill-ring-max 1 select-enable-clipboard nil select-enable-primary nil save-interprogram-paste-before-kill nil xterm-extra-capabilities '(getSelection setSelection modifyOtherKeys) mouse-drag-copy-region nil truncate-lines nil display-line-numbers-type t cursor-type 'bar savehist-additional-variables '(kill-ring search-ring) recentf-max-saved-items 75 ) ;; Modes (global-hl-line-mode t ) (global-display-line-numbers-mode 1) (hl-line-mode 1) (savehist-mode 1) (global-clipetty-mode) ;; Fonts (set-face-attribute 'default nil :family "Fira Code" :height 120) (set-face-attribute 'variable-pitch nil :family "Noto Sans" :height 140) ;; Apply variable pitch to specific modes (dolist (hook '(markdown-mode-hook org-mode-hook json-mode-hook)) (add-hook hook (lambda () (buffer-face-set '(:family "Noto Sans" :height 140))))) (add-to-list 'auto-mode-alist '("\\.qd$" . markdown-mode)) (add-to-list 'term-file-aliases '("xterm-256" . "xterm")) ;; Recentf cleanup (with-eval-after-load 'recentf (run-at-time nil 600 #'recentf-cleanup) (setq recentf-exclude '(".*/$"))) ;; don't include dirs ;; ============================================================ ;; packages ;; ============================================================ (use-package emacs :ensure nil :custom (tab-always-indent 'complete) (TEXT-mode-ispell-word-completion nil) (read-extended-command-predicate #'command-completion-default-include-p) ) (use-package corfu :init (global-corfu-mode) (corfu-popupinfo-mode) :custom (corfu-auto t) (corfu-auto-delay 0.2) (corfu-auto-prefix 2) (corfu-popupinfo-delay '(0.5 . 0.3))) (use-package vertico :config :init (vertico-mode)) (use-package mason :config :init (mason-ensure)) (use-package marginalia :config :init (marginalia-mode)) (use-package orderless :custom (completion-styles '(orderless basic)) (completion-category-overrides '((file (styles partial-completion)))) (completion-pcm-leading-wildcard t)) ;; Emacs 31: partial-completion behaves like substring (use-package meow :init (setq meow--kbd-kill-region "C-S-x") :config (setq meow-use-clipboard nil) (meow-setup) (meow-global-mode 1) (setq meow-use-clipboard t)) (use-package avy :config (setq avy-timeout-seconds 0.25 avy-style 'at-full avy-keys (number-sequence ?a ?z))) (use-package treesit-auto :custom (treesit-auto-install 'prompt) :config (treesit-auto-add-to-auto-mode-alist 'all) (global-treesit-auto-mode) ) (use-package treesit-fold :commands (treesit-fold-close treesit-fold-close-all treesit-fold-open treesit-fold-toggle treesit-fold-open-all treesit-fold-mode global-treesit-fold-mode treesit-fold-open-recursively treesit-fold-line-comment-mode) :custom (treesit-fold-line-count-show t) (treesit-fold-line-count-format " ▼") :config (set-face-attribute 'treesit-fold-replacement-face nil :foreground "#808080" :box nil :weight 'bold)) ;; Systems and General Purpose (add-hook 'c-ts-mode-hook #'treesit-fold-mode) (add-hook 'c++-ts-mode-hook #'treesit-fold-mode) (add-hook 'java-ts-mode-hook #'treesit-fold-mode) (add-hook 'rust-ts-mode-hook #'treesit-fold-mode) (add-hook 'go-ts-mode-hook #'treesit-fold-mode) (add-hook 'ruby-ts-mode-hook #'treesit-fold-mode) ;; Web and Frontend (add-hook 'js-ts-mode-hook #'treesit-fold-mode) (add-hook 'typescript-ts-mode-hook #'treesit-fold-mode) (add-hook 'tsx-ts-mode-hook #'treesit-fold-mode) (add-hook 'css-ts-mode-hook #'treesit-fold-mode) (add-hook 'html-ts-mode-hook #'treesit-fold-mode) ;; Scripting and Infrastructure (add-hook 'bash-ts-mode-hook #'treesit-fold-mode) (add-hook 'cmake-ts-mode-hook #'treesit-fold-mode) (add-hook 'dockerfile-ts-mode-hook #'treesit-fold-mode) ;; Data and Configuration (add-hook 'json-ts-mode-hook #'treesit-fold-mode) (add-hook 'toml-ts-mode-hook #'treesit-fold-mode) (add-hook 'emacs-lisp-mode-hook #'outline-minor-mode) (add-hook 'lisp-interaction-mode-hook #'hs-minor-mode) ; scratch (add-hook 'lisp-mode-hook #'outline-minor-mode) (add-hook 'conf-mode-hook #'outline-minor-mode) (add-hook 'markdown-mode-hook #'outline-minor-mode) (add-hook 'diff-mode-hook #'outline-minor-mode) (use-package highlight-quoted :hook (emacs-lisp-mode . highlight-quoted-mode)) (use-package highlight-defined :hook (emacs-lisp-mode . highlight-defined-mode)) (use-package color-identifiers-mode :hook (emacs-lisp-mode . color-identifiers-mode)) (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode) (inferior-ess-mode . rainbow-delimiters-mode)) (use-package nerd-icons-completion :after marginalia :config (nerd-icons-completion-mode) (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup)) (use-package nerd-icons-corfu) (use-package nerd-icons-dired :hook (dired-mode . nerd-icons-dired-mode)) (use-package smartparens :init (smartparens-global-mode 1) :config (sp-pair "\"" "\"" :wrap "C-c \"") (sp-pair "'" "'" :wrap "C-c '") (sp-pair "<<" ">>" :wrap "C-c <")) (use-package indent-bars :config (setq indent-bars-prefer-character ?. indent-bars-pattern "true" indent-bars-width-frac 1 indent-bars-pad-frac 0.2 indent-bars-color-by-depth '(:regexp "outline-\\([0-9]+\\)" :blend 0.9) indent-bars-highlight-current-depth '(:face default :blend 0.4) ) :init (add-hook 'prog-mode-hook #'indent-bars-mode)) (use-package kirigami :commands (kirigami-open-fold kirigami-open-fold-rec kirigami-close-fold kirigami-toggle-fold kirigami-open-folds kirigami-close-folds-except-current kirigami-close-folds)) (use-package clipetty :hook (after-init . global-clipetty-mode)) (use-package denote :config (setq denote-directory (list (expand-file-name "~/lyfi/SOR/Notes/")) denote-file-type 'markdown-yaml denote-prompts '(title subdirectory keywords))) (use-package colorful-mode :init (setq colorful-allow-mouse-clicks nil)) ;; Tree-sitter & LSP tweaks (with-eval-after-load 'treesit (setq treesit-font-lock-level 4)) (with-eval-after-load 'typescript-ts-mode (setq typescript-ts-mode-indent-offset 2) (defvaralias 'typescript-indent-level 'typescript-ts-mode-indent-offset)) (with-eval-after-load 'lsp-mode (setq lsp-completion-provider :none)) ;; ============================================================ ;; Functions ;; ============================================================ (defun my/system-copy (beg end) "Copy region directly to system clipboard, bypassing Emacs' kill ring. Uses wl-copy on Wayland GUI, and Clipetty (OSC 52) in TUI/SSH." (interactive "r") (unless (region-active-p) (user-error "No active region to copy")) (let ((text (buffer-substring-no-properties beg end))) (if (display-graphic-p) ;; GUI: Pipe text directly to Wayland clipboard (let ((process-connection-type nil)) (let ((proc (start-process "wl-copy" nil "wl-copy"))) (process-send-string proc text) (process-send-eof proc))) ;; TUI/SSH: Use clipetty to send OSC 52 sequence to kitty (if (fboundp 'clipetty-set-selection) (clipetty-set-selection text) (error "Clipetty not loaded. Ensure (global-clipetty-mode) is active"))) (deactivate-mark) (message "Copied to system clipboard"))) (defun my/system-cut (beg end) "Cut region directly to system clipboard, bypassing the kill ring." (interactive "r") (my/system-copy beg end) ;; delete-region deletes without adding to the kill ring (delete-region beg end)) (defun my/system-paste () "Paste from system clipboard directly to point, bypassing the kill ring." (interactive) (if (display-graphic-p) ;; GUI: Read directly from Wayland clipboard and insert (let ((text (shell-command-to-string "wl-paste -n"))) (insert text)) ;; TUI/SSH: Warn the user to use the terminal's native paste (message "In a terminal, use your terminal emulator's native paste shortcut (e.g., Ctrl-Shift-V)."))) (defun dired-follow-symlink () "In dired, visit the file or directory at point by its true physical path." (interactive) (let ((filename (dired-get-file-for-visit))) (if filename (find-file (file-truename filename)) (message "No file at point")))) (defun my/vterm-sync-here () "Switch to vterm and instantly 'cd' to the current buffer's directory." (interactive) (let ((cwd default-directory)) (vterm-toggle) (vterm-send-string (format "cd %s" (shell-quote-argument cwd))) (vterm-send-return))) (defun my/delete-word-backward () "Delete the word and any preceding whitespace without affecting the kill ring." (interactive) (let ((start (point))) (skip-chars-backward "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") (skip-chars-backward " \t") (delete-region (point) start))) (defun my-modern-delete-word-backward () "Delete a block of similar characters backward without using the kill ring. Similar characters are grouped as: 1. Whitespace (including newlines) 2. Alphanumerics 3. Special characters/symbols" (interactive) (let ((end (point)) (start (1- (point)))) (when (>= start 1) (let* ((first-char (char-before end)) (first-type (my-get-char-type first-char))) ;; Move start point backward as long as the character type matches (while (and (>= start 1) (string= first-type (my-get-char-type (char-before start)))) (setq start (1- start))) ;; Delete the range without adding to the kill ring (delete-region start end))))) (defun my-get-char-type (char) "Helper to categorize characters into three groups." (let ((cat (char-category char))) (cond ((member cat '("space" "newline")) "whitespace") ((member cat '("letter" "number")) "alphanum") (t "special")))) ;; Bind it to Ctrl-Backspace ;; Note: In many terminals, C-Backspace is sent as C-h or C-Delete. ;; This binding works in GUI Emacs. (global-set-key (kbd "C-") 'my-modern-delete-word-backward) ;; ============================================================ ;; Theming, ui ;; ============================================================ ;; (my/apply-theme (selected-frame)) ;; Apply to all future frames (important for emacsclient users) (add-hook 'after-make-frame-functions 'my/apply-theme) ;;fonts (setq doom-font "Fira Code 12") (setq doom-variable-pitch-font "Noto Sans 14") (setq nerd-icons-font-family "Symbols Nerd Font Mono") (defun my-set-buffer-font () "Set a specific font for the current buffer only." (buffer-face-set '(:family "Noto Sans" :height 140))) (add-hook 'after-make-frame-functions #'my/apply-theme)