Skip to content

Latest commit

 

History

History
1641 lines (1345 loc) · 40.1 KB

init.org

File metadata and controls

1641 lines (1345 loc) · 40.1 KB

meta

;;; -*- lexical-binding: t -*-

core

env

;; https://github.com/hlissner/doom-emacs/blob/develop/core/core.el

(setq auto-mode-case-fold nil
      bidi-inhibit-bpa t
      highlight-nonselected-windows nil
      fast-but-imprecise-scrolling t
      frame-inhibit-implied-resize t

      idle-update-delay 1.0

      pgtk-wait-for-event-timeout 0.001

      redisplay-skip-fontification-on-input t
      command-line-ns-option-alist nil
      ad-redefinition-action 'accept
      debug-on-error init-file-debug
      jka-compr-verbose init-file-debug

      inhibit-default-init t)

(setq-default bidi-display-reordering 'left-to-right
              bidi-paragraph-direction 'left-to-right
              cursor-in-non-selected-windows nil)


;; custom

(setq default-directory "~/"
      custom-file (expand-file-name "custom.el" user-emacs-directory)
      read-process-output-max (* 1024 1024) ;; https://emacs-lsp.github.io/lsp-mode/page/performance/
      enable-local-eval t
      mouse-wheel-scroll-amount '(2 ((shift) . 2))
      mouse-wheel-progressive-speed nil
      same-window-buffer-names '("*Help*")
      Man-notify-method 'pushy


      inhibit-startup-screen t
      initial-scratch-message nil
      initial-major-mode 'text-mode

      ring-bell-function 'ignore

      use-dialog-box nil
      vc-follow-symlinks t

      split-height-threshold 120
      split-width-threshold 160

      ;; Kill active process buffer no prompt
      kill-buffer-query-functions (remq 'process-kill-buffer-query-function kill-buffer-query-functions)

      display-buffer-alist
      '(("*Async Shell Command*" . (display-buffer-no-window))))

(setq-default indent-tabs-mode nil
              sentence-end-double-space nil
              ;; increase (magit-commit) line autowrap width (default=70)
              fill-column 80)

(setopt use-short-answers t)
(delete-selection-mode 1)
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)


;;;; as per https://github.com/minad/vertico#configuration

;; Add prompt indicator to `completing-read-multiple'.
;; Alternatively try `consult-completing-read-multiple'.
(defun crm-indicator (args)
  (cons (concat "[CRM] " (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)

;; disabled: breaks kill-buffer diff save behavior
;; (setq enable-recursive-minibuffers t)

elpaca

(setq use-package-compute-statistics nil
      comp-async-report-warnings-errors nil
      warning-suppress-log-types '((comp))
      )


;; https://github.com/progfolio/elpaca/issues/222#issuecomment-1872596777
;; https://github.com/NixOS/nixpkgs/issues/267548
(setq elpaca-core-date '(20231211))

;; installer
(defvar elpaca-installer-version 0.7)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                 ,@(when-let ((depth (plist-get order :depth)))
                                                     (list (format "--depth=%d" depth) "--no-single-branch"))
                                                 ,(plist-get order :repo) ,repo))))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
;; installer end



;; (setq elpaca-verbosity most-positive-fixnum)

;; load directly before elpaca-use-package to register :blackout use-package handler
(elpaca blackout)

(elpaca elpaca-use-package
  ;; Enable use-package :ensure support for Elpaca.
  (elpaca-use-package-mode)
  (setq use-package-always-ensure t))

(elpaca-wait)

core packages

(use-package emacs
  :ensure nil
  :custom

  browse-url-browser-function 'browse-url-generic
  browse-url-generic-program (executable-find "handlr")
  browse-url-generic-args '("open")

  ;; https://github.com/minad/corfu?tab=readme-ov-file#configuration
  ;;
  (tab-always-indent 'complete)
  ;; Disable Ispell completion function (using manual jinx spell check instead)
  (text-mode-ispell-word-completion nil)
  ;; Hide commands in M-x which do not apply to the current mode
  (read-extended-command-predicate #'command-completion-default-include-p)

  ;; permanently accept '-*- buffer-auto-save-file-name: nil; -*-' file variable as safe
  ;; https://emacs.stackexchange.com/questions/73868/automatically-mark-risky-file-local-variable-safe
  (safe-local-variable-values `((buffer-auto-save-file-name . nil)))
)


(use-package f)
(use-package dash)
(use-package a)
(use-package ts)
(use-package request)


;; install latest transient before my-menus
(use-package transient)


(use-package my
  :after (request transient)
  :load-path "my"
  :ensure nil
  :config
  (add-to-list 'save-some-buffers-action-alist
               `(?r my-discard-buffer-action
                    "discard this buffer"))
  :custom
  (org-make-link-description-function 'my-url-get-title)
  :demand t)

(elpaca-wait)

;; Required to get e.g. wl-clipboard, browse-url wayland connection working on system start (without having to restart the daemon)
;; Used in conjunction with 'systemctl --user import-environment WAYLAND_DISPLAY DISPLAY' in the sway config
(use-package exec-path-from-shell
  :custom
  (exec-path-from-shell-arguments nil)
  (exec-path-from-shell-variables '("DISPLAY" "WAYLAND_DISPLAY"))
  :init
  (my-with-eval-after-frame
   (exec-path-from-shell-initialize)))

common

keybindings

(use-package general
  :config
  (general-auto-unbind-keys))

(elpaca-wait)

(general-unbind
  ;; disable overwrite
  "<insert>"
  ;; disable mouse-wheel zoom
  "<C-mouse-4>"
  "<C-mouse-5>"
  "C-<wheel-down>"
  "C-<wheel-up>"
  "<pinch>"

  ;; https://www.reddit.com/r/emacs/comments/3c61zl/abolish_the_secondary_selection_quick_and_easy/
  "<M-drag-mouse-1>" ; mouse-set-secondary
  "<M-down-mouse-1>"   ; mouse-drag-secondary
  "<M-mouse-1>"	  ; mouse-start-secondary
  "<M-mouse-2>"	  ; mouse-yank-secondary
  "<M-mouse-3>")	  ; mouse-secondary-save-then-kill

(general-def
  :keymaps 'prog-mode-map
  ;; not global because conflict with org-mode new heading
  "C-<return>" 'crux-smart-open-line
  "M-<return>" 'crux-smart-open-line-above
  )

(general-def
  "C-z" 'yank

  "C-M-`" 'quoted-insert

  "M-y" 'consult-yank-pop
  "C-M-s" 'consult-ripgrep

  "C-M-r" 'project-query-replace-regexp

  "C-x b" 'magit-blame
  "C-x l" 'magit-log-buffer-file

  "M-." 'xref-find-definitions
  ;; clashes with run-elisp-function
  ;; "M-:" 'xref-find-definitions-other-window
  "C-M-." 'xref-find-references
  "C-r" 'substitute-target-in-buffer

  "C-<backspace>" 'puni-backward-kill-word)

(general-def
  :keymaps 'override
  :prefix "C-c"
  "l" 'org-store-link
  "a" 'org-agenda
  "b" 'org-iswitchb
  "c" 'org-capture)

(general-def
  :keymaps 'org-mode-map :package 'org
  "<return>" 'org-return
  "M-S-<delete>" 'my/org-delete-heading-or-line

  "C-M-<end>" 'org-sentence-newline
  "C-c e" 'org-edit-special
  "C-c TAB" nil)


(general-def :package 'mosey
  "C-a" 'mosey-backward-bounce
  "C-e" 'mosey-forward-bounce
  )


(general-def
  :keymaps 'key-translation-map
  "M-q" "C-u")

(general-def
  :keymaps 'org-src-mode-map :package 'org
  "C-c e" 'org-edit-src-exit)

(general-def
  :keymaps 'drag-stuff-mode-map :package 'drag-stuff
  "M-<up>" 'drag-stuff-up
  "M-<down>" 'drag-stuff-down)

(general-def
  :keymaps 'isearch-mode-map
  "C-r" 'isearch-query-replace)

(general-def
  :keymaps 'dired-mode-map :package 'dirvish
  "M-<return>" 'dirvish-dispatch
  "M-#" 'dired-find-file
  )

(general-def
  :keymaps 'override

  "C-l" 'goto-line
  "C-q" 'embark-act


  "M-'" 'crux-duplicate-current-line-or-region

  "˝" 'previous-buffer
  "" 'next-buffer

  "C-^" 'delete-window
  "C-1" 'delete-other-windows
  "C-2" 'split-window-right
  "C-3" 'split-window-below
  "C-M-1" 'winner-undo
  "C-M-2" 'winner-redo

  "M-[" 'windmove-left
  "M-\\" 'windmove-right
  "M-=" 'windmove-up
  "M-]" 'windmove-down

  "M-{" 'buf-move-left
  "M-|" 'buf-move-right
  "M-+" 'buf-move-up
  "M-}" 'buf-move-down

  "C-d" 'consult-project-extra-find
  "M-d" 'consult-buffer
  "C-M-d" 'find-file



  "C-M-p" 'vundo
  "C-p" 'undo-fu-only-undo
  "M-p" 'undo-fu-only-redo


  "C-<up>" 'golden-ratio-scroll-screen-down
  "C-<down>" 'golden-ratio-scroll-screen-up

  "C-x ^" nil
  "C-x d" nil

  "C-<tab>" 'my-indent-rigidly

  "C-M-k" 'kill-current-buffer
  "M-r" 'revert-buffer

  "<f5>" 'profiler-start
  "<f6>" 'profiler-stop

  "C-M-f" 'my-menus-code
  "C-x f" 'my-menus-files
)

(general-def
  :keymaps 'eglot-mode-map :package 'eglot
  "M-/"  'eglot-code-actions
  "C-r" 'eglot-rename)

(general-def
  :keymaps 'embark-file-map :package 'embark
  "!" nil
  "&" nil
  "f" nil
  "r" nil
  "d" nil
  "R" nil
  "b" nil
  "l" nil
  "m" 'magit-project-status)

(general-def
  :keymaps 'embark-region-map :package 'embark
  "t" 'google-translate-smooth-translate
  "d" 'lexic-search)

(general-def
  :keymaps 'embark-identifier-map :package 'embark
  "s" 'jinx-correct)

(general-def
  :keymaps 'embark-symbol-map :package 'embark
  "s" 'jinx-correct)

editing

(blackout 'eldoc-mode)
(setq xref-prompt-for-identifier nil)

modeline

(setq-default
 mode-line-position (list "%l,%c")
 mode-line-format
 '("%e" mode-line-front-space
   mode-line-mule-info
   (:propertize (" " mode-line-position) display (min-width (8.0)))
   mode-line-frame-identification mode-line-buffer-identification
   mode-line-modes mode-line-misc-info " "
   mode-line-end-spaces)
 )

debugging

(use-package explain-pause-mode
  :ensure (:host github :repo "lastquestion/explain-pause-mode")
  :commands explain-pause-mode)

(use-package esup
  :custom
  (esup-depth 0)
  :commands esup)

backup & auto-save

source

(setq delete-old-versions t
      kept-new-versions 6
      create-lockfiles nil
      kept-old-versions 2
      version-control t
      backup-by-copying t
      emacs-tmp-dir (my-ensure-dir user-emacs-directory "tmp/")
      emacs-backup-dir (my-ensure-dir emacs-tmp-dir "backups/")
      emacs-autosave-dir (my-ensure-dir emacs-tmp-dir "autosaves/")
      backup-directory-alist `(("." . ,emacs-backup-dir))
      auto-save-file-name-transforms `((".*" ,emacs-autosave-dir t))
      auto-save-list-file-prefix emacs-autosave-dir)

buffer & window management

(winner-mode 1)

(use-package buffer-move)

(use-package mosey)

(use-package edit-indirect
  :ensure (:type git :host github :repo "Fanael/edit-indirect")
  :commands edit-indirect-region)


(use-package activities
  :init
  (activities-mode)
  (activities-tabs-mode)

  :bind (("M-1" . activities-new)
         ("M-2" . activities-resume)
         ("M-3" . activities-suspend)
         ("M-4" . activities-discard))

  :custom
  (tab-bar-show 1)
  (tab-bar-new-button-show nil)
  (tab-bar-close-button-show nil)
  )


;;; from https://github.com/nex3/perspective-el#some-musings-on-emacs-window-layouts

(customize-set-variable 'display-buffer-base-action
                        '((display-buffer-reuse-window display-buffer-same-window)
                          (reusable-frames . t)))

(customize-set-variable 'even-window-sizes nil)     ; avoid resizing

secrets

(use-package auth-source-pass
  :ensure nil
  :init (auth-source-pass-enable)
  :custom
  (auth-source-pass-filename (getenv "PASSWORD_STORE_DIR"))
  (auth-sources '(password-store)))

(use-package
  epa-file
  :ensure nil
  :custom (epa-file-select-keys nil))

(use-package secrets
  :ensure nil)

minibuffer abort

http://trey-jackson.blogspot.com/2010/04/emacs-tip-36-abort-minibuffer-when.html

(defun stop-using-minibuffer ()
  "kill the minibuffer"
  (when (and (>= (recursion-depth) 1) (active-minibuffer-window))
    (abort-recursive-edit)))

(add-hook 'mouse-leave-buffer-hook 'stop-using-minibuffer)

general-purpose

org-mode

;; disabled in order to fix isearch reveal of collapsed org trees
;; (use-package reveal
;;   :hook (org-mode . reveal-mode)
;;     :blackout reveal-mode)
(use-package org
  :mode ("\\.org\\'" . org-mode)
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((emacs-lisp . t)
     (latex . t)
     (python . t)
     (gnuplot . t)
     (shell . t)
     (sql . t)))

  :custom
  (org-directory "~/dot/notes/org")
  (org-capture-templates '(("a" "Brain" plain (function org-brain-goto-end)
                            "* %i%?" :empty-lines 1)))
  (org-return-follows-link nil)
  (org-support-shift-select t)
  (org-image-actual-width '(500))
  (org-list-allow-alphabetical t)
  (org-use-property-inheritance t)
  (org-use-sub-superscripts nil)
  (org-checkbox-hierarchical-statistics t)

  (org-export-with-toc nil)
  (org-export-with-section-numbers nil)
  (org-refile-use-outline-path t)
  (org-datetree-add-timestamp 1)
  (org-extend-today-until 6)
  (org-duration-format (quote h:mm))
  (org-outline-path-complete-in-steps nil)
  (org-hide-emphasis-markers t)
  (org-refile-targets '((nil :level . 2)))

  (org-src-fontify-natively t)
  (org-src-tab-acts-natively t)
  (org-pretty-entities t)
  (org-src-preserve-indentation t)
  (org-src-window-setup 'current-window)
  (org-edit-src-auto-save-idle-delay 60)

  (org-insert-heading-respect-content t)

  (org-startup-folded t)

  (org-priority-lowest 70)
  )

(use-package org-contrib
  :after org
  :config
  (require 'org-checklist)
  )

(use-package company-org-block
  :commands company-org-block
  :after (org cape))

(use-package my-org
  :after org
  :load-path "my/org"
  :ensure nil
  )

(use-package polymode :defer t)

(use-package org-modern
  :after org
  :init (global-org-modern-mode))

(use-package org-web-tools
  :after org
  :bind (:map org-mode-map
        (("M-l" . org-web-tools-insert-link-for-url)))
  )

org-crypt

https://orgmode.org/manual/Org-Crypt.html

(use-package org-crypt
  :after org
  :ensure nil
  :config
  (org-crypt-use-before-save-magic)
  (add-to-list 'safe-local-variable-values
               '(buffer-auto-save-file-name nil))
  :custom
  (org-crypt-key "jan.moeller0@gmail.com")
  )

;; https://stackoverflow.com/questions/76388376/emacs-org-encrypt-entry-hangs-when-file-is-modified
(fset 'epg-wait-for-status 'ignore)


(use-package age
  :config
  (age-file-enable)
  :custom
  (age-default-identity  "~/.config/age/identity.age")
  (age-default-recipient "~/.config/age/public-key.txt"))

notes

(use-package denote
  :after org
  :init
  (setq my/denote-directory-moi "~/dot/notes/moi"
        my/denote-directory-wrk "~/dot/notes/wrk")
  :custom
  (denote-directory my/denote-directory-moi)
  )

(use-package denote-silo-extras
  :after denote
  :ensure nil
  :custom
  (denote-silo-extras-directories `(,my/denote-directory-wrk))
  )

(use-package consult-notes
  :after denote
  :bind
  ("C-<home>" . consult-notes)
  ("C-S-<home>" . (lambda () (interactive) (let ((denote-directory my/denote-directory-wrk)) (consult-notes))))
  ("M-<home>" . consult-notes-search-in-all-notes)
  ("M-S-<home>" . (lambda () (interactive) (let ((denote-directory my/denote-directory-wrk)) (consult-notes-search-in-all-notes))))
  :init (consult-notes-denote-mode)
  :custom
  (consult-notes-denote-display-id nil)
  )

special modes

(use-package vlf
    :commands (vlf-mode vlf)
    :init (require 'vlf-setup))

(use-package logview
    :commands logview-mode)

(use-package journalctl-mode
  :commands journalctl)

passive modes

(use-package whole-line-or-region
    :init (whole-line-or-region-global-mode 1)
    :blackout whole-line-or-region-local-mode)

(use-package editorconfig
    :config (editorconfig-mode 1)
    :blackout editorconfig-mode)

(use-package hungry-delete
    :init (global-hungry-delete-mode)
    :blackout hungry-delete-mode)

;; https://github.com/lassik/emacs-format-all-the-code/issues/33
(use-package envrc
  :init (envrc-global-mode)
  :blackout envrc-mode)

;; currently no usage for it
;; but keep an eye on future features
;; (use-package async)

(use-package midnight
  :ensure nil
  :config
  (setq midnight-period (* 60 60 3)) ;; 3h
  (advice-add 'clean-buffer-list :around 'suppress-message-advice-around)
  (midnight-delay-set 'midnight-delay (ts-format "%I:%M%p" (ts-adjust 'minute 5 (ts-now))))
  :custom
  ;; https://www.emacswiki.org/emacs/CleanBufferList
  (clean-buffer-list-delay-special (* 60 60 2)) ;; 2h
  (clean-buffer-list-kill-regexps '(".*")))

editing

(use-package treesit-auto
  :config
  (global-treesit-auto-mode))

(use-package substitute)

(use-package drag-stuff
  :init
  (drag-stuff-global-mode 1)
  :config
  (setq drag-stuff-except-modes '(org-mode))
  :blackout drag-stuff-mode)

(use-package golden-ratio-scroll-screen
  :custom
  (golden-ratio-scroll-highlight-flag nil)
  (golden-ratio-scroll-screen-ratio 3.0))

(use-package quickrun :commands quickrun)

(use-package expreg
  :bind
  ("C-M-w" . expreg-expand)
  ("C-M-q" . expreg-contract)
  )

(use-package combobulate
  :custom
  (combobulate-flash-node nil)
  :ensure (:host github :repo "mickeynp/combobulate" :build (:not autoloads))
  )

;; (use-package dogears
;;   :init (dogears-mode)
;;   :custom
;;   (dogears-hooks '(xref-after-jump-hook isearch-mode-end-hook))
;;   :straight (:host github :repo "alphapapa/dogears.el"))

(use-package binky
  :init
  (binky-mode)
  )

formatting

;; Unified approach inc: https://github.com/purcell/reformatter.el/pull/24
(use-package apheleia
  :init (apheleia-global-mode +1)
  :config
  ;; add additional formatters
  (push '(treefmt . ("treefmt" "-v" "--config-file" (expand-file-name "~/.config/treefmt/treefmt.toml") "--tree-root" "." "--stdin" filepath)) apheleia-formatters)

  ;; overwrite mode formatters
  (setf (alist-get 'nix-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'sh-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'bash-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'go-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'terraform-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'rustic-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'web-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'html-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'typescript-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'js-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'css-ts-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'scss-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'slint-mode apheleia-mode-alist) 'treefmt)
  (setf (alist-get 'toml-ts-mode apheleia-mode-alist) 'treefmt)

  ;; add mode formatters
  (push '(nxml-mode . treefmt) apheleia-mode-alist)
  (push '(jsonnet-mode . treefmt) apheleia-mode-alist)
  (push '(d2-mode . treefmt) apheleia-mode-alist)
  (push '(emacs-lisp-mode . lisp-indent) apheleia-mode-alist)

  ;; disable mode formatting
  (setf apheleia-mode-alist (assoc-delete-all 'yaml-ts-mode apheleia-mode-alist))
  :blackout)

lsp

(use-package eglot
  :ensure nil
  :custom
  (eglot-autoshutdown t)
  (eglot-confirm-server-initiated-edits nil)
  )

(use-package eglot-booster
  :ensure (:host github :repo "jdtsmith/eglot-booster")
  :after eglot
  :config
  (eglot-booster-mode)
  )

linting

(use-package jinx ;; spell checking
  :ensure nil ;; built by nix
  :hook (text-mode conf-mode)
  :custom (jinx-languages "en_US")
  :blackout)

isearch

(use-package isearch-mb
  :init (isearch-mb-mode)
  :config
  (setq-default
   isearch-lazy-count t
   search-ring-max 200
   regexp-search-ring-max 200))


;;;; isearch tweaks

;;; auto-wrap isearch: https://stackoverflow.com/a/36707038

;; Prevents issue where you have to press backspace twice when
;; trying to remove the first character that fails a search
(define-key isearch-mode-map [remap isearch-delete-char] 'isearch-del-char)

(defadvice isearch-search (after isearch-no-fail activate)
  (unless isearch-success
    (ad-disable-advice 'isearch-search 'after 'isearch-no-fail)
    (ad-activate 'isearch-search)
    (isearch-repeat (if isearch-forward 'forward))
    (ad-enable-advice 'isearch-search 'after 'isearch-no-fail)
    (ad-activate 'isearch-search)))


;; instant isearch reverse
;; https://emacs.stackexchange.com/a/58825
(define-advice isearch-repeat (:before (direction &optional count) goto-other-end)
  "If reversing, start the search from the other end of the current match."
  (unless (eq isearch-forward (eq direction 'forward))
    (when isearch-other-end
      (goto-char isearch-other-end))))

minibuffer & completion

(use-package vertico
  :ensure (vertico :files (:defaults "extensions/*"))
  :init
  (vertico-mode)
)

(use-package vertico-directory
  :ensure nil
  :after vertico
  :hook (rfn-eshadow-update-overlay . vertico-directory-tidy)
  :bind (:map vertico-map
              (("M-<right>" . vertico-directory-enter)
               ("M-<left>" . vertico-directory-delete-word)))
  )

(use-package savehist
  :ensure nil
  :init
  (savehist-mode))

(use-package orderless
  :custom
  (orderless-matching-styles '(orderless-prefixes))
  )


(use-package fussy
  :custom
  (completion-styles '(fussy basic))
  (completion-category-defaults nil)
  (completion-category-overrides nil)

  (fussy-filter-fn 'fussy-filter-orderless)
  )


;; https ://www.reddit.com/r/emacs/comments/krptmz/emacs_completion_framework_embark_consult/
(use-package consult
  :after consult-project-extra
  :custom
  (consult-preview-key nil)
  (consult-narrow-key "^")

  (consult-buffer-sources '(consult--source-hidden-buffer
                            consult--source-modified-buffer
                            consult--source-buffer
                            consult--source-recent-file
                            consult--source-file-register
                            consult--source-bookmark))

  :config
  (consult-customize
   consult--source-bookmark
   :hidden t)
  )

(use-package consult-project-extra)


(use-package marginalia
  :init
  (marginalia-mode)
  :config
  ;; disable all annotations
  (mapc
   (lambda (x)
     (setcdr x (cons 'none (remq 'none (cdr x)))))
   marginalia-annotator-registry)
  )

(use-package embark-consult
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

(use-package embark
  :after embark-consult
  :config
  (push 'embark--ignore-target
        (alist-get 'jinx-correct embark-target-injection-hooks))
  )

crux

https://github.com/bbatsov/crux

(use-package crux)

rainbow-mode

(use-package colorful-mode
    :ensure (:type git :host github :repo "DevelopmentCool2449/colorful-mode")
    :hook (css-ts-mode org-mode help-mode)
    :blackout)

kurecolor

(use-package kurecolor :defer t)

Evil-Nerd-Commenter

(use-package evil-nerd-commenter
    :config
    (evilnc-default-hotkeys))

Outshine

(use-package outshine
    :commands outshine-mode)

(use-package navi-mode
    :commands navi-mode)

Corfu & Cape

(use-package corfu
    :init (global-corfu-mode)
    :custom
    (corfu-auto t)
    (corfu-auto-prefix 2)
    :blackout corfu-mode
    )

(use-package cape
  :init
  ;; TODO: maybe use cape-dict with long delay
  ;; (add-to-list 'completion-at-point-functions #'cape-ispell)
  (add-to-list 'completion-at-point-functions (cape-company-to-capf #'company-org-block))
  )

parentheses

(use-package puni
  :defer t
  :init
  ;; The autoloads of Puni are set up so you can enable `puni-mode` or
  ;; `puni-global-mode` before `puni` is actually loaded. Only after you press
  ;; any key that calls Puni commands, it's loaded.
  (puni-global-mode)

  :custom
  (puni-confirm-when-delete-unbalanced-active-region nil))

(electric-pair-mode 1)

vundo & undo-fu

(use-package vundo)

(use-package undo-fu)

Ediff

TODO more at oremacs.com

Config

(setq ediff-keep-variants nil)
(setq ediff-window-setup-function 'ediff-setup-windows-plain
      ediff-split-window-function 'split-window-horizontally)
(add-hook 'ediff-prepare-buffer-hook 'show-all)

Ripgrep

(use-package wgrep :defer t)

project

(setq project-vc-merge-submodules nil)

which-key

(use-package which-key
    :init (which-key-mode)
    :custom
    (which-key-show-early-on-C-h t)
    (which-key-idle-delay 3.0)
    (which-key-idle-secondary-delay 0.05)
    :blackout which-key-mode)

helpful

(use-package
  helpful
  :bind* (("C-h f" . helpful-callable)
          ("C-h v" . helpful-variable)
          ("C-h k" . helpful-key)
          ("C-h C-d" . helpful-at-point)
          ("C-h F" . helpful-function)
          ("C-h C" . helpful-command)))

dumb-jump

(use-package
  dumb-jump
  :init (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
  :custom (dumb-jump-prefer-searcher 'rg))

ix.io

(use-package webpaste
  :commands (webpaste-paste-buffer-or-region)
  :custom
  (webpaste-provider-priority '("paste.rs"))
)

magit

(use-package magit
    :commands magit-status
    :custom
    (magit-auto-revert-mode nil)
    (magit-diff-section-arguments (quote ("--no-ext-diff" "-U2")))
    (magit-diff-refine-ignore-whitespace nil)
    (magit-refs-margin '(t "%Y-%m-%d %H:%M" magit-log-margin-width nil 18))
    (magit-log-margin '(t "%Y-%m-%d %H:%M" magit-log-margin-width t 18))
    (magit-diff-refine-hunk t)
    (magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)
    (git-commit-use-local-message-ring t)

    :config
    (magit-add-section-hook 'magit-status-sections-hook
                            'magit-insert-modules-unpulled-from-upstream
                            'magit-insert-unpulled-from-upstream)
    (magit-add-section-hook 'magit-status-sections-hook
                            'magit-insert-modules-unpushed-to-upstream
                            'magit-insert-unpulled-from-upstream)

    ;; significantly improves performance in large repos (e.g. nixpkgs)
    ;; see https://discourse.nixos.org/t/how-to-handle-nixpkgs-as-a-very-large-git-repo/24614/9
    (remove-hook 'magit-status-headers-hook 'magit-insert-tags-header)

    (defun my--git-commit-check-commitlint (force)
      (or force
          (let ((old-buffer (current-buffer)))
            (save-window-excursion
              (with-temp-buffer
                (insert-buffer-substring old-buffer)
                (let* ((commitlint-cmd "commitlint -g ~/.config/commitlint-rs/config.yaml")
                       (exit-code (shell-command-on-region (point-min) (point-max) commitlint-cmd)))
                  (if (equal exit-code 0)
                      t
                    (pop-to-buffer-same-window "*Shell Command Output*" ())
                    (ansi-color-apply-on-region (point-min) (point-max))
                    (y-or-n-p "Commitlint error. Commit anyway?")
                    )
                  ))))))
    (cl-pushnew 'my--git-commit-check-commitlint git-commit-finish-query-functions))

dired & dirvish

;; mostly based on https://github.com/alexluigit/dirvish/blob/main/Configuration.org#Example-config

(use-package dired
  :ensure nil
  :custom
  (dired-recursive-deletes 'top)
  (dired-recursive-copies 'always)
  (delete-by-moving-to-trash t)
  (dired-dwim-target t)
  (dired-auto-revert-buffer t)
  (dired-clean-confirm-killing-deleted-buffers nil)
  ;; todo: not respected?
  (dired-kill-when-opening-new-dired-buffer t)
  (dired-listing-switches "-aDGhvl --group-directories-first --time-style=long-iso"))

(use-package dirvish
  :demand t
  :custom
  (dirvish-mode-line-height 15)
  :init
  (dirvish-override-dired-mode))

(use-package dired-x
  :ensure nil
  ;; Enable dired-omit-mode by default
  ;; :hook
  ;; (dired-mode . dired-omit-mode)
  :config
  ;; Make dired-omit-mode hide all "dotfiles"
  ;; (setq dired-omit-files
  ;;       (concat dired-omit-files "\\|^\\..*$"))
  )

(use-package diredfl
  :hook
  (dired-mode . diredfl-mode))

languages

adoc

(use-package adoc-mode
    :mode "\\.adoc\\'")

c++

(use-package c++-mode
  :ensure nil
  :mode "\\.h\\'")

(use-package rtags
  :defer t)

(use-package irony
  :init (defun my--on-c++-mode ()
          (irony-mode)
          (flycheck-mode)
          ;; (rtags-start-process-unless-running)
          )
  :hook (c++-mode . my--on-c++-mode))

(use-package flycheck-irony
  :hook (flycheck-mode . flycheck-irony-setup))

docs

;; no idea which one is the main elisp file
;; (use-package doc-tools
;;   :ensure (:host github :repo "dalanicolai/doc-tools")
;;   :mode "\\.pdf\\'")


(use-package csv-mode)

docker

(use-package dockerfile-mode
    :mode ("\\Dockerfile\\'" "\\Dockerfile\\'"))

elisp

(use-package emacs-lisp
  :ensure nil
  :hook (emacs-lisp-mode . nameless-mode)
  )

(use-package nameless
  :commands nameless-mode
  :custom (nameless-private-prefix t))

(use-package xtest :defer t)

elixir

(use-package elixir-mode
    :hook (elixir-mode . flycheck-mode)
    :mode "\\.ex\\'")

(use-package flycheck-credo
    :after elixir-mode
    :config
    (flycheck-credo-setup)
    :custom
    (flycheck-elixir-credo-strict nil))

golang

(use-package go-mode
    :mode "\\.go\\'"
    ;; :config
    ;; (require 'dap-dlv-go)
    :hook (go-ts-mode . eglot-ensure))

graphql

(use-package graphql-mode
    :mode ("\\.gql\\'" "\\.graphql\\'"))

java

(use-package java-mode
  :ensure nil
  ;; :hook (java-mode . eglot-ensure)
  :mode "\\.java\\'")

json

(use-package json-mode
    :mode "\\.json\\'"
    :custom
    (json-reformat:indent-width 2)
    (js-indent-level 2))

jsonnet

(use-package jsonnet-mode
    :mode  ("\\.jsonnet\\'" "\\.libsonnet\\'"))

just

(use-package just-mode
    :mode  ("justfile\\'"))

kotlin

(use-package kotlin-mode
  :mode "\\.kt\\'")

latex

(use-package tex
  :ensure nil ;; built by nix
  :mode "\\.tex\\'"
  :hook (LaTeX-mode . turn-on-reftex))

lua

(use-package lua-mode
    :mode ("\\.lua\\'"))

markdown

(use-package markdown-mode
    :mode "\\.md\\'")

(use-package grip-mode
  :custom
  (grip-preview-use-webkit nil))

nim

(use-package nim-mode
    :mode "\\.nim\\'"
    :hook ((nim-mode . nimsuggest-mode)))

nix

(use-package nix-mode
    :mode "\\.nix.*\\'"
    :hook (nix-mode . eglot-ensure))

php

(use-package php-mode
    :mode "\\.php\\'")

plantuml

(use-package
    plantuml-mode
    :mode "\\.puml\\'"
    :custom
    (plantuml-executable-path "/usr/bin/plantuml")
    (plantuml-default-exec-mode 'executable))

d2

(use-package d2-mode
  :mode "\\.d2\\'"
  )

python

(use-package python
  :ensure nil
  :mode ("\\.py\\'" . python-ts-mode)
  :hook (python-ts-mode . (lambda ()
                            (eglot-ensure)
                            (combobulate-mode)
                            ))
  )

;; emacs-ipython-notebook
(use-package ein
  :mode ("\\.ipynb\\'" . ein:ipynb-mode))

rust

;; No conditional-on-mode necessary
(use-package rustic
  :ensure (:host github :repo "emacs-rustic/rustic")
  :custom
  (rustic-lsp-client 'eglot)
  (rust-mode-treesitter-derive t)
  )

(use-package ron-mode
  :mode "\\.ron\\'"
  )

(use-package slint-mode
  :mode "\\.slint\\'")

sql

(use-package sql-indent
  :hook (sql-mode . sqlind-minor-mode)
  :blackout sqlind-minor-mode)

terraform

(use-package terraform-mode
    :mode "\\.tf\\'"
    ;; too expensive to auto-enable when just viewing files (enable on demand when developing)
    ;; :hook (terraform-mode . eglot-ensure)
    )

shell

(use-package sh-mode
  :ensure nil
  :hook (sh-mode . eglot-ensure))

(use-package nushell-mode
  :mode "\\.nu\\'")

(use-package fish-mode
  :mode "\\.fish\\'")

tramp

(setq tramp-default-method "ssh")

webdev

(use-package css-ts-mode
  :ensure nil
  :mode ("\\.less\\'" "\\.css\\'" "\\.sass\\'")
  :custom
  (css-indent-offset 4))

(use-package scss-mode
  :ensure nil
  :mode ("\\.scss\\'"))

(use-package js2-mode
    :mode "\\.js\\'"
    :hook (js2-mode . eglot-ensure)
    :custom
    (js2-basic-offset 2)
    (js2-strict-inconsistent-return-warning nil)
    (js2-strict-missing-semi-warning nil)
    :blackout)

(use-package web-mode
    :mode ("\\.jsx\\'" "\\.tsx\\'")
    :custom
    (web-mode-enable-auto-closing t)
    (web-mode-enable-auto-indentation nil))

(use-package html-ts-mode
  :ensure nil
  :mode ("\\.html?\\'"))


(use-package typescript-ts-mode
  :ensure nil
  :mode "\\.ts\\'"
  :hook (typescript-ts-mode . (lambda ()
                                (eglot-ensure)
                                (combobulate-mode)
                                ))
  )

yaml

;; todo: https://github.com/zkry/yaml-pro
(use-package yaml-ts-mode
  :ensure nil
  :mode ("\\.yml.*\\'" "\\.yaml.*\\'")
  )

hurl

(use-package hurl-mode
  :ensure (:host github :repo "jaszhe/hurl-mode")
    :mode "\\.hurl\\'")

tools

(use-package x509-mode :defer t)

(use-package restclient
    :mode ("\\.http\\'" . restclient-mode)
    :commands restclient-mode)

(use-package ledger-mode
    :mode "\\.ledger\\'")

(use-package google-translate)
(use-package google-translate-smooth-ui
  :after google-translate
  :ensure nil
  :commands google-translate-smooth-translate
  :config
  ;; https://github.com/atykhonov/google-translate/issues/52#issuecomment-727920888
  (setq google-translate-translation-directions-alist '(("de" . "en")("en" . "de")))
  (defun google-translate--search-tkk () "Search TKK." (list 430675 2721866130))
  (google-translate--setup-minibuffer-keymap)
)

(use-package mail-mode
  :ensure nil
    :mode "\\/tmp\\/neomutt.*\\'")
(use-package khardel
  :general
  (:keymaps 'mail-mode-map
            "C-f" 'khardel-insert-email))

(use-package himalaya)
(use-package notmuch)

(use-package gif-screencast
  :commands gif-screencast-start-or-stop
  :custom
  (gif-screencast-program "grim")
  (gif-screencast-args ()))

(use-package insert-shebang
  :init
  ;; revert ;;;###autoload(add-hook 'find-file-hook 'insert-shebang)
  (remove-hook 'find-file-hook 'insert-shebang)
  :commands insert-shebang
  :custom
  (insert-shebang-track-ignored-filename nil))

(use-package org-download
  :after org)

(use-package string-inflection
  :commands (string-inflection-all-cycle))

(use-package recover-buffers)

(use-package ebuku
  :commands ebuku)

(use-package units-mode
  :commands units-mode)

(use-package lorem-ipsum
  :commands (lorem-ipsum-insert-sentences lorem-ipsum-insert-paragraphs))

;; (use-package spookfox
;;   :straight
;;   (spookfox :type git
;;             :host github
;;             :repo "bitspook/spookfox"
;;             :files ("lisp/*.el" "lisp/apps/*.el"))
;;   :config
;;   (require 'spookfox-org-tabs)
;;   (setq spookfox-enabled-apps '(spookfox-org-tabs))
;;   ;; (spookfox-init)
;;   )

(use-package org-ai
  :after org)

ui

(menu-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(mouse-avoidance-mode)
(setq blink-cursor-blinks 3)
(scroll-bar-mode -1)
(column-number-mode 1)
(set-face-attribute 'default nil :family "Monospace" :height 110)
(setq-default cursor-type 'bar)

(use-package olivetti
  :hook
  ;; alternatively try as global mode: https://github.com/rnkn/olivetti/pull/56
  (text-mode . olivetti-mode)
  (prog-mode . olivetti-mode)
  (dired-mode . olivetti-mode)
  (magit-mode . olivetti-mode)
  ;; (fundamental-mode . olivetti-mode)
  :custom
  (olivetti-mode-on-hook '())
  (olivetti-body-width 125)
  :blackout olivetti-mode)


;; https://stackoverflow.com/questions/27845980/how-do-i-remove-newline-symbols-inside-emacs-vertical-border
(setf (cdr (assq 'continuation fringe-indicator-alist))
      '(nil right-curly-arrow) ;; right indicator only
      )

(use-package modus-themes
    :init
    (defun my-modus-themes-custom-faces ()
      (modus-themes-with-colors
        (custom-set-faces
         `(show-paren-match ((,c :foreground ,green-intense :background unspecified :weight bold)))
         `(olivetti-fringe ((,c :background ,bg-main)))
         )))
    ;; TODO: not working, must be called manually after load-theme
    (add-hook 'modus-themes-after-load-theme-hook #'my-modus-themes-custom-faces)
    :config
    (load-theme 'modus-operandi-tinted :no-confim)
    (my-modus-themes-custom-faces)
    )

finish

(use-package gcmh
  :init
  ;; https://github.com/hlissner/doom-emacs/blob/develop/core/core.el#L295
  (setq gcmh-idle-delay 'auto  ; default is 15s
        gcmh-auto-idle-delay-factor 10
        ;; 16mb
        gcmh-high-cons-threshold (* 16 1024 1024))
  (gcmh-mode 1)
  :blackout)

Calc

https://www.reddit.com/r/emacs/comments/1mbn0s/the_emacs_calculator/

braindump

other

elisp tips

regexp

\(Buy: \)\([0-9]+\) -> \1\,(+ \#2 \#)

C-c C-o save search results

reset var: `(setq foo (eval (car (get ‘foo ‘standard-value))))`

plausiblly

https://github.com/abo-abo/hydra/wiki/Emacs