From fcd18f085aae31b7a579839ff9f9f56c1fe29537 Mon Sep 17 00:00:00 2001 From: Alexey Norets Date: Sun, 21 Sep 2025 13:55:18 +0300 Subject: [PATCH] Emacs minimalistic config --- .gitignore | 4 + early-init.el | 42 ++++ init.el | 562 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+) create mode 100644 .gitignore create mode 100644 early-init.el create mode 100644 init.el diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be2a50a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/* +!/*.el +!/*.org +!/.gitignore diff --git a/early-init.el b/early-init.el new file mode 100644 index 0000000..993a217 --- /dev/null +++ b/early-init.el @@ -0,0 +1,42 @@ +;; -*- lexical-binding: t; -*- +;; Temporarily increase the garbage collection threshold. These +;; changes help shave off about half a second of startup time. The +;; `most-positive-fixnum' is DANGEROUS AS A PERMANENT VALUE. See the +;; `emacs-startup-hook' a few lines below for what I actually use. +(setq gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.5) + +;; Same idea as above for the `file-name-handler-alist' and the +;; `vc-handled-backends' with regard to startup speed optimisation. +;; Here I am storing the default value with the intent of restoring it +;; via the `emacs-startup-hook'. +(defvar prot-emacs--file-name-handler-alist file-name-handler-alist) +(defvar prot-emacs--vc-handled-backends vc-handled-backends) + +(setq file-name-handler-alist nil + vc-handled-backends nil) + +(add-hook 'emacs-startup-hook + (lambda () + (setq gc-cons-threshold (* 1024 1024 20) + gc-cons-percentage 0.2 + file-name-handler-alist prot-emacs--file-name-handler-alist + vc-handled-backends prot-emacs--vc-handled-backends))) + + +;; + +(setq load-prefer-newer t) +(setq byte-compile-warnings '(not obsolete)) +(setq warning-suppress-log-types '((comp) (bytecomp))) + +;; Initialise installed packages at this early stage, by using the +;; available cache. I had tried a setup with this set to nil in the +;; early-init.el, but (i) it ended up being slower and (ii) various +;; package commands, like `describe-package', did not have an index of +;; packages to work with, requiring a `package-refresh-contents'. +(setq package-enable-at-startup t) + +(menu-bar-mode -1) +(scroll-bar-mode -1) +(tool-bar-mode -1) diff --git a/init.el b/init.el new file mode 100644 index 0000000..33b7e21 --- /dev/null +++ b/init.el @@ -0,0 +1,562 @@ +;; -*- lexical-binding: t; -*- +(require 'package) + +(setq package-vc-register-as-project nil) ; Emacs 30 + +(add-hook 'package-menu-mode-hook #'hl-line-mode) + +;; List of packages for auto install scratch +(setq package-list '(use-package)) + + +;; Also read: +(setq package-archives + '(("gnu-elpa" . "https://elpa.gnu.org/packages/") + ("gnu-elpa-devel" . "https://elpa.gnu.org/devel/") + ("nongnu" . "https://elpa.nongnu.org/nongnu/") + ("melpa" . "https://melpa.org/packages/"))) + +;; Highest number gets priority (what is not mentioned has priority 0) +(setq package-archive-priorities + '(("gnu-elpa" . 3) + ("melpa" . 2) + ("nongnu" . 1))) + +;; NOTE 2023-08-21: I build Emacs from source, so I always get the +;; latest version of built-in packages. However, this is a good +;; solution to set to non-nil if I ever switch to a stable release. +(setq package-install-upgrade-built-in nil) + + ; activate all the packages (in particular autoloads) +(package-initialize) + + ; fetch the list of packages available +(unless package-archive-contents + (package-refresh-contents)) + + +;; Whitespace-mode settings. +(use-package emacs + :init + (custom-set-variables '(whitespace-display-mappings '((space-mark 32 [183] [46]) + (space-mark 160 [164] [95]) + (newline-mark 10 [172 10]) + (tab-mark 9 [187 9] [92 9])))) + (custom-set-faces + '(whitespace-space ((t (:bold t :foreground "#282828")))) + '(whitespace-newline ((t (:bold t :foreground "#282828")))) + ) + (add-hook 'prog-mode-hook (lambda () (setq display-line-numbers 'relative))) + (add-hook 'prog-mode-hook 'whitespace-mode) + ) + +;; Frame settings. +(use-package emacs + :init + (setq initial-frame-alist '( + (fullscreen . maximized) + (font . "JetBrains Mono 22") + ;; (font . "Iosevka Fixed 24") + (undecorated . t) + (vertical-scroll-bars . nil) + (inhibit-double-buffering . t) + ;; (ns-transparent-titlebar . nil) + (background-color . "#181818") ;;"black") + )) + ) + +;; Basic settings. +(use-package emacs + :init + (delete-selection-mode 1) + (blink-cursor-mode 0) + (save-place-mode t) + (savehist-mode t) + (setq use-short-answers t + ring-bell-function 'ignore + scroll-conservatively 101 + help-window-select t + create-lockfiles nil + auto-save-default nil + make-backup-files nil + custom-safe-themes t + initial-buffer-choice nil ;; If not nil always show scratch buffer, even open other file. + backup-directory-alist '((".*" . "/tmp")) + custom-file (make-temp-file "emacs-custom-") + whitespace-style (quote (face spaces tabs space-mark tab-mark newline newline-mark)) + Man-sed-command "gsed") + :config + (setq-default truncate-lines nil + display-line-numbers-width 3 + indent-tabs-mode nil + tab-width 4 + c-basic-offset 4 + c-basic-indent 4) + (xterm-mouse-mode t) + (show-paren-mode t)) + +;; Mode line format. ----- +(defun my-evil-mode-indicator () + (interactive) + (cond ((eq evil-state 'visual) "") + ((eq evil-state 'insert) "") + ((eq evil-state 'normal) "") + ((eq evil-state 'emacs) "") + (t " * "))) +(setq-default mode-line-format '("%e " mode-line-modified " " + (:eval (propertize (buffer-name)) 'face 'font-lock-constant-face) + "%6l:%c (%o) " + (:eval + (if (and (mode-line-window-selected-p) (bound-and-true-p evil-mode)) + (my-evil-mode-indicator)) + " ") + (:eval (unless (not vc-mode) (concat " | git:" (substring-no-properties vc-mode 5)))) + mode-line-format-right-align + (:eval (concat " " (symbol-name major-mode))) + " " mode-line-misc-info)) + +(use-package emacs + :init + ;; Add prompt indicator to `completing-read-multiple'. + ;; We display [CRM], e.g., [CRM,] if the separator is a comma. + (defun crm-indicator (args) + (cons (format "[CRM%s] %s" + (replace-regexp-in-string + "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" + crm-separator) + (car args)) + (cdr args))) + (advice-add #'completing-read-multiple :filter-args #'crm-indicator) + ;; Do not allow the cursor in the minibuffer prompt + (setq minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt)) + (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) + ;; (add hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy) + ;; Enable recursive minibuffers + (setq enable-recursive-minibuffers t)) + + +(use-package modus-themes + :ensure t + :defer t + :config + (custom-set-faces + '(dired-header ((t (:foreground "#95a99f" :slant italic))))) + (custom-set-faces + '(dired-directory ((t (:foreground "#96a6c8" :slant italic))))) + (setq modus-themes-italic-constructs nil + modus-themes-bold-constructs nil + modus-themes-mixed-fonts nil + modus-themes-variable-pitch-ui nil + modus-themes-custom-auto-reload t + modus-themes-disable-other-themes t + modus-themes-prompts '(italic bold) + modus-themes-completions + '((matches . (extrabold)) + (selection . (semibold italic text-also))) + modus-themes-org-blocks 'tined-background + ) + (setq modus-vivendi-palette-overrides + ;; Setting for theme like gruber-darker + `( + (fg-main "#e4e4ef") + ;; Background + (bg-main "#181818") + ;; Comment and string + (comment "#cc8c3c") ;; yellow-faint) + (string "#73c936") + ;; Code colors + (builtin "#95a99f") + (constant "#95a99f") + (fnname "#96a6c8") + (keyword "#ffdd33") + (preprocessor "#95a99f") + (type "#95a99f") + (cursor "#ffdd33") + (variable "#f4f4ff") + (bg-region "#484848") + (fg-region unspecified) + (fg-completion-match-0 "#ffdd33") + (fg-prompt "#96a6c8") + (bg-mode-line-active "#282828") + (fg-mode-line-active "#ffffff") + (fg-line-number-active "#ffdd33") + (bg-line-number-active "#181818") + (bg-line-number-inactive "#181818") + ;; SRC-block + (bg-prose-block-delimiter bg-cyan-nuanced) + ;; Region bg and fg + (bg-region bg-hover) ; try to replace `bg-ochre' with `bg-lavender', `bg-sage' + (fg-region unspecified) + ))) +(load-theme 'modus-vivendi t) + +;; Denote package. ---- +(use-package denote + :ensure t + :init + :custom + (denote-directory "~/Nextcloud/DenoteNotes/") + :hook + (dired-mode . denote-dired-mode) + :custom-face + (denote-faces-link ((t (:slant italic))))) +(global-set-key (kbd "C-c n n") 'denote-open-or-create) + +(use-package consult-denote + :ensure t + :config + (define-key global-map (kbd "C-c n f") #'consult-denote-find) + (define-key global-map (kbd "C-c n g") #'consult-denote-grep)) + +(use-package vertico + :ensure t + :init + (setq vertico-scroll-margin 0) + (setq vertico-count 5) + (setq vertico-resize nil) + (setq vertico-cycle t) + (vertico-mode)) + +(use-package emacs + :custom + ;; Support opening new minibuffers from inside existing minibuffers. + (enable-recursive-minibuffers t) + ;; Hide commands in M-x which do not work in the current mode. Vertico + ;; commands are hidden in normal buffers. This setting is useful beyond + ;; Vertico. + (read-extended-command-predicate #'command-completion-default-include-p) + ;; Do not allow the cursor in the minibuffer prompt + (minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt))) + +;; Dired module. ----- +(when (string= system-type "darwin") + (setq dired-use-ls-dired nil)) +(use-package dired + :init + (setq delete-by-moving-to-trash t) + (setq dired-listing-switches "-lah") + (setq dired-dwim-target t) + (setq dired-auto-revert-buffer #'dired-directory-changed-p) + ) + +;; ISearch module. ----- +(use-package isearch + :ensure nil + :demand t + :config + (setq search-whitespace-regexp ".*?" ; one `setq' here to make it obvious they are a bundle + isearch-lax-whitespace t + isearch-regexp-lax-whitespace nil)) +(use-package isearch + :ensure nil + :demand t + :config + (setq search-highlight t) + (setq isearch-lazy-highlight t) + (setq lazy-highlight-initial-delay 0.5) + (setq lazy-highlight-no-delay-length 4)) +(use-package isearch + :ensure nil + :demand t + :config + (setq isearch-lazy-count t) + (setq lazy-count-prefix-format "(%s/%s) ") + (setq lazy-count-suffix-format nil)) +(use-package isearch + :ensure nil + :demand t + :config + (setq isearch-wrap-pause t) ; `no-ding' makes keyboard macros never quit + (setq isearch-repeat-on-direction-change t)) +(use-package isearch + :ensure nil + :demand t + :config + (setq list-matching-lines-jump-to-current-line nil) ; do not jump to current line in `*occur*' buffers + ;; (add-hook 'occur-mode-hook #'prot-common-truncate-lines-silently) ; from `prot-common.el' + (add-hook 'occur-mode-hook #'hl-line-mode)) +(use-package isearch + :ensure nil + :defer t + :config + (defun my-occur-from-isearch () + (interactive) + (let ((query (if isearch-regexp + isearch-string + (regexp-quote isearch-string)))) + (isearch-update-ring isearch-string isearch-regexp) + (let (search-nonincremental-instead) + (ignore-errors (isearch-done t t))) + (occur query))) + :bind + (:map isearch-mode-map + ("C-o" . my-occur-from-isearch))) +(use-package isearch + :ensure nil + :defer t + :config + (defun my-project-search-from-isearch () + (interactive) + (let ((query (if isearch-regexp + isearch-string + (regexp-quote isearch-string)))) + (isearch-update-ring isearch-string isearch-regexp) + (let (search-nonincremental-instead) + (ignore-errors (isearch-done t t))) + (project-find-regexp query))) + :bind + (:map isearch-mode-map + ("C-f" . my-project-search-from-isearch))) + + +;; Selection window (rg, grep, help etc). ----- +(defun my-select-window (window &rest _) + "Select WINDOW for display-buffer-alist" + (select-window window)) + +(setq display-buffer-alist + '(((or . ((derived-mode . occur-mode) + (derived-mode . rg-mode) + (derived-mode . grep-mode) + (derived-mode . Buffer-menu-mode) + (derived-mode . log-view-mode) + (derived-mode . help-mode) ; See the hooks for `visual-line-mode' + (derived-mode . flymake-diagnostics-buffer-mode) + "\\*\\(|Buffer List\\|Occur\\|vc-change-log\\|compilation\\|eldoc.*\\).*" + )) + (display-buffer-reuse-mode-window display-buffer-pop-up-window) + (body-function . my-select-window) + (dedicated . t) + (preserve-size . (t . t))))) + +;; Marginalia. ----- +(use-package marginalia + :ensure t + ;; Either bind `marginalia-cycle' globally or only in the minibuffer + :bind (("M-A" . marginalia-cycle) + :map minibuffer-local-map + ("M-A" . marginalia-cycle)) + + ;; The :init configuration is always executed (Not lazy!) + :init + + ;; Must be in the :init section of use-package such that the mode gets + ;; enabled right away. Note that this forces loading the package. + (marginalia-mode)) + +;; Orderless. ----- +(use-package orderless + :ensure t + :demand t + :after minibuffer + :config + (setq completion-styles '(orderless basic) + completion-category-defaults nil + ;; orderless-matching-styles '(orderless-prefixes orderless-regexp) + completion-category-overrides '((file (styles . (partial-completion)))) + )) + +;; Corfu +(use-package corfu + :ensure t + :defer 3 + :custom + (corfu-cycle t) ;; Enable cycling for `corfu-next/previous' + (corfu-auto t) ;; Enable auto completion + ;; (corfu-separator ?\s) ;; Orderless field separator + ;; (corfu-quit-at-boundary nil) ;; Never quit at completion boundary + ;; (corfu-quit-no-match nil) ;; Never quit, even if there is no match + ;; (corfu-preview-current nil) ;; Disable current candidate preview + ;; (corfu-preselect 'prompt) ;; Preselect the prompt + ;; (corfu-on-exact-match nil) ;; Configure handling of exact matches + ;; (corfu-scroll-margin 5) ;; Use scroll margin + (corfu-popupinfo-mode t) + + :bind (:map corfu-map + ("C-y" . corfu-complete) + ("C-e" . corfu-reset) + ("" . nil) + ) + :init + (global-corfu-mode)) + +(use-package corfu-terminal + :ensure t + :defer 3 + :init + (unless (display-graphic-p) + (corfu-terminal-mode +1))) + +;; Add extensions +(use-package cape + :ensure t + :defer 3 + ;; Bind prefix keymap providing all Cape commands under a mnemonic key. + ;; Press C-c p ? to for help. + :bind ("C-c p" . cape-prefix-map) ;; Alternative keys: M-p, M-+, ... + ;; Alternatively bind Cape commands individually. + ;; :bind (("C-c p d" . cape-dabbrev) + ;; ("C-c p h" . cape-history) + ;; ("C-c p f" . cape-file) + ;; ...) + :init + ;; Add to the global default value of `completion-at-point-functions' which is + ;; used by `completion-at-point'. The order of the functions matters, the + ;; first function returning a result wins. Note that the list of buffer-local + ;; completion functions takes precedence over the global list. + (add-hook 'completion-at-point-functions #'cape-dabbrev) + (add-hook 'completion-at-point-functions #'cape-file) + (add-hook 'completion-at-point-functions #'cape-elisp-block) + ;; (add-hook 'completion-at-point-functions #'cape-history) + ;; ... + ) + +;; Org-mode. ------ +(use-package org + :ensure t + :hook (org-mode . visual-line-mode) + :custom + (org-ellipsis "...") + (org-sturtup-indent t) + (org-hide-emphasis-markers t)) + +;; (use-package mini-modeline +;; :ensure t +;; ;; :quelpa (mini-modeline :repo "kiennq/emacs-mini-modeline" :fetcher github) +;; ;; :after smart-mode-line +;; :config +;; (setq mini-modeline-display-gui-line t) +;; (setq mini-modeline-enhance-visual t) +;; (setq mini-modeline-l-format '("%e " mode-line-modified " " +;; (:eval (propertize (buffer-name)) 'face 'font-lock-constant-face) +;; "%6l:%c (%o) " +;; (:eval (unless (not vc-mode) (concat " | git:" (substring-no-properties vc-mode 5)))))) +;; (setq mini-modeline-r-format '("" +;; (:eval (concat " " (symbol-name major-mode))) +;; " " mode-line-misc-info)) +;; (mini-modeline-mode t)) + +;; Avy ----- +(use-package avy + :ensure t) + +;; Move Lines/Text ----- +(use-package move-text + :ensure t) +(defun indent-region-advice (&rest ignored) + (let ((deactivate deactivate-mark)) + (if (region-active-p) + (indent-region (region-beginning) (region-end)) + (indent-region (line-beginning-position) (line-end-position))) + (setq deactivate-mark deactivate))) +(advice-add 'move-text-up :after 'indent-region-advice) +(advice-add 'move-text-down :after 'indent-region-advice) + + +;;; Magig ------ +(use-package magit + :ensure t + :defer 0 + :init + (setq magit-auto-revert-mode nil)) + + +;; Keybindings ------ +(define-key (current-global-map) [remap dired] 'dired-jump) +(define-key (current-global-map) [remap list-buffers] 'ibuffer) +(define-key (current-global-map) [remap kill-buffer] 'kill-current-buffer) + +(global-set-key (kbd "C-c d d") 'kill-whole-line) +(global-set-key (kbd "C-c w s") 'visual-line-mode) + +(global-set-key (kbd "S-C-") 'shrink-window-horizontally) +(global-set-key (kbd "S-C-") 'enlarge-window-horizontally) +(global-set-key (kbd "S-C-") 'shrink-window) +(global-set-key (kbd "S-C-") 'enlarge-window) + +(global-set-key (kbd "C-z") 'move-text-down) +(global-set-key (kbd "C-q") 'move-text-up) + +(global-set-key (kbd "C-c t c") 'avy-goto-char) +(global-set-key (kbd "C-c t w") 'avy-goto-word-0) + + +;;; Functions. ----- +;;RecentFiles +(defun my/recentf-open-files-compl () + (interactive) + (recentf-mode 1) + (let* ((tocpl (mapcar (lambda (x) (cons (file-name-nondirectory x) x)) + recentf-list)) + (fname (completing-read "Recent-file name: " tocpl nil nil))) + (when fname + (find-file (cdr (assoc-string fname tocpl)))))) +(global-set-key "\C-x\C-r" 'my/recentf-open-files-compl) + +;; Half page up/down +(defun my/half-page-up () + (interactive) + ;; (scroll-down-command ) + (move-to-window-line-top-bottom 0) + (recenter)) +(global-set-key (kbd "M-p") 'my/half-page-up) + +(defun my/half-page-down () + (interactive) + (if (eq (line-number-at-pos) (line-number-at-pos (window-start))) + ;; If the cursor is at the top of the page, move it to the center + (progn + (let ((middle-line (1+ (/ (window-height) 2)))) + (move-to-window-line middle-line))) + ;; (scroll-up scroll-amount) + (move-to-window-line -1 ) + (recenter))) +(global-set-key (kbd "M-n") 'my/half-page-down) + +;; Split window and follow cursor. ----- +(defun my/split-and-follow-horizontaly () + (interactive) + (split-window-below) + (balance-windows) + (other-window 1)) +(global-set-key (kbd "C-x 2") 'my/split-and-follow-horizontaly) + +(defun my/split-and-follow-verticaly () + (interactive) + (split-window-right) + (balance-windows) + (other-window 1)) +(global-set-key (kbd "C-x 3") 'my/split-and-follow-verticaly) + +;; Kill whole word/line +(defun my/kill-whole-word () + (interactive) + (forward-char 1) + (backward-word) + (kill-word 1)) +(global-set-key (kbd "C-c w w") 'my/kill-whole-word) + +(defun my/copy-whole-line () + (interactive) + (save-excursion + (kill-new + (buffer-substring + (point-at-bol) + (point-at-eol))))) +(global-set-key (kbd "C-c w l") 'my/copy-whole-line) + +;; Duplicate Line. +(defun my/duplicate-line () + "Duplicate current line" + (interactive) + (let ((column (- (point) (point-at-bol))) + (line (let ((s (thing-at-point 'line t))) + (if s (string-remove-suffix "\n" s) "")))) + (move-end-of-line 1) + (newline) + (insert line) + (move-beginning-of-line 1) + (forward-char column))) +(global-set-key (kbd "C-,") 'my/duplicate-line)