1040 lines
35 KiB
EmacsLisp
1040 lines
35 KiB
EmacsLisp
;;; init.el -- emacs configuration file
|
|
|
|
;;; Commentary:
|
|
;;; none
|
|
|
|
;;; Code:
|
|
(require 'package)
|
|
(with-eval-after-load 'package
|
|
(add-to-list 'package-archives
|
|
'("melpa" . "https://melpa.org/packages/")
|
|
'("nongnu" . "https://elpa.nongnu.org/nongnu/")))
|
|
(package-initialize)
|
|
(setq package-enable-at-startup t)
|
|
(require 'use-package)
|
|
|
|
(setq auth-sources '("~/.authinfo.gpg"))
|
|
|
|
(defvar bootstrap-version)
|
|
(let ((bootstrap-file
|
|
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
|
|
(bootstrap-version 5))
|
|
(unless (file-exists-p bootstrap-file)
|
|
(with-current-buffer
|
|
(url-retrieve-synchronously
|
|
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
|
|
'silent 'inhibit-cookies)
|
|
(goto-char (point-max))
|
|
(eval-print-last-sexp)))
|
|
(load bootstrap-file nil 'nomessage))
|
|
|
|
|
|
;; 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/visit-todo ()
|
|
"Visit TODO.org in home."
|
|
(interactive)
|
|
(find-file "~/alioth/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 to-do 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 ()
|
|
"Setup personal 'org-mode' configuration."
|
|
(setq org-agenda-inhibit-startup t
|
|
org-adapt-indentation nil
|
|
org-todo-keywords '((sequence "HOLD(h)" "TODO(t)" "WAIT(w)" "|"
|
|
"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-prefix-format '((agenda . "%i %?-12t% s ")
|
|
(todo . " %i ")
|
|
(tags . " %i ")
|
|
(search . " %i "))
|
|
org-agenda-hide-tags-regexp "."
|
|
org-agenda-todo-keyword-format "%s"
|
|
org-archive-location "%s_archive::datetree/"
|
|
org-log-done 'note
|
|
org-agenda-hide-tags-regexp nil
|
|
org-agenda-tags-column -80
|
|
org-log-into-drawer t)
|
|
|
|
(setq org-stuck-projects
|
|
'("+prj/-maybe-someday" ("TODO" "WAIT" "CNCL" "prj") nil ""))
|
|
(setq org-tags-exclude-from-inheritance '("prj"))
|
|
|
|
(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 ()
|
|
"Setup org-refile targets."
|
|
(defvar org-refile-targets)
|
|
(defvar org-refile-use-outline-path)
|
|
(defvar org-outline-path-complete-in-steps)
|
|
(defvar org-refile-allow-creating-parent-nodes)
|
|
(setq org-refile-targets
|
|
'((nil :maxlevel . 9)
|
|
("~/alioth/lkn/TODO.org" :maxlevel . 9))
|
|
org-refile-use-outline-path 'file
|
|
org-outline-path-complete-in-steps nil
|
|
org-refile-allow-creating-parent-nodes 'confirm))
|
|
|
|
(defun my/org-capture-config ()
|
|
"Setup org-capture."
|
|
(setq org-capture-templates
|
|
'(("t" "TODO" entry (file "~/alioth/TODO.org")
|
|
"* TODO %?\n %i" :prepend t)))
|
|
(with-eval-after-load 'denote
|
|
(defvar denote-org-capture-specifiers)
|
|
(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)))
|
|
(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))
|
|
|
|
(use-package emacs
|
|
:init
|
|
(tool-bar-mode -1)
|
|
(require 'server)
|
|
(unless (server-running-p) (server-start))
|
|
(setq tramp-persistency-file-name "~/.config/emacs/tramp")
|
|
(setq tramp-inline-compress-start-size 1000)
|
|
|
|
(setq backup-by-copying t
|
|
completion-cycle-threshold 3
|
|
create-lockfiles nil
|
|
custom-file (concat user-emacs-directory "custom.el")
|
|
delete-old-versions t
|
|
enable-recursive-minibuffers t
|
|
focus-follows-mouse t
|
|
indent-tabs-mode nil
|
|
inhibit-startup-screen t
|
|
kept-new-versions 20
|
|
kept-old-versions 2
|
|
minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)
|
|
mouse-autoselect-window t
|
|
read-extended-command-predicate #'command-completion-default-include-p
|
|
read-process-output-max (* 1024 1024)
|
|
tab-always-indent 'complete
|
|
tab-width 4
|
|
text-mode-ispell-word-completion nil
|
|
use-short-answers t
|
|
version-control t
|
|
warning-minimum-level :emergency
|
|
backup-directory-alist `((".*" . ,(concat user-emacs-directory "backup")))
|
|
read-extended-command-predicate #'command-completion-default-include-p
|
|
text-mode-ispell-word-completion nil
|
|
gc-cons-threshold 100000000)
|
|
|
|
(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)
|
|
|
|
(load-theme 'misterioso)
|
|
|
|
(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)
|
|
(with-eval-after-load 'ibuffer
|
|
(define-key ibuffer-mode-map (kbd "TAB") nil))
|
|
|
|
(global-unset-key (kbd "C-x n"))
|
|
|
|
(global-set-key (kbd "<f12>") 'dired-sidebar-toggle-sidebar)
|
|
(global-set-key (kbd "M-/") 'dabbrev-expand)
|
|
(global-set-key (kbd "C-=") 'multi-vterm-dedicated-toggle)
|
|
(global-set-key (kbd "M-s r") 'replace-regexp)
|
|
(global-set-key (kbd "M-s M-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-x C-<" . 'mc/edit-lines)
|
|
("C->" . 'mc/mark-next-like-this)
|
|
("C-<" . 'mc/mark-previous-like-this)
|
|
("C-x 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)
|
|
|
|
(defun my/add-remote-todo-file ()
|
|
"Add remote TODO.org file to org-agenda-files."
|
|
(interactive)
|
|
(unless org-agenda-files
|
|
(setq org-agenda-files
|
|
(list "~/alioth/TODO.org"))))
|
|
|
|
(advice-add 'org-agenda :before #'my/add-remote-todo-file)
|
|
(org-babel-do-load-languages 'org-babel-load-languages
|
|
'((shell . t))))
|
|
|
|
;; knowledge management
|
|
;; ===
|
|
|
|
|
|
(use-package denote :ensure t
|
|
:commands (my/search-denote-notes)
|
|
:bind (("C-x n f" . #'denote-open-or-create)
|
|
("C-x n n" . #'my/denote-create-note)
|
|
("C-x n d s" . #'my/denote-switch-directory)
|
|
("C-x n d d" . #'my/denote-echo-current-directory)
|
|
("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 /" . #'my/search-denote-notes)
|
|
("C-x n r r" . #'denote-rename-file)
|
|
("C-x n r f" . #'denote-rename-file-using-front-matter))
|
|
|
|
:config
|
|
|
|
(defvar my/denote-local-notes-directory "~/notes/")
|
|
(defvar my/denote-remote-notes-directory "~/alioth/notes/")
|
|
(setq denote-directory my/denote-local-notes-directory)
|
|
|
|
(defun my/denote-echo-current-directory ()
|
|
"Echo the current value of denote-directory."
|
|
(interactive)
|
|
(message "Current Denote directory: %s" denote-directory))
|
|
|
|
(defun my/denote-directory-segment ()
|
|
"Return the current Denote directory for the mode line."
|
|
(when (bound-and-true-p denote-directory)
|
|
(format " Denote:%s " (file-name-nondirectory (directory-file-name denote-directory)))))
|
|
|
|
|
|
(defun my/denote-switch-directory ()
|
|
"Switch current denote working directory."
|
|
(interactive)
|
|
(let ((new-dir (if (string= denote-directory my/denote-local-notes-directory)
|
|
my/denote-remote-notes-directory
|
|
my/denote-local-notes-directory)))
|
|
(setq denote-directory new-dir)
|
|
(setq my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org"))
|
|
|
|
(message "Denote directory switched to %s" new-dir)))
|
|
|
|
(defun my/denote-use-both-directories (orig-fun &rest args)
|
|
"Temporarily use both directories for a given function."
|
|
(let ((original-dir denote-directory)
|
|
(result nil))
|
|
(setq denote-directory my/denote-local-notes-directory)
|
|
(setq result (apply orig-fun args))
|
|
(setq denote-directory my/denote-remote-notes-directory)
|
|
(setq result (append result (apply orig-fun args)))
|
|
(setq denote-directory original-dir)
|
|
result))
|
|
|
|
(defun my/denote-create-note ()
|
|
"Create a note in directory of choice."
|
|
(interactive)
|
|
(let* ((dirs (list my/denote-local-notes-directory my/denote-remote-notes-directory))
|
|
(chosen-dir (completing-read "Choose directory: " dirs)))
|
|
(let ((denote-directory chosen-dir))
|
|
(call-interactively #'denote))))
|
|
|
|
|
|
(advice-add 'denote-directory-files :around #'my/denote-use-both-directories)
|
|
(advice-add 'denote-directory-subdirectories :around #'my/denote-use-both-directories)
|
|
|
|
(defun my/search-denote-notes ()
|
|
"Use consult-ripgrep to search only the Denote metadata index file, without recursion."
|
|
(interactive)
|
|
(let* ((index-file my/denote-metadata-index-file)
|
|
(default-directory (file-name-directory index-file))
|
|
(file-name (file-name-nondirectory index-file))
|
|
(consult-ripgrep-args
|
|
(concat "rg --null --ignore-case --type org --line-buffered --color=never "
|
|
"--max-columns=500 --no-heading --line-number "
|
|
"--max-depth 0 "
|
|
(shell-quote-argument file-name))))
|
|
(if (file-exists-p index-file)
|
|
(consult-ripgrep default-directory)
|
|
(message "Metadata index file not found: %s" index-file))))
|
|
|
|
|
|
;; (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-save-buffers nil
|
|
denote-known-keywords '("emacs" "golang" "dev")
|
|
denote-infer-keywords t
|
|
denote-sort-keywords t
|
|
denote-file-type nil
|
|
denote-history-completion-in-prompts 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))
|
|
|
|
|
|
;; version control
|
|
;; ===
|
|
(use-package magit :ensure t :defer t
|
|
:commands (magit-status-here
|
|
magit-get-current-branch
|
|
magit-get-upstream-branch
|
|
magit-read-branch
|
|
magit-git-string
|
|
magit-reset-to-merge-base
|
|
magit-reset-to-upstream)
|
|
:custom (magit-display-buffer-function
|
|
#'magit-display-buffer-same-window-except-diff-v1)
|
|
:bind (("C-x g g" . #'magit-status)
|
|
("C-x g r b" . #'magit-reset-to-merge-base)
|
|
("C-x g r u" . #'magit-reset-to-upstream)
|
|
("C-x g l l" . #'magit-log)
|
|
("C-x g l a" . #'magit-log-all)
|
|
("C-x g l f" . #'magit-log-buffer-file)
|
|
("C-x g R s" . #'magit-reset-soft)
|
|
("C-x g R h" . #'magit-reset-hard)
|
|
("C-x g !" . #'magit-run)
|
|
("C-x g b b" . #'magit-branch)
|
|
("C-x g b c" . #'magit-branch-create)
|
|
("C-x g b s" . #'magit-branch-checkout)
|
|
("C-x g m m" . #'magit-merge)
|
|
("C-x g d d" . #'magit-diff))
|
|
|
|
:config
|
|
(defun magit-reset-to-merge-base ()
|
|
"Reset to merge-base of two branches using Magit's branch selection."
|
|
(interactive)
|
|
(require 'magit)
|
|
(let* ((branch1 (magit-read-branch "Select first branch: "))
|
|
(branch2 (magit-read-branch "Select second branch: "))
|
|
(merge-base (magit-git-string "merge-base" branch1 branch2)))
|
|
(if merge-base
|
|
(progn
|
|
(magit-reset-internal "--soft" merge-base)
|
|
(message "Reset to merge-base of %s and %s" branch1 branch2))
|
|
(user-error "No merge-base found between %s and %s" branch1 branch2))))
|
|
|
|
(defun magit-reset-to-upstream ()
|
|
"Reset to upstream of the current branch using Magit."
|
|
(interactive)
|
|
(require 'magit)
|
|
(let ((current-branch (magit-get-current-branch)))
|
|
(if current-branch
|
|
(let ((upstream (magit-get-upstream-branch current-branch)))
|
|
(if upstream
|
|
(progn
|
|
(magit-reset-internal "--soft" "@{u}")
|
|
(message "Reset to upstream of %s (%s)" current-branch upstream))
|
|
(user-error "No upstream branch set for %s" current-branch)))
|
|
(user-error "Not on a branch")))))
|
|
|
|
;; 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
|
|
:custom
|
|
(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"))
|
|
:config
|
|
(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 python-environment :ensure t)
|
|
(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
|
|
:init
|
|
(global-flycheck-mode))
|
|
|
|
|
|
;; navigation
|
|
;; ===
|
|
(use-package avy
|
|
:ensure t
|
|
:bind (("C-x j j" . avy-goto-char)
|
|
("C-x j c" . avy-goto-char-2)
|
|
("C-x j l" . avy-goto-line)
|
|
("C-x j w" . avy-goto-word-1)
|
|
("C-x j ." . avy-resume))
|
|
:config
|
|
|
|
(avy-setup-default)
|
|
(setq avy-background t))
|
|
|
|
;; api testing
|
|
;; ===
|
|
(use-package plz :ensure t)
|
|
|
|
|
|
;; force recompiling init.el
|
|
;; ===
|
|
(defun my-compile-init-file ()
|
|
"Compile the init file."
|
|
(let ((init-file (file-truename user-init-file)))
|
|
(when (string= (buffer-file-name) init-file)
|
|
(byte-compile-file init-file))))
|
|
|
|
(add-hook 'after-save-hook #'my-compile-init-file)
|
|
|
|
(use-package orderless
|
|
:ensure t
|
|
:custom
|
|
(completion-styles '(orderless basic))
|
|
(completion-category-overrides '((file (styles . (partial-completion)))))
|
|
(orderless-matching-styles '(orderless-flex orderless-prefixes orderless-initialism))
|
|
:config
|
|
(setq orderless-component-separator #'orderless-escapable-split-on-space))
|
|
|
|
;; better minibuffer
|
|
(use-package vertico
|
|
:ensure t
|
|
:init
|
|
(vertico-mode)
|
|
:custom
|
|
(vertico-cycle t)
|
|
(vertico-count 13)
|
|
:config
|
|
(setq read-file-name-completion-ignore-case t
|
|
read-buffer-completion-ignore-case t
|
|
completion-ignore-case t
|
|
completion-styles '(orderless basic)
|
|
completion-category-defaults nil
|
|
completion-category-overrides '((file (styles partial-completion)))))
|
|
|
|
;; it's like ace-jump in a vertico minibuffer
|
|
(use-package vertico-quick
|
|
:after vertico
|
|
:bind (:map vertico-map
|
|
("C-q" . vertico-quick-insert)
|
|
("M-q" . vertico-quick-exit)))
|
|
|
|
;; save history
|
|
(use-package savehist
|
|
:ensure t
|
|
:init
|
|
(savehist-mode))
|
|
|
|
;; inline completion
|
|
(use-package corfu
|
|
:ensure t
|
|
:demand t
|
|
:bind ("C-." . completion-at-point)
|
|
:init
|
|
(global-corfu-mode)
|
|
|
|
:hook (eglot-managed-mode . corfu-mode)
|
|
|
|
:config
|
|
;; Enable Orderless for Corfu
|
|
(setq completion-styles '(orderless basic)
|
|
completion-category-defaults nil
|
|
completion-at-point-functions nil
|
|
completion-category-overrides '((file (styles . (partial-completion)))))
|
|
|
|
(setq corfu-auto t) ;; Enable auto completion
|
|
(setq corfu-separator ?\s) ;; Orderless field separator
|
|
(setq corfu-quit-at-boundary nil) ;; Never quit at completion boundary
|
|
(setq corfu-quit-no-match nil) ;; Never quit, even if there is no match
|
|
(setq corfu-preview-current nil) ;; Disable current candidate preview
|
|
(setq corfu-on-exact-match nil) ;; Configure handling of exact matches
|
|
(setq corfu-echo-documentation nil) ;; Disable documentation in the echo area
|
|
(setq corfu-scroll-margin 5) ;; Use scroll margin
|
|
|
|
|
|
;; Set up dabbrev as the primary completion source
|
|
;; (setq-local completion-at-point-functions '(dabbrev-completion))
|
|
|
|
;; Enable kind-icon
|
|
(use-package kind-icon
|
|
:ensure t
|
|
:after corfu
|
|
:custom
|
|
(kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
|
|
:config
|
|
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
|
|
|
|
;; Add Cape for additional completion sources
|
|
(use-package cape
|
|
:ensure t
|
|
:init
|
|
;; Add `completion-at-point-functions', used by `completion-at-point'.
|
|
(add-to-list 'completion-at-point-functions #'cape-file)
|
|
(add-to-list 'completion-at-point-functions #'cape-elisp-block)
|
|
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-history)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-keyword)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-tex)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-sgml)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-rfc1345)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-abbrev)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-dict)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-symbol)
|
|
;;(add-to-list 'completion-at-point-functions #'cape-line)
|
|
))
|
|
|
|
;; better search
|
|
(use-package consult
|
|
:ensure t
|
|
:demand t
|
|
:bind (("C-s" . consult-line)
|
|
("C-r" . consult-line-multi)
|
|
("C-c h" . consult-history)
|
|
("C-c m" . consult-mode-command)
|
|
("C-c k" . consult-kmacro)
|
|
;; Add keybindings for ripgrep-related commands
|
|
("C-c s" . consult-ripgrep)
|
|
("C-c C-s" . consult-ripgrep-multi))
|
|
:custom
|
|
(consult-preview-key nil) ; Disable preview
|
|
|
|
:config
|
|
;; Configure ripgrep for consult
|
|
(setq consult-ripgrep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS")
|
|
|
|
;; Use ripgrep for `consult-grep` as well
|
|
(setq consult-grep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS")
|
|
|
|
;; Configure mode-specific histories
|
|
(setq consult-mode-histories
|
|
'((comint-mode . comint-input-ring)
|
|
(eshell-mode . eshell-history-ring)
|
|
(geiser-repl-mode . geiser-repl--history)
|
|
(inferior-python-mode . comint-input-ring)
|
|
(minibuffer-local-map . consult-history)))
|
|
|
|
(consult-customize
|
|
consult-theme
|
|
:preview-key '(:debounce 0.2 any)
|
|
consult-ripgrep consult-git-grep consult-grep
|
|
consult-bookmark consult-recent-file consult-xref
|
|
consult--source-bookmark consult--source-recent-file
|
|
consult--source-project-recent-file
|
|
:preview-key '(:debounce 0.4 any))
|
|
|
|
;; Configure async search for better performance
|
|
(setq consult-async-min-input 2)
|
|
(setq consult-async-refresh-delay 0.15)
|
|
(setq consult-async-input-throttle 0.2)
|
|
(setq consult-async-input-debounce 0.1)
|
|
|
|
;; Use Consult to select xref locations with preview
|
|
(setq xref-show-xrefs-function #'consult-xref
|
|
xref-show-definitions-function #'consult-xref))
|
|
|
|
(use-package projectile
|
|
:ensure t
|
|
:config
|
|
(projectile-mode +1)
|
|
:bind-keymap
|
|
("C-x p" . projectile-command-map)
|
|
:bind (:map projectile-command-map
|
|
("f" . consult-projectile-find-file)
|
|
("d" . consult-projectile-find-dir)
|
|
("b" . consult-projectile-switch-to-buffer)
|
|
("s" . consult-projectile-switch-project)
|
|
("p" . consult-projectile)
|
|
("/" . consult-ripgrep)))
|
|
|
|
|
|
(use-package consult-projectile
|
|
:ensure t
|
|
:after (consult projectile)
|
|
:config
|
|
(advice-add 'projectile-find-file :override #'consult-projectile-find-file)
|
|
(advice-add 'projectile-find-dir :override #'consult-projectile-find-dir)
|
|
(advice-add 'projectile-switch-to-buffer :override #'consult-projectile-switch-to-buffer)
|
|
(advice-add 'projectile-switch-project :override #'consult-projectile-switch-project)
|
|
|
|
;; Custom function to always use consult-projectile-find-file
|
|
(defun my-consult-projectile-find-file ()
|
|
"Use consult-projectile-find-file, falling back to consult-projectile if not in a project."
|
|
(interactive)
|
|
(if (projectile-project-p)
|
|
(consult-projectile-find-file)
|
|
(consult-projectile)))
|
|
|
|
;; Replace the keybinding in projectile-command-map
|
|
(define-key projectile-command-map (kbd "f") #'my-consult-projectile-find-file))
|
|
|
|
|
|
;; util
|
|
;; =====
|
|
|
|
(use-package elfeed :ensure t :defer t)
|
|
|
|
(use-package eshell :ensure t :defer t
|
|
:bind (("C-<escape>" . #'eshell-toggle))
|
|
:config
|
|
(use-package eshell-autojump :ensure t :defer t)
|
|
(use-package eshell-bookmark :ensure t :defer t)
|
|
(use-package eshell-git-prompt :ensure t :defer t)
|
|
(use-package eshell-toggle :ensure t :defer t))
|
|
|
|
(use-package dired-sidebar :ensure t :defer t)
|
|
|
|
(use-package surround :ensure t :defer t
|
|
:bind-keymap ("M-'" . surround-keymap))
|
|
|
|
(use-package wrap-region :ensure t :defer t)
|
|
|
|
(use-package eat :ensure t :defer t)
|
|
|
|
(use-package scratch :ensure t :defer t
|
|
:bind (("C-;" . scratch)))
|
|
|
|
(defun my-set-scratch-mode ()
|
|
(funcall 'text-mode))
|
|
|
|
(add-hook 'scratch-create-buffer-hook 'my-set-scratch-mode)
|
|
|
|
(use-package go-playground :ensure t :defer t)
|
|
|
|
(use-package persistent-scratch
|
|
:ensure t
|
|
:init
|
|
(setq persistent-scratch-directory "~/.local/state/emacs/")
|
|
(unless (file-exists-p persistent-scratch-directory)
|
|
(make-directory persistent-scratch-directory t))
|
|
:config
|
|
(persistent-scratch-setup-default)
|
|
:custom
|
|
(persistent-scratch-autosave-interval 30)
|
|
(persistent-scratch-save-file
|
|
(expand-file-name "scratch" persistent-scratch-directory))
|
|
(persistent-scratch-what-to-save
|
|
'(((:current . "*scratch*") . (major-mode point narrowing))))
|
|
:hook
|
|
(after-init . persistent-scratch-autosave-mode)
|
|
:bind
|
|
(("C-c s" . persistent-scratch-save)
|
|
("C-c r" . persistent-scratch-restore)))
|
|
|
|
|
|
(defun my/dired-duplicate-file (arg)
|
|
"Duplicate the current file in Dired."
|
|
(interactive "p")
|
|
(let ((filename (dired-get-filename)))
|
|
(setq target (concat (file-name-sans-extension filename)
|
|
"-old"
|
|
(if (> arg 1) (number-to-string arg))
|
|
(file-name-extension filename t)))
|
|
(if (file-directory-p filename)
|
|
(copy-file filename target))
|
|
)
|
|
)
|
|
|
|
;; (define-key dired-mode-map (kbd "C-c d") 'my/dired-duplicate-file)
|
|
|
|
|
|
(add-to-list 'auto-mode-alist '("BUILD\\'" . python-mode))
|
|
|
|
(use-package org-journal
|
|
:ensure t
|
|
:defer t
|
|
:config
|
|
(setq org-journal-dir "~/daily/")
|
|
(setq org-journal-date-format "%A, %d %B %Y")
|
|
(setq org-journal-file-format "%Y%m%d.org")
|
|
(setq org-journal-enable-agenda-integration t))
|
|
|
|
(global-set-key (kbd "C-x O j") 'org-journal-new-entry)
|
|
|
|
(defun copy-file-path-to-clipboard ()
|
|
"Copy the current buffer's file path to the clipboard."
|
|
(interactive)
|
|
(let ((filepath (buffer-file-name)))
|
|
(when filepath
|
|
(kill-new filepath)
|
|
(message "Copied file path: %s" filepath))))
|
|
|
|
(global-set-key (kbd "C-x y b p") 'copy-file-path-to-clipboard)
|
|
(add-hook 'before-save-hook #'gofmt-before-save)
|
|
|
|
(use-package lmtf :ensure t :defer t
|
|
:straight (lmtf :type git :host github :repo "lneely/lmtf")
|
|
:bind (:map emacs-lisp-mode-map
|
|
("C-c t !" . lmtf-run-all-tests)
|
|
("C-c t r" . lmtf-show-results)))
|
|
|
|
(defun compile-init-on-save ()
|
|
"Automatically compile init.el when saving it."
|
|
(when (equal buffer-file-name (expand-file-name "~/.emacs.d/init.el"))
|
|
(byte-compile-file buffer-file-name)))
|
|
|
|
(add-hook 'after-save-hook #'compile-init-on-save)
|
|
|
|
(use-package claude-shell
|
|
:straight (claude-shell :type git :host github :repo "arminfriedl/claude-shell")
|
|
:config
|
|
(setq claude-shell-api-token (getenv "CLAUDE_API_KEY")))
|
|
|
|
|
|
(use-package websocket :ensure t)
|
|
|
|
(use-package ellama
|
|
:ensure t
|
|
:demand t
|
|
:config
|
|
(setq ellama-language "English")
|
|
(setq ellama-provider 'ollama)
|
|
(setq ellama-ollama-binary "/usr/local/bin/ollama")
|
|
(require 'llm-ollama)
|
|
(setopt ellama-provider
|
|
(make-llm-ollama
|
|
:chat-model "codellama:latest"
|
|
:embedding-model "nomic-embed-text"
|
|
:default-chat-non-standard-params '(("num_ctx" . 8192))))
|
|
:config
|
|
(setq ellama-keymap-prefix "C-x A e"))
|
|
|
|
(with-eval-after-load 'ellama
|
|
(ellama-setup-keymap))
|
|
;; :bind
|
|
;; (("C-x A c" . ellama-chat)
|
|
;; ("C-x A a" . ellama-ask-about)
|
|
;; ("C-x A s" . ellama-summarize)
|
|
;; ("C-x A t" . ellama-translate)
|
|
;; ("C-x A r" . ellama-code-review)))
|
|
|
|
(use-package org-ai
|
|
:load-path (lambda () "~/.config/emacs/elisp/org-ai")
|
|
:commands (org-ai-mode
|
|
org-ai-global-mode
|
|
org-ai-install-yasnippets)
|
|
:init
|
|
(add-hook 'org-mode-hook #'org-ai-mode)
|
|
(org-ai-global-mode)
|
|
(org-ai-install-yasnippets)
|
|
:config
|
|
(setq org-ai-service 'anthropic)
|
|
(setq org-ai-use-auth-source t)
|
|
(setq org-ai-default-chat-model "claude-3-5-sonnet-20240620"))
|
|
|
|
|
|
(use-package yasnippet
|
|
:ensure t
|
|
:config
|
|
(setq yas-snippet-dirs '("~/.config/emacs/snippets/"))
|
|
(yas-global-mode 1)
|
|
:bind
|
|
("C-x y i" . yas-insert-snippet)
|
|
("C-x y n" . yas-new-snippet)
|
|
("C-x y v" . yas-visit-snippet-file))
|
|
|
|
(use-package yasnippet-snippets
|
|
:ensure t
|
|
:after yasnippet)
|
|
|
|
(use-package wgrep :ensure t)
|
|
|
|
;; web browsing
|
|
;; ===
|
|
(require 'eww)
|
|
(setq shr-max-image-proportion 0.7)
|
|
(setq shr-image-animate nil)
|
|
(setq browse-url-browser-function 'eww-browse-url)
|
|
|
|
;; for pages that require JS for some stupid reason...
|
|
(defun browse-url-xdg-open (url &optional _new-window)
|
|
"Open URL using the default browser via `xdg-open'."
|
|
(interactive (browse-url-interactive-arg "URL: "))
|
|
(call-process "xdg-open" nil 0 nil url))
|
|
|
|
(defun eww-open-in-default-browser ()
|
|
"Open the current EWW page in the default system browser."
|
|
(interactive)
|
|
(browse-url-xdg-open (eww-current-url)))
|
|
|
|
(define-key eww-mode-map (kbd "C-c o") 'eww-open-in-default-browser)
|
|
|
|
(add-hook 'prog-mode-hook #'yas-minor-mode)
|
|
(add-hook 'org-mode-hook #'yas-minor-mode)
|
|
|
|
(use-package emmet-mode :ensure t :defer t)
|
|
|
|
(use-package embark
|
|
:ensure t
|
|
:bind
|
|
(("C-." . embark-act) ;; pick some comfortable binding
|
|
("C-;" . embark-dwim) ;; good alternative: M-.
|
|
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
|
|
|
|
:init
|
|
;; Optionally replace the key help with a completing-read interface
|
|
(setq prefix-help-command #'embark-prefix-help-command)
|
|
|
|
:config
|
|
;; Hide the mode line of the Embark live/completions buffers
|
|
(add-to-list 'display-buffer-alist
|
|
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
|
|
nil
|
|
(window-parameters (mode-line-format . none)))))
|
|
|
|
(use-package ement
|
|
:ensure t
|
|
:defer t
|
|
:config
|
|
(setq ement-save-sessions t)
|
|
(setq ement-room-list-auto-update t))
|
|
|
|
(require 'denote)
|
|
|
|
;; automated denote metadata management
|
|
(defvar my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org")
|
|
"Path to the metadata index file.")
|
|
|
|
(defun my/extract-date-from-filename (file)
|
|
"Extract date from FILE name. Assumes first 8 characters are YYYYMMDD."
|
|
(substring (file-name-nondirectory file) 0 8))
|
|
|
|
(defun my/format-date (date-string)
|
|
"Format DATE-STRING (YYYYMMDD) as YYYY-MM-DD."
|
|
(format "%s-%s-%s"
|
|
(substring date-string 0 4)
|
|
(substring date-string 4 6)
|
|
(substring date-string 6 8)))
|
|
|
|
(defun my/extract-summary (file)
|
|
"Extract summary from FILE."
|
|
(with-temp-buffer
|
|
(insert-file-contents file)
|
|
(goto-char (point-min))
|
|
(when (re-search-forward "^\\* Summary$" nil t)
|
|
(forward-line)
|
|
(let ((start (point)))
|
|
(if (re-search-forward "^\\*" nil t)
|
|
(string-trim (buffer-substring-no-properties start (line-beginning-position)))
|
|
(string-trim (buffer-substring-no-properties start (point-max))))))))
|
|
|
|
|
|
(defun my/update-denote-metadata-index ()
|
|
"Update the metadata index for Denote notes in Org format."
|
|
(interactive)
|
|
(with-temp-file my/denote-metadata-index-file
|
|
(insert "#+TITLE: Denote Metadata Index\n")
|
|
(insert "#+AUTHOR: Your Name\n")
|
|
(insert "#+DATE: " (format-time-string "%Y-%m-%d") "\n\n")
|
|
(dolist (file (directory-files-recursively denote-directory "\\.org$"))
|
|
(let ((file-contents (with-temp-buffer
|
|
(insert-file-contents file)
|
|
(buffer-string))))
|
|
(with-temp-buffer
|
|
(insert file-contents)
|
|
(goto-char (point-min))
|
|
(when (re-search-forward "^#\\+title: \\(.+\\)$" nil t)
|
|
(let* ((title (string-trim (match-string 1)))
|
|
(keywords (denote-extract-keywords-from-path file))
|
|
(date-string (my/extract-date-from-filename file))
|
|
(formatted-date (my/format-date date-string))
|
|
(is-journal (and (listp keywords) (member "journal" keywords)))
|
|
(summary (if is-journal
|
|
(format "Journal entry for [%s]" formatted-date)
|
|
(my/extract-summary file))))
|
|
(with-current-buffer (find-file-noselect my/denote-metadata-index-file)
|
|
(goto-char (point-max))
|
|
(insert (format "* [[file:%s][%s]]\n" file title))
|
|
(insert " :PROPERTIES:\n")
|
|
(insert (format " :KEYWORDS: %s\n" keywords))
|
|
(insert (format " :DATE: [%s]\n" formatted-date))
|
|
(insert " :END:\n\n")
|
|
(insert (if summary
|
|
summary
|
|
"No summary provided."))
|
|
(unless (string-suffix-p "\n\n" (or summary ""))
|
|
(insert "\n\n"))))))))))
|
|
|
|
(use-package detached
|
|
:ensure t
|
|
:init
|
|
(detached-init)
|
|
:bind (([remap async-shell-command] . detached-shell-command)
|
|
([remap compile] . detached-compile)
|
|
([remap recompile] . detached-compile-recompile)
|
|
([remap detached-open-session] . detached-consult-session))
|
|
:custom
|
|
((detached-show-output-on-attach t)
|
|
(detached-terminal-data-command system-type)))
|
|
|
|
|
|
(defun generate-tags-for-directory (directory)
|
|
"Generate CTAGS for the specified DIRECTORY."
|
|
(interactive
|
|
(list (read-directory-name "Generate tags for directory: " default-directory nil t)))
|
|
(let ((default-directory directory))
|
|
(shell-command "ctags -e -R .")
|
|
(message "Tags generated for %s" directory)))
|
|
|
|
(global-set-key (kbd "C-x E t") 'generate-tags-for-directory)
|
|
|
|
(defun mount-alioth-sshfs ()
|
|
"Mount Alioth Uberspace using SSHFS."
|
|
(interactive)
|
|
(let ((default-directory "~"))
|
|
(shell-command "sshfs lkn@alioth.uberspace.de:/home/lkn/ ~/alioth")))
|
|
|
|
(defun umount-alioth-sshfs ()
|
|
"Unmount Alioth Uberspace using SSHFS."
|
|
(interactive)
|
|
(let ((default-directory "~"))
|
|
(shell-command "umount ~/alioth")))
|
|
|
|
|
|
(provide 'init)
|
|
|
|
;; Local Variables:
|
|
;; flycheck-disabled-checkers: (emacs-lisp-checkdoc emacs-lisp)
|
|
;; End:
|
|
|
|
;;; init.el ends here
|
|
|