syscfg/emacs/init.el

492 lines
16 KiB
EmacsLisp

(require 'package)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
(require 'use-package)
;; helper functions
;; =====
(defun my/format-elisp-buffer ()
"Format the whole elisp buffer."
(interactive)
(save-excursion
(indent-region (point-min) (point-max))))
(defun my/setup-elisp-formatting-key ()
"Set up the formatting key for Emacs Lisp buffers."
(local-set-key (kbd "C-c f") 'my/format-elisp-buffer))
(add-hook 'emacs-lisp-mode-hook #'my/setup-elisp-formatting-key)
(defun my/swiper-query-replace ()
"Run `query-replace-regexp' on Swiper results."
(interactive)
(if (not (window-minibuffer-p))
(user-error "Call this from swiper")
(let* ((enable-recursive-minibuffers t)
(from (ivy--regex ivy-text))
(to (query-replace-read-to from
(concat "Query replace (regexp " from ") with: ")
t)))
(ivy-exit-with-action
(lambda (_)
(let ((case-fold-search (ivy--case-fold-p ivy-text)))
(query-replace-regexp from to)))))))
(defun my/visit-todo ()
"Visit TODO.org in home"
(interactive)
(find-file "~/TODO.org"))
(defun my/reload-init-file ()
"Reload the Emacs init file."
(interactive)
(load-file user-init-file))
(defun my/reload-org-config ()
"Reload the org configuration."
(interactive)
(my/org-config)
(message "Org configuration reloaded"))
(defun my-org-agenda-format-item (item)
"Format an agenda or TODO item without the filename, but keep the keyword."
(let* ((marker (or (get-text-property 0 'org-marker item)
(get-text-property 0 'org-hd-marker item)))
(line (when marker
(org-with-point-at marker
(buffer-substring (line-beginning-position) (line-end-position)))))
(todo-keyword (when marker
(org-with-point-at marker
(org-get-todo-state))))
(truncated-line (when line
(replace-regexp-in-string "^\\[#[A-Z]\\] " "" line))))
(if truncated-line
(let ((clean-item (replace-regexp-in-string "^\\(\\*+ \\)" "" truncated-line)))
(if todo-keyword
(concat todo-keyword " " (replace-regexp-in-string (concat "^" todo-keyword " ?") "" clean-item))
clean-item))
item)))
(defun my/kill-all-org-agenda-buffers ()
"Close all org-agenda-related buffers."
(interactive)
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (or (derived-mode-p 'org-agenda-mode)
(and (buffer-file-name)
(member (buffer-file-name) (org-agenda-files))))
(kill-buffer buffer))))
(message "Closed all org-agenda-related buffers."))
(defun my/org-config ()
(setq org-agenda-inhibit-startup t
org-todo-keywords '((sequence "HOLD(h)"
"TODO(t)"
"|"
"DONE(d)"
"CNCL(x)"))
org-todo-keyword-faces '(("HOLD" . (:foreground "orange" :weight bold))
("TODO" . (:foreground "red" :weight bold))
("DONE" . (:foreground "green" :weight bold))
("CNCL" . (:foreground "gray" :weight bold)))
org-agenda-files (append
(list "~/TODO.org")
(directory-files-recursively "~/notes" "\\.org$"))
org-agenda-prefix-format '((agenda . " %i ")
(todo . " %i ")
(tags . " %i ")
(search . " %i "))
org-agenda-hide-tags-regexp "."
org-agenda-todo-keyword-format "%s")
(advice-add 'org-agenda-format-item-line :filter-return #'my-org-agenda-format-item)
(advice-add 'org-agenda-format-item :filter-return #'my-org-agenda-format-item))
(defun my/org-refile-config ()
(setq org-refile-targets '((nil :maxlevel . 9)
("~/TODO.org" :maxlevel . 9)))
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm))
(defun my/org-capture-config ()
(setq org-capture-templates
'(("t" "TODO" entry (file "~/TODO.org")
"* TODO %?\n %i\n %a" :prepend t))))
(defun my/refresh-org-agenda-files ()
(interactive)
(setq org-agenda-files
(append
(list "~/TODO.org")
(directory-files-recursively "~/notes" "\\.org$"))))
(use-package emacs
:init
(tool-bar-mode -1)
(require 'server)
(unless (server-running-p) (server-start))
(setq gc-cons-threshold 100000000
read-process-output-max (* 1024 1024)
custom-file (concat user-emacs-directory "custom.el")
create-lockfiles nil
tab-always-indent 'complete
mac-command-modifier 'super
backup-directory-alist `((".*" . ,(concat user-emacs-directory "backup")))
delete-old-versions t
kept-new-versions 20
kept-old-versions 2
version-control t
delete-old-versions t
backup-by-copying t
focus-follows-mouse t
mouse-autoselect-window t
enable-recursive-minibuffers t
warning-minimum-level :emergency
inhibit-startup-screen t
completion-cycle-threshold 3
text-mode-ispell-word-completion nil
text-mode-ispell-word-completion nil
read-extended-command-predicate #'command-completion-default-include-p
read-extended-command-predicate #'command-completion-default-include-p
minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt))
(load-theme 'misterioso)
(put 'upcase-region 'disabled nil)
(put 'erase-buffer 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-defun 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'widen 'disabled nil)
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
(setq hippie-expand-try-functions-list
'(try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-expand-dabbrev-from-kill
try-complete-file-name-partially
try-complete-file-name
try-expand-all-abbrevs
try-expand-list
try-expand-line
try-complete-lisp-symbol-partially
try-complete-lisp-symbol))
(global-unset-key (kbd "C-x n"))
(global-set-key (kbd "<f12>") 'speedbar)
(global-set-key (kbd "M-/") 'hippie-expand)
(with-eval-after-load 'swiper
(define-key swiper-map (kbd "M-%") #'my/swiper-query-replace))
(global-set-key (kbd "C-=") 'multi-vterm-dedicated-toggle)
(global-set-key (kbd "C-x r") 'replace-regexp)
(global-set-key (kbd "C-x R") 'replace-string)
(global-set-key (kbd "C-x c O") 'my/reload-org-config)
(global-set-key (kbd "C-x c r") #'my/reload-init-file)
(global-set-key (kbd "C-x E n p") #'narrow-to-page)
(global-set-key (kbd "C-x E n f") #'narrow-to-defun)
(global-set-key (kbd "C-x E n r") #'narrow-to-region)
(global-set-key (kbd "C-x E w") #'widen)
(when (file-exists-p custom-file)
(load custom-file)))
(use-package async :ensure t)
(use-package async-status :ensure t)
(use-package dash :ensure t :defer t)
(use-package multiple-cursors :ensure t :defer t
:init (multiple-cursors-mode 1)
:bind (("C-S-c C-S-c" . 'mc/edit-lines)
("C->" . 'mc/mark-next-like-this)
("C-<" . 'mc/mark-previous-like-this)
("C-c C->" . 'mc/mark-all-like-this)))
;; minibuffer
;; ===
(use-package which-key :ensure t :defer t
:init (which-key-mode))
;; eye candy
;; ===
(use-package doom-modeline :ensure t
:init (doom-modeline-mode 1))
(use-package nerd-icons :ensure t :defer t)
(use-package nerd-icons-dired :ensure t :defer t
:hook (dired-mode . nerd-icons-dired-mode))
(use-package nerd-icons-completion :ensure t :defer t)
(use-package nerd-icons-ibuffer :ensure t :defer t)
(use-package unicode-fonts :ensure t :defer t)
;; org-mode
;; ===
(use-package org
:ensure t
:bind (("C-x O a" . org-agenda)
("C-x O c" . org-capture)
("C-x O t" . my/visit-todo)
("C-x O k" . my/kill-all-org-agenda-buffers))
:mode ("\\.org\\'" . org-mode)
:config
(my/org-config)
(my/org-refile-config)
(my/org-capture-config)
(run-with-idle-timer 300 t #'my/refresh-org-agenda-files))
;; knowledge management
;; ===
(use-package denote :ensure t
:bind (("C-x n f" . #'denote-open-or-create)
("C-x n n" . #'denote)
("C-x n c" . #'denote-region)
("C-x n !" . #'denote-date)
("C-x n z" . #'denote-signature)
("C-x n s" . #'denote-subdirectory)
("C-x n S-n" . #'denote-type)
("C-x n t" . #'denote-template)
("C-x n l l" . #'denote-link)
("C-x n l a" . #'denote-add-links)
("C-x n l f" . #'denote-find-link)
("C-x n b l" . #'denote-backlinks)
("C-x n b f" . #'denote-find-backlink)
("C-x n r r" . #'denote-rename-file)
("C-x n r f" . #'denote-rename-file-using-front-matter))
:config
;; Key bindings specifically for Dired.
(let ((map dired-mode-map))
(define-key map (kbd "C-x n d") #'denote-dired-mode)
(define-key map (kbd "C-x n i") #'denote-link-dired-marked-notes)
(define-key map (kbd "C-x n k") #'denote-dired-rename-marked-files-with-keywords)
(define-key map (kbd "C-x n r r") #'denote-dired-rename-files)
(define-key map (kbd "C-x n r f") #'denote-dired-rename-marked-files-using-front-matter))
(setq denote-directory (expand-file-name "~/notes/")
denote-save-buffers nil
denote-known-keywords '("emacs" "golang" "dev")
denote-infer-keywords t
denote-sort-keywords t
denote-file-type nil
denote-prompts '(title keywords)
denote-excluded-directories-regexp nil
denote-excluded-keywords-regexp nil
denote-rename-confirmations '(rewrite-front-matter modify-file-name)
denote-date-prompt-use-org-read-date t
denote-date-format nil
denote-backlinks-show-context t)
(denote-rename-buffer-mode 1)
(with-eval-after-load 'org-capture
(setq denote-org-capture-specifiers "%l\n%i\n%?")
(add-to-list 'org-capture-templates
'("n" "New note (with denote.el)" plain
(file denote-last-path)
#'denote-org-capture
:no-save t
:immediate-finish nil
:kill-buffer t
:jump-to-captured t))))
;; version control
;; ===
(use-package magit :ensure t :defer t
:bind ("C-c C-g" . magit-status)
:commands (magit-status-here magit-get-current-branch)
:custom (magit-display-buffer-function
#'magit-display-buffer-same-window-except-diff-v1))
;; diagramming
;; ===
(use-package graphviz-dot-mode :ensure t :defer t
:config
(with-eval-after-load 'org
(org-babel-do-load-languages
'org-babel-load-languages '((dot . t)))))
(use-package plantuml-mode :ensure t
:config
(setq plantuml-default-exec-mode 'jar
plantuml-jar-path "/usr/share/plantuml/plantuml.jar"
org-plantuml-jar-path (expand-file-name "/usr/share/plantuml/plantuml.jar"))
(with-eval-after-load 'org
(org-babel-do-load-languages
'org-babel-load-languages '((plantuml . t)))))
;; coding
;; ===
(use-package go-mode :ensure t :defer t
:mode (("\\.go$" . go-mode)))
(use-package yaml-mode :ensure t :defer t
:mode (("\\.yml$" . yaml-mode)))
(use-package json-mode :ensure t :defer t
:mode (("\\.json$" . json-mode)))
(use-package php-mode :ensure t :defer t
:mode (("\\.php$" . php-mode)))
(use-package python-mode :ensure t :defer t
:mode (("BUILD\\'" . python-mode) ;; please BUILD config
("\\.py$" . python-mode)))
(use-package mmm-mode :ensure t :defer t)
(use-package multi-web-mode :ensure t :defer t)
(use-package protobuf-mode :ensure t :defer t)
(use-package rego-mode :ensure t :defer t)
(use-package kotlin-mode :ensure t :defer t)
(use-package fic-mode :ensure t :defer t)
(use-package flycheck :ensure t :defer t)
(use-package eglot
:ensure t
:hook ((python-mode . eglot-ensure)
(rust-mode . eglot-ensure)
(c-mode . eglot-ensure)
(c++-mode . eglot-ensure)
(js-mode . eglot-ensure)
(typescript-mode . eglot-ensure))
:config
(setq eglot-autoshutdown t)
(setq eglot-sync-connect 1)
(setq eglot-connect-timeout 10)
(setq eglot-autoreconnect t)
(setq completion-category-defaults nil)
(setq completion-category-overrides '((eglot (styles orderless))))
(setq eglot-completion-style 'orderless)
(setq eglot-completion-provider :company)
(with-eval-after-load 'orderless
(setq orderless-matching-styles '(orderless-flex orderless-regexp orderless-initialism)))
:bind (:map eglot-mode-map
("s-l a" . eglot-code-actions)
("s-l r" . eglot-rename)
("s-l f" . eglot-format)
("s-l d" . eglot-find-declaration)
("s-l i" . eglot-find-implementation)
("s-l t" . eglot-find-typeDefinition)))
;; navigation
;; ===
(use-package ace-jump-mode :ensure t :defer t
:bind (("C-x j j" . ace-jump-mode)
("C-x j p" . 'ace-jump-mode-pop-mark)))
;; api testing
;; ===
(use-package plz :ensure t)
;; search & narrow
;; ===
(use-package ivy
:ensure t
:config
(setq ivy-re-builders-alist
'((swiper . ivy--regex-plus)
(t . orderless-ivy-re-builder)))
(setq ivy-initial-inputs-alist nil)
(setq orderless-matching-styles '(orderless-flex orderless-literal orderless-regexp)))
(use-package counsel
:ensure t
:after ivy
:config
(counsel-mode 1)
(setq counsel-grep-base-command
"rg -i -M 120 --no-heading --line-number --color never '%s' %s")
(setq counsel-rg-base-command
"rg -i -M 120 --no-heading --line-number --color never %s .")
;; Replace vanilla commands with Counsel versions
:bind (("C-x C-f" . counsel-find-file)
("M-x" . counsel-M-x)
("C-x b" . counsel-switch-buffer)
("C-x C-b" . counsel-ibuffer)
("C-h f" . counsel-describe-function)
("C-h v" . counsel-describe-variable)
("C-h l" . counsel-find-library)
("C-h i" . counsel-info-lookup-symbol)
("C-x r b" . counsel-bookmark)
("C-c g" . counsel-git)
("C-c j" . counsel-git-grep)
("C-c k" . counsel-ag)
("C-c r" . counsel-rg)
("C-x 8 RET" . counsel-unicode-char)
("C-c s" . counsel-grep-or-swiper)
:map minibuffer-local-map
("C-r" . counsel-minibuffer-history)))
(use-package projectile
:ensure t
:diminish projectile-mode
:init
(projectile-mode)
:config
(setq projectile-completion-system 'ivy
projectile-enable-caching t
projectile-indexing-method 'hybrid)
(when (executable-find "fd")
(setq projectile-generic-command
(format "%s . -0 --type f --color=never" (executable-find "fd"))))
:bind-keymap
("C-x p" . projectile-command-map))
(use-package counsel-projectile
:ensure t
:after (counsel projectile)
:config
(counsel-projectile-mode 1)
:bind
(:map projectile-command-map
("f" . counsel-projectile-find-file)
("g" . counsel-projectile-grep)
("s" . counsel-projectile-switch-project)
("b" . counsel-projectile-switch-to-buffer)
("d" . projectile-dired)
("/" . counsel-projectile-rg)
("4 b" . counsel-projectile-switch-to-buffer-other-window)
("4 d" . projectile-dired-other-window)
("5 b" . counsel-projectile-switch-to-buffer-other-frame)
("5 d" . projectile-dired-other-frame)))
(use-package ivy-prescient
:ensure t
:after counsel
:config
(ivy-prescient-mode 1)
(prescient-persist-mode 1)
(setq ivy-prescient-enable-filtering nil)
(setq ivy-prescient-enable-sorting t))
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
;; completion
;; ===
(use-package company
:ensure t
:hook (after-init . global-company-mode)
:config
(setq company-idle-delay 0.1
company-minimum-prefix-length 1
company-tooltip-align-annotations t
company-tooltip-limit 10
company-tooltip-flip-when-above t
company-require-match nil
company-dabbrev-downcase nil
company-dabbrev-ignore-case t))
(use-package company-box
:ensure t
:hook (company-mode . company-box-mode))
(use-package swiper
:ensure t
:after ivy
:bind (("C-s" . swiper)
("C-r" . swiper-backward)
:map swiper-map
("M-%" . swiper-query-replace)))