From 3cf89bc9b046923763646cc08ab4cfd185452da2 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Sun, 6 Oct 2024 00:51:46 -0400 Subject: [PATCH] Emacs: numerous changes, added org-roam, and bibliography management --- dot_config/emacs/early-init.el.tmpl | 12 +- dot_config/emacs/init.el | 486 +++++++++++++++++++++------- dot_config/emacs/lisp/init-org.el | 433 ++++++++++++++++++++----- 3 files changed, 726 insertions(+), 205 deletions(-) diff --git a/dot_config/emacs/early-init.el.tmpl b/dot_config/emacs/early-init.el.tmpl index 00752ef..7466036 100644 --- a/dot_config/emacs/early-init.el.tmpl +++ b/dot_config/emacs/early-init.el.tmpl @@ -62,7 +62,7 @@ (unless (file-directory-p xdg_cache_home) (make-directory xdg_cache_home)) -;; Move `eln-cache' to XDG_CACHE_HOME +;; Move eln-cache to XDG_CACHE_HOME (when (fboundp 'startup-redirect-eln-cache) (if (< emacs-major-version 29) (push (expand-file-name "eln-cache/" xdg_cache_home) native-comp-eln-load-path) @@ -73,7 +73,7 @@ ;; Enable async native compilation and suppress warnings (when (featurep 'native-compile) - (setq native-comp-deferred-compilation t + (setq native-comp-jit-compilation t native-comp-async-report-warnings-errors nil)) ;; Don't advertise instructions for frame exit @@ -102,10 +102,10 @@ ;; Fonts {{- if eq .chezmoi.hostname "helix" }} -(push '(font . "Hack-24") default-frame-alist) -(set-face-font 'default "Hack-24") -(set-face-font 'fixed-pitch "Hack-24") -(set-face-font 'variable-pitch "DejaVu Sans-24") +(push '(font . "Hack-14") default-frame-alist) +(set-face-font 'default "Hack-14") +(set-face-font 'fixed-pitch "Hack-14") +(set-face-font 'variable-pitch "DejaVu Sans-14") {{- else }} (push '(font . "Hack-11") default-frame-alist) (set-face-font 'default "Hack-11") diff --git a/dot_config/emacs/init.el b/dot_config/emacs/init.el index 40a9825..23922eb 100644 --- a/dot_config/emacs/init.el +++ b/dot_config/emacs/init.el @@ -90,7 +90,7 @@ (add-hook 'after-init-hook #'elpaca-process-queues) (elpaca `(,@elpaca-order)) -;; Configure `use-package' with `Elpaca' +;; Ensure `Elpaca' doesn't pull built-in packages from source (defmacro use-feature (name &rest args) "Like `use-package' but accounting for asynchronous installation. NAME and ARGS are in `use-package'. Credit to @progfolio: @@ -103,7 +103,10 @@ (elpaca elpaca-use-package (require 'elpaca-use-package) (elpaca-use-package-mode) - (setopt use-package-always-ensure t)) + (setopt use-package-always-ensure t + ;; REVIEW - apparently `use-package-always-pin' doesn't work with `Elpaca' + ;; TODO - remove stats when done profiling startup + use-package-compute-statistics t)) (if debug-on-error (setopt use-package-verbose t @@ -112,9 +115,6 @@ (setopt use-package-verbose nil use-package-expand-minimally t)) -;; TODO - remove this when done profiling startup -(setopt use-package-compute-statistics t) - ;; Wait for `Elpaca' to process current queue (elpaca-wait) @@ -122,26 +122,65 @@ ;; Revert a buffer if the file has changed (use-feature autorevert - :config (global-auto-revert-mode t) - :defer 3 + :config + (setopt auto-revert-interval 10) + (global-auto-revert-mode) + :defer 5 :init (setopt auto-revert-interval 0.01)) +(use-feature bibtex + :after (ebib) + :config (setopt bibtex-autokey-additional-names "EtAl" + bibtex-autokey-edit-before-use t ; "t" by default + bibtex-autokey-expand-strings t + bibtex-autokey-name-case-convert-function #'capitalize + bibtex-autokey-name-year-separator "" + bibtex-autokey-names-stretch 1 + bibtex-autokey-titlewords 2 + bibtex-autokey-titlewords-stretch 1 + bibtex-autokey-titleword-length #'infty + bibtex-autokey-titleword-separator "" + bibtex-autokey-titleword-case-convert-function #'capitalize + bibtex-autokey-year-length 4 + bibtex-autokey-year-length 4 + bibtex-autokey-year-title-separator "")) + +(use-feature c-ts-mode + :commands (c-ts-mode) + :config + (setopt c-ts-mode-indent-style 'linux + c-ts-mode-indent-offset 8 + indent-tabs-mode t)) + + +(use-feature c++-ts-mode + :commands (c++-ts-mode) + :config + (setopt c++-ts-mode-indent-style 'gnu + c-ts-mode-indent-offset 4)) + ;; Customize compilation & related buffers (use-feature compile - :bind - (:map prog-mode-map - ("C-c r" . recompile)) :commands (compile recompile) :config (setopt compilation-ask-about-save nil - compilation-scroll-output 'first-error)) + compilation-scroll-output 'first-error) + :hook (compilation-filter . ansi-color-compilation-buffer)) ;; Write external customizations to /dev/null (use-feature cus-edit :init (setopt custom-file null-device)) +;; Library for creating/customizing user options (use-feature custom :config (setopt custom-safe-themes t)) +;; File manager +(use-feature dired + :commands (dired) + :config + (setopt dired-kill-when-opening-new-dired-buffer t + dired-mouse-drag-files t)) + ;; Customize fill column indicator (use-feature display-fill-column-indicator :hook ((conf-mode @@ -157,31 +196,34 @@ (use-feature eglot :bind (:map eglot-mode-map - ("C-c c a" . eglot-code-actions) - ("C-c c o" . eglot-code-actions-organize-imports) - ("C-c c r" . eglot-rename) - ("C-c c f" . eglot-format)) + ("C-c e a" . eglot-code-actions) + ("C-c e o" . eglot-code-actions-organize-imports) + ("C-c e r" . eglot-rename) + ("C-c e f" . eglot-format)) :config - (push '(astro-mode . ("astro-ls" "--stdio" - :initializationOptions - (:typescript (:tsdk "/usr/lib/node_modules/typescript/lib")))) eglot-server-programs) - (push '((rust-ts-mode rust-mode) . ("rustup" "run" "stable" "rust-analyzer" - :initializationOptions - (:check (:command "clippy")))) eglot-server-programs) + (add-to-list 'eglot-server-programs '(astro-mode . ("astro-ls" "--stdio" + :initializationOptions + (:typescript (:tsdk "/usr/lib/node_modules/typescript/lib"))))) + (add-to-list 'eglot-server-programs '((rust-ts-mode rust-mode) . ("rustup" "run" "stable" "rust-analyzer" + :initializationOptions + (:check (:command "clippy"))))) (setopt eglot-autoshutdown t eglot-ignored-server-capabilities - '(;; :colorProvider ; "Decorate color references" + '(:colorProvider ; "Decorate color references" ;; :documentFormattingProvider ; "Format buffer" ;; :documentHighlightProvider ; "Highlight symbols automatically" :documentOnTypeFormattingProvider ; "On-type formatting" ;; :documentRangeFormattingProvider ; "Format portion of buffer" - ;; :hoverProvider ; "Documentation on hover" + :hoverProvider ; "Documentation on hover" :inlayHintProvider ; "Inlay hints" )) :hook ((astro-mode + bash-ts-mode c-ts-mode c++-ts-mode + python-ts-mode rust-ts-mode + sh-script zig-mode) . eglot-ensure)) ;; Automatic parenthesis/brackets matching @@ -194,8 +236,9 @@ (use-feature emacs :bind (:map global-map - ;; Disable `suspend-frame' shortcut + ;; Disable `suspend-frame' shortcut (currently overwritten by undo-fu anyway) ("C-z" . nil) + ("C-x k" . my-kill-buffer) ;; Escape behaves like C-g ("" . keyboard-escape-quit) ;; Resizing buffers @@ -204,13 +247,40 @@ ("S-C-" . shrink-window) ("S-C-" . enlarge-window)) :config - (setopt cursor-type 'bar - fill-column 80 + (setopt completion-ignore-case t + cursor-type 'bar + enable-recursive-minibuffers t + fill-column 79 + history-delete-duplicates t + minibuffer-prompt-properties ; Disable prompt in minibuffer + '(read-only t cursor-intangible t face minibuffer-prompt) + read-buffer-completion-ignore-case t scroll-step 1 scroll-conservatively 1000 - scroll-preserve-screen-position t) + scroll-preserve-screen-position t + sentence-end-double-space nil + tab-always-indent 'complete + use-short-answers t) + :hook (minibuffer-setup . cursor-intangible-mode) :init - ;; Vertico + (defun my-kill-buffer () + (interactive) + (catch 'quit + (save-window-excursion + (let (done) + (when (and buffer-file-name (buffer-modified-p)) + (while (not done) + (let ((response (read-char-choice + (format "Save file %s? (y, n, d, q) " (buffer-file-name)) + '(?y ?n ?d ?q)))) + (setq done (cond + ((eq response ?q) (throw 'quit nil)) + ((eq response ?y) (save-buffer) t) + ((eq response ?n) (set-buffer-modified-p nil) t) + ((eq response ?d) (diff-buffer-with-file) nil)))))) + (kill-buffer (current-buffer)) + (force-mode-line-update))))) + ;; For Vertico (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string @@ -218,12 +288,7 @@ crm-separator) (car args)) (cdr args))) - (advice-add #'completing-read-multiple :filter-args #'crm-indicator) - - (setopt enable-recursive-minibuffers t ; Recursive minibuffers - minibuffer-prompt-properties ; Disable prompt in minibuffer - '(read-only t cursor-intangible t face minibuffer-prompt)) - (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)) + (advice-add #'completing-read-multiple :filter-args #'crm-indicator)) ;; Customize file handling & automatic backups (use-feature files @@ -232,15 +297,19 @@ (unless (file-directory-p auto_save_directory) (make-directory auto_save_directory)) (setopt backup-directory-alist `((".*" . ,auto_save_directory)) - auto-save-list-file-prefix (concat auto_save_directory "saves-")) - auto-save-file-name-transforms `((".*" ,auto_save_directory t))) + auto-save-list-file-prefix (concat auto_save_directory "saves-") + auto-save-file-name-transforms `((".*" ,auto_save_directory t)))) (setopt backup-by-copying t confirm-kill-processes nil delete-old-versions t kept-new-versions 5 kept-old-versions 3 require-final-newline t - version-control t)) + version-control t) + ;; Treesit doesn't play nice in Emacs 29 + ;; REVIEW: Putting this here for now because `major-mode-remap-alist' belongs to `files' + (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode)) + (add-to-list 'major-mode-remap-alist '(python-mode . python-ts-mode))) ;; Customize syntax checking (use-feature flymake @@ -255,15 +324,22 @@ (use-feature flyspell :hook (((git-commit-mode - org-mode - text-mode) . flyspell-mode) - (prog-mode . flyspell-prog-mode))) + org-mode) . flyspell-mode)) + :init (setopt flyspell-use-meta-tab nil)) ;; Customize frame behavior/cursor blinking (use-feature frame :config (setopt blink-cursor-delay 1.0 blink-cursor-interval 0.75)) +(use-feature help + :config (setopt help-enable-variable-value-editing t + help-window-select t) + :defer 3) + +(use-feature minibuffer + :config (setopt read-file-name-completion-ignore-case t)) + ;; Ensure UTF-8 terminal encoding (use-feature mule :config (unless (display-graphic-p) @@ -276,6 +352,10 @@ mouse-wheel-progressive-speed nil mouse-wheel-scroll-amount '(2 ((shift) . 1)))) +;; Customize comment insertion +(use-feature newcomment + :config (setopt comment-style 'multi-line)) + ;; Visualize matching parenthesis/brackets (use-feature paren :hook ((conf-mode @@ -285,25 +365,31 @@ ;; Enable smooth mouse wheel scrolling when available (when (fboundp 'pixel-scroll-precision-mode) (use-feature pixel-scroll - :config (pixel-scroll-precision-mode))) + :config (pixel-scroll-precision-mode) + :defer 1)) +;; Customize project management (use-feature project + :commands (project-find-dir project-find-file project-switch-project) :config (setopt project-vc-extra-root-markers '("Cargo.toml"))) ;; Customize handling of recent files (use-feature recentf :config - (setopt recentf-max-menu-items 100 - recentf-max-saved-items 1000 + (setopt recentf-auto-cleanup 600 + recentf-max-menu-items 100 + recentf-max-saved-items 100 recentf-save-file (expand-file-name "recentf" xdg_cache_home)) (recentf-mode)) ;; Customize saving mini-buffer history (use-feature savehist :config - (setopt savehist-autosave-interval 120 + (setopt savehist-additional-variables '(compile-command kill-buffer kill-ring) + savehist-autosave-interval 120 savehist-file (expand-file-name "history" xdg_cache_home)) - (savehist-mode)) + (savehist-mode) + :defer 1) ;; Customize saving place in files (use-feature saveplace @@ -325,6 +411,9 @@ (use-package seq :ensure `(seq :build ,(+elpaca-seq-build-steps))) +(use-feature shell + :hook (shell-mode . ansi-color-for-comint-mode-on)) + ;; "A grab-bag of basic Emacs commands" (use-feature simple :config @@ -335,15 +424,15 @@ prog-mode) . column-number-mode) (text-mode . auto-fill-mode))) -;; Customize text buffers -(use-feature text-mode - :config (setq-local fill-column 120)) - ;; Customize tree-sitter parsing (use-feature treesit - :config - (push '(c++-mode . c++-ts-mode) major-mode-remap-alist) - (setopt treesit-font-lock-level 4)) + :config (setopt treesit-font-lock-level 4) + :defer 1) + +;; Remember to take breaks +(use-feature type-break + :config (setopt type-break-keystroke-threshold '(nil . nil)) + :commands (type-break-mode)) ;; Customize whitespace visualization & cleanup (use-feature whitespace @@ -363,6 +452,11 @@ ;;; Packages: +;; `other-window' replacement +(use-package ace-window + :commands (ace-window) + :bind ("C-c w" . ace-window)) + ;; Shows current/total matches in mode line (use-package anzu :config (global-anzu-mode) @@ -372,10 +466,15 @@ (use-package apheleia :defer t) +(use-package biblio + :after (ebib) + :config (setopt biblio-bibtex-use-autokey t)) + ;; Extensions for `completion-at-point' (use-package cape :bind ("M-p" . cape-prefix-map)) +;; TODO: pick a theme ;; Catppuccin theme (use-package catppuccin-theme :disabled @@ -473,9 +572,9 @@ :after (eglot) :bind (:map eglot-mode-map - ("C-c c s" . consult-eglot-symbols))) + ("C-c e s" . consult-eglot-symbols))) -;; In-buffer completion +;; In-buffer completion with popup menu (use-package company :disabled :bind @@ -491,44 +590,116 @@ company-tooltip-align-annotations t) :hook (prog-mode . company-mode)) -;; FIXME - I crash emacs +;; In-buffer completion with popup menu (use-package corfu - ;; :disabled - :bind - (:map corfu-map - ("M-SPC" . corfu-insert-separator) - ("RET" . nil)) :config - (setopt corfu-auto t - corfu-cycle t) + (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster) + (advice-add 'eglot-completion-at-point :around #'cape-wrap-noninterruptible) + (setopt corfu-auto nil + corfu-cycle t + corfu-preview-current nil) (global-corfu-mode) + (corfu-history-mode) (corfu-popupinfo-mode) :defer 3 :ensure (corfu :files (:defaults "extensions/*"))) -(use-package doom-themes - :disabled - :config - (setopt doom-themes-enable-bold t - doom-themes-enable-italic t)) +(use-package ebib + ;;; Functions: + ;; ebib-list-recent ; list entries added within the given # of days + ;; ebib-download-url ; attempt to download file, rename, and save to target directory, filename determined by `ebib-name-transform-function' + ;; ebib-import-file ; import a local file, file is renamed and moved, works for more than pdf, name deterined by `ebib-name-transform-function' + ;;; ebib-biblio + ;; ebib-biblio-selection-import ; function to import entry from biblio into ebib + ;; ebib-biblio-import-doi ; function to import an entry while in ebib UI, can also choose different key -(use-package nimbus-theme - :config (load-theme 'nimbus)) - -(use-package ef-themes - :disabled + ;;; keymaps + ;; ebib-index-mode-map + ;; ebib-entry-mode-map + ;; ebib-strings-mode-map + ;; ebib-multiline-mode-map + ;; ebib-search-map + ;; ebib-filters-map + ;; ebib-keywords-map + ;; ebib-reading-list-map + ;; ebib-log-mode-map + :commands (ebib) :config - (setopt ef-themes-headings '((0 variable-pitch light 1.9) - (1 variable-pitch light 1.8) - (2 variable-pitch regular 1.7) - (3 variable-pitch regular 1.6) - (4 variable-pitch regular 1.5) - (5 variable-pitch 1.4) ; absence of weight means `bold' - (6 variable-pitch 1.3) - (7 variable-pitch 1.2) - (t variable-pitch 1.1)) - ef-themes-mixed-fonts t - ef-themes-variable-pitch-ui t)) + + ;; ebib-autogenerate-keys ; default t + (setopt ebib-bibtex-dialect 'biblatex + ebib-use-timestamp t ; create timestamp when entry added to db, note that "When importing or exporting entries, existing timestamps are overwritten." + ebib-filters-default-file (expand-file-name "ebib-filters" user-emacs-directory) ; file location to save filters + ebib-allow-identical-fields t ; merge repeated fields into one, separated by `ebib-keywords-separator' + ebib-file-associations '(("pdf" . nil) + ("ps" . "gs")) ; specify which programs to use for which file types, or remove to open in emacs + ebib-import-target-directory "~/Nextcloud/Library/" ; set directory for `ebib-download-url' and `ebib-import-file' + ;; ebib-default-directory "~/Nextcloud/Library/"; "This mainly determines which directory is the default directory when reading a file name from the user" + ebib-preload-bib-files '("~/Nextcloud/Library/bib/library.bib") ; autoload list of .bib files when started + ;; ebib-bib-search dirs ; specify directory to search for multiple bib files when starting + ;; ebib-extra-fields ; probably not needed + + ;;; UI + ;; ebib-index-columns ; change displayed UI fields + ;; ebib-field-transformation-functions ; new/change UI fields + ;; ebib-field-sort-functions-alist ; customize UI sorting + ;; ebib-edit-fields-functions ; disable/enable completion for different UI fields + ;; ebib-multiline-display-function ; customize multiline values in UI + ;; ebib-multiline-display-max-lines ; customize multiline values in UI + ;; ebib-hidden-fields ; control which UI fields are hidden + ;; ebib-save-indent-as-bibtex ; spaces instead of tabs in bib files, uses value of `bibtex-entry-offset' and `bibtex-field-indentation' to compute # of spaces + ;; ebib-biblatex-ineritances ; customize biblatex inheritance + + ;;; Editing + ;; ebib-save-xrefs-first ; must be enabled for cross-referencing to work, unless sort order changed? + ;; ebib-sort-order ; change sort order of bib file - default is by key - make sure to unset `ebib-save-xrefs-first' + ;; ebib-multiline-major-mode ; customize major mode for `ebib-multiline-edit-mode' + ;; ebib-citation-commands ; might need to customize if using BibTex, which I'm not + ;; ebib-citations-insert-multiple ; enable adding multiple citations at once, likely not needed + ;; ebib-filters-ignore-case ; case-insensitive search filters are default + + ;;; Downloading/Filenames + ;; ebib-name-transform-function ; modify file name that ebib searches for when associating a file with an entry + ;; ebib-url-download-transformations ; sets how URLs are converted when attempting to download + + ;;; Notes + ;; ebib-notes-storage 'multiple-notes-per-file ; probabably set to `multiple-notes-per-file' for org? + ;; ebib-notes-locations ; files/directories containing notes + ;; ebib-notes-default-file ; not used if creating note via org capture, but can still be set + + ;;; Org-capture + ;; ebib-notes-use-org-capture ; set to desired keybind for ebib to skip org capture selection and use the one specified + ;; ebib-org-capture ; function to call org capture from ebib + + ;;; Reading list + ;; ebib-reading-list-file ; location of reading list + ;; ebib-reading-list-todo-marker ; change default todo state for reading list items + ;; ebib-reading-list-template ; change format of reading list items, available options in `ebib-reading-list-template-specifiers' + ;; ebib-reading-list-add-item-function ; change where new items should be added + ;; ebib-reading-list-remove-item ; by default only set to `ebib-reading-list-mark-item-as-done' - doesn't actually remove anything + + ;;; Keywords + ;; ebib-keywords ; specify named keywords always available for completion + + ;;; Display options + ;; ebib-windows ; customization group to change display + + ;;; Copying from ebib + ;; ebib-reference-templates and ebib-citation-template ; change how entries are copied into kill ring + + ;;; org-ebib + ;; org-ebib-link-type 'multiple-notes-per-file ; specify type of link produced by `org-store-link' on bib entries + ) + ) + +(use-package ebib-biblio + :after (ebib biblio) + :bind + (:map ebib-index-mode-map + ("B" . ebib-biblio-import-doi)) + (:map biblio-selection-mode-map + ("e" . ebib-biblio-selection-import)) + :ensure nil) ;; Use `emacs-lsp-booster' with `Eglot' (use-package eglot-booster @@ -544,12 +715,13 @@ ("C-h B" . embark-bindings)) :config ;; Hide the Embark live/completions buffer mode line - (push '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - nil - (window-parameters (mode-line-format . none))) display-buffer-alist) + (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none)))) :defer 3 :init (setopt prefix-help-command #'embark-prefix-help-command)) +;; Integration between `embark' and `consult' (use-package embark-consult :hook (embark-collect-mode . consult-preview-at-point-mode)) @@ -565,13 +737,13 @@ ("Find File" find-file "f") ("Recent Files" consult-recent-file "r") ("Projects" project-switch-project "p") - ("Open Project Buffer" consult-project-buffer "o")))) + ("Treemacs" treemacs "t")))) " " ,(enlight-menu '(("Org" - ("Agenda" (org-agenda nil "a") "a") + ("My Agenda" (org-agenda nil "y") "y") + ("Work Agenda" (org-agenda nil "w") "w") ("Time blocking" org-timeblock "b") - ("Super agenda" (org-agenda nil "u") "u") ("Match Tags" (org-agenda nil "m") "m")))) " " ,(enlight-menu @@ -608,31 +780,41 @@ (use-package hl-todo :bind (:map hl-todo-mode-map - ("C-c t p" . hl-todo-previous) - ("C-c t n" . hl-todo-next) - ("C-c t o" . hl-todo-occur) - ("C-c t i" . hl-todo-insert)) + ("C-c C-t p" . hl-todo-previous) + ("C-c C-t n" . hl-todo-next) + ("C-c C-t o" . hl-todo-occur) + ("C-c C-t i" . hl-todo-insert)) :config (setopt hl-todo-highlight-punctuation ":!?" hl-todo-keyword-faces '(("DEBUG" warning bold) ("HACK" warning bold) ("FIXME" error bold) - ("NOTE" highlight italic) - ("REVIEW" highlight italic) - ("TODO" highlight bold))) + ("NOTE" homoglyph italic) + ("REVIEW" homoglyph italic) + ("TODO" homoglyph bold))) :ensure (hl-todo :depth nil) :hook (prog-mode . hl-todo-mode)) +;; Search `hl-todo' buffers with `consult' +(use-package consult-todo + :after (hl-todo) + :bind + (:map hl-todo-mode-map + ("C-c C-t c" . consult-todo) + ("C-c C-t C-c" . consult-todo-all))) + ;; Icons for `corfu' (use-package kind-icon :disabled :after (corfu) :config - (push #'kind-icon-margin-formatter corfu-margin-formatters) + ;; Using nerd icons below + ;; (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter) (setopt kind-icon-default-face 'corfu-default)) ;; Git -(use-package transient ; Must declare before magit +;; Required by `magit' for menus +(use-package transient :defer t) (use-package magit @@ -641,6 +823,7 @@ (setopt magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1 transient-history-file (expand-file-name "transient-history" xdg_cache_home))) +;; Show `hl-todo' keywords in `magit' status buffers (use-package magit-todos :hook (magit-mode . magit-todos-mode)) @@ -649,10 +832,12 @@ :config (marginalia-mode) :defer 1) -;;; File format/markup support +;; File format/markup support (use-package markdown-mode - :commands (markdown-mode) - :config (setq-local fill-column 120)) + :bind + (:map markdown-mode-map + ("C-M-i" . fill-paragraph)) + :commands (markdown-mode)) ;; Better C++ font locking (use-package modern-cpp-font-lock @@ -668,11 +853,12 @@ (moody-replace-vc-mode) :defer 1) +;; Menu for minor modes in `minor-mode-list' (use-package minions :after (moody) :config (minions-mode)) -;; Various nerd icon packages +;; Nerd icons for various modes & packages (use-package nerd-icons :defer 1) @@ -683,7 +869,8 @@ (use-package nerd-icons-corfu :after (nerd-icons corfu) - :config (push #'nerd-icons-corfu-formatter corfu-margin-formatters)) + :config + (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter)) (use-package nerd-icons-dired :hook (dired-mode . nerd-icons-dired-mode)) @@ -691,6 +878,15 @@ (use-package nerd-icons-ibuffer :hook (ibuffer-mode . nerd-icons-ibuffer-mode)) +;; TODO: pick a theme +(use-package nimbus-theme + :config (load-theme 'nimbus)) + +;; EPUB +(use-package nov + :config (setopt nov-text-width 100) + :mode ("\\.epub\\'" . nov-mode)) + ;; Completion style (use-package orderless :config @@ -701,14 +897,24 @@ orderless-initialism)) :defer 1) -;; Project Management +;; Better PDF viewing and navigation +(use-package pdf-tools + :config + (setopt pdf-info-epdfinfo-program (expand-file-name "pdf-tools/server/epdfinfo" elpaca-builds-directory) + pdf-view-resize-factor 1.1 + pdf-view-display-size 'fit-page) + :ensure (pdf-tools :pre-build ("./server/autobuild") :files (:defaults ("lisp/*" "server/epdfinfo"))) + :hook (pdf-view-mod . pdf-view-midnight-minor-mode) + :mode ("\\.pdf\\'" . pdf-view-mode)) + +;; Project Management - disabled, see `project' built-in package (use-package projectile :disabled :bind ("C-c p" . projectile-command-map) :config (setopt projectile-known-projects-file (expand-file-name "projectile-bookmarks" xdg_cache_home) projectile-project-search-path '(("~/Nextcloud/Projects/src/" . 2))) - (push "*node_modules" projectile-globally-ignored-directories) + (add-to-list 'projectile-globally-ignored-directories "*node_modules") (projectile-mode)) ;; Visualize color names @@ -729,6 +935,39 @@ :init (setopt rust-mode-treesitter-derive t) :commands (rust-mode)) +(use-package treemacs + :defer t + :commands (treemacs) + :config + (setopt treemacs-eldoc-display 'simple + treemacs-hide-dot-git-directory nil + treemacs-is-never-other-window t + treemacs-persist-file (expand-file-name "treemacs-persist" xdg_cache_home) + treemacs-text-scale 2 + treemacs-width-is-initially-locked t) + (add-to-list 'treemacs-litter-directories "./zig-cache") + (treemacs-resize-icons 22) + (treemacs-follow-mode t) + (treemacs-filewatch-mode t) + (treemacs-fringe-indicator-mode 'always) + (treemacs-git-commit-diff-mode t) + (treemacs-git-mode 'deferred) + ;; (treemacs-hide-gitignored-files-mode nil) + (treemacs-indent-guide-mode) + :init + (bind-keys :prefix-map as/treemacs-prefix-map + :prefix "C-c t" + ("0" . treemacs-select-window) + ("1" . treemacs-delete-other-windows) + ("t" . treemacs) + ("d" . treemacs-select-directory) + ("B" . treemacs-bookmark) + ("C-f" . treemacs-find-file) + ("M-t" . treemacs-find-tag))) + +(use-package treemacs-magit + :after (treemacs magit)) + ;; Automatically download tree-sitter grammars (use-package treesit-auto :defer 1 @@ -750,17 +989,18 @@ :url "https://github.com/tree-sitter/tree-sitter-cpp" :revision "v0.21.0" :ext "\\.cpp\\'")) - (push as/c-tsauto-config treesit-auto-recipe-list) - (push as/cpp-tsauto-config treesit-auto-recipe-list) - ;;(add-to-list 'treesit-auto-recipe-list as/cpp-tsauto-config) + (add-to-list 'treesit-auto-recipe-list as/c-tsauto-config) + (add-to-list 'treesit-auto-recipe-list as/cpp-tsauto-config) + ;; (push as/c-tsauto-config treesit-auto-recipe-list) + ;; (push as/cpp-tsauto-config treesit-auto-recipe-list) (treesit-auto-add-to-auto-mode-alist 'all) (global-treesit-auto-mode)) ;; Better undo/redo (use-package undo-fu :bind (("C-z" . undo-fu-only-undo) - ("C-M-z" . undo-fu-only-redo)) - :defer 1) + ("C-S-z" . undo-fu-only-redo)) + :defer 3) (use-package undo-fu-session :after (undo-fu) @@ -775,10 +1015,7 @@ (setopt vertico-cycle t vertico-count 10 vertico-resize t - vertico-scroll-margin 1 - completion-ignore-case t - read-buffer-completion-ignore-case t - read-file-name-completion-ignore-case t) + vertico-scroll-margin 1) :init (vertico-mode)) ;; Web and SSG @@ -808,17 +1045,18 @@ ;; Expand abbreviations to templates (use-package yasnippet :bind - (:map yas-minor-mode-map - ("C-c y i" . yas-insert-snippet) - ("C-c y v" . yas-visit-snippet-file)) - :config (yas-reload-all) - :hook (prog-mode . yas-minor-mode)) + (;; :map yas-minor-mode-map ; need to enable mode with something other than `:commands' + ("C-c y i" . yas-insert-snippet) + ("C-c y v" . yas-visit-snippet-file)) + :config + (yas-reload-all) ; needed if not using `yas-global-mode' + (yas-minor-mode)) (use-package yasnippet-snippets :after (yasnippet)) (use-package consult-yasnippet - :after (yasnippet) + :after (consult yasnippet) :bind (:map yas-minor-mode-map ("C-c y c" . consult-yasnippet))) @@ -834,12 +1072,14 @@ :commands (zig-mode) :config (defun zig-compile () + "Save buffer and compile using `zig build`. Overrides `zig-compile' in 'zig-mode.el'." (interactive) (save-buffer) (zig--run-cmd "build")) - (setq-local fill-column 100) - (setopt zig-format-on-save nil)) + (setopt fill-column 100 + zig-format-on-save nil)) -(push "~/.config/emacs/lisp/" load-path) +(add-to-list 'load-path "~/.config/emacs/lisp/") (require 'init-org) + ;;; init.el ends here diff --git a/dot_config/emacs/lisp/init-org.el b/dot_config/emacs/lisp/init-org.el index 07a63b2..d787b90 100644 --- a/dot_config/emacs/lisp/init-org.el +++ b/dot_config/emacs/lisp/init-org.el @@ -29,6 +29,49 @@ ;;; Code: +(use-package citar + :after (oc) + :config + (setopt citar-bibliography org-cite-global-bibliography + citar-library-paths '("~/Nextcloud/Library/")) + ;; (when (elpaca-installed-p 'citar-embark) + ;; (setopt citar-at-point-function 'embark-act)) + :hook (org-mode . citar-capf-setup)) + +(use-package citar-embark + :after (citar embark) + :config (citar-embark-mode)) + +(use-package citar-org-roam + :after (citar org-roam) + :config + (setopt citar-org-roam-capture-template-key "r" + citar-org-roam-note-title-template "${title} - ${author}" + citar-org-roam-subdir "references") + (citar-org-roam-mode)) + +(use-package citeproc + :after (oc)) + +(use-feature oc + :after (org) + :config + (setopt org-cite-export-processors '((latex biblatex) + (t csl)) + org-cite-global-bibliography '("~/Nextcloud/Library/bib/library.bib")) + (when (elpaca-installed-p 'citar) + (setopt org-cite-activate-processor 'citar + org-cite-follow-processor 'citar + org-cite-insert-processor 'citar))) + +(use-feature oc-csl + :after (citeproc oc) + :config + (let ((zotero_styles (expand-file-name + "Zotero/styles/" (or (getenv "XDG_DATA_HOME") "~/.local/share/")))) + (if (file-directory-p zotero_styles) + (setopt org-cite-csl-styles-dir zotero_styles)))) + (use-package org :bind (:map global-map @@ -38,83 +81,85 @@ :config ;; General options TODO: Comment various options more descriptively (setopt org-agenda-block-separator nil + ;; org-agenda-files (list org-directory) org-agenda-files (directory-files-recursively org-directory "org$") - org-agenda-skip-deadline-if-done t + org-agenda-skip-deadline-if-done nil org-agenda-skip-scheduled-if-deadline-is-shown t - org-agenda-skip-scheduled-if-done t + org-agenda-skip-scheduled-if-done nil org-agenda-start-on-weekday nil org-agenda-tags-column 0 - org-archive-location (concat org-directory "/archive/archive.org::* From %s") + org-archive-location (expand-file-name "archive/%s_archive.org::" org-directory) org-blank-before-new-entry '((heading . auto) (plain-list-item . auto)) + org-deadline-warning-days 5 org-enforce-todo-dependencies t org-fold-catch-invisible-edits 'show-and-error - org-fontify-done-headline nil + org-fontify-done-headline t org-fontify-quote-and-verse-blocks t org-fontify-todo-headline t org-goto-interface 'outline-path-completion org-hide-emphasis-markers t org-insert-heading-respect-content t org-log-done 'time + org-log-into-drawer t + org-log-redeadline 'time + org-log-refile 'note + org-log-reschedule 'time org-outline-path-complete-in-steps nil org-pretty-entities t org-refile-allow-creating-parent-nodes 'confirm org-refile-targets '((nil :maxlevel . 3) (org-agenda-files :maxlevel . 2)) - org-refile-use-outline-path nil + org-refile-use-outline-path 'file org-return-follows-link t org-reverse-note-order t ; Add/refile notes at the beginning of an entry + org-special-ctrl-a/e t org-src-preserve-indentation t org-startup-folded 'fold org-tags-column 0 - org-todo-keywords '((sequence "BLOCKED(b@/!)" "IN-PROGRESS(i@/!)" - "TODO(t)" "WAITING(w@/!)" "|" "DONE(d!)" - "WONT-DO(n@/!)")) - ;; org-todo-keyword-faces '(("TODO" . (icon-button)) - ;; ("IN-PROGRESS" . (tool-bar)) - ;; ("BLOCKED" . (match)) - ;; ("WONT-DO" . (org-done))) + org-todo-keywords '((sequence "TODO(t)" + "NEXT(n)" + "PROG(p)" + "|" + "DONE(d!)" + "WONT(w@/!)") + (sequence "IDEA(i)" "|")) + org-todo-keyword-faces '(("TODO" . (dired-mark)) + ("PROG" . (success)) + ("NEXT" . (dired-special)) + ("WONT" . (org-done))) org-todo-repeat-to-state "TODO") (setq org-capture-templates `( - ("m" "Personal") - ("mj" "Log Entry" - entry (file+datetree ,(concat org-directory "/personal_log.org")) + ("i" "Inbox" entry (file "roam/inbox.org") + "* %?" + :empty-lines 1 + ) + + ("j" "Log Entry" + entry (file+datetree "log.org") "** %?" :empty-lines 1) - ("mn" "Note" - entry (file+headline ,(concat org-directory "/notes.org") "Note to self...") - "** %?" + ("n" "Note" + entry (file+headline "inbox.org" "Notes") + "** %? +:PROPERTIES: +:CREATED: %U +:END:" :empty-lines 1) - ("mt" "Todo" - entry (file+headline ,(concat org-directory "/inbox.org") "You can do it!") - "** TODO [#B] %?\n:CREATED: %U\n " + ("t" "Todo" entry (file+headline "inbox.org" "Tasks") + "** TODO [#B] %? +:PROPERTIES: +:CREATED: %U +:END: +%i\n%a +Notes: " :empty-lines 1) - - ("p" "Projects") - ("pn" "Note" - entry (file+headline ,(concat org-directory "/notes.org") "Project Notes") - "** %?" - :empty-lines 1) - - ("pt" "Todo" - entry (file+headline ,(concat org-directory "/inbox.org") "Project Tasks") - "** TODO [#B] %?\n:CREATED: %U\n%i\n%a\nNotes: " - :empty-lines 1) - - ("w" "Work") - ("wn" "Note" - entry (file+headline ,(concat org-directory "/notes.org") "Work Notes") - "** %?" - :empty-lines 1) - - ("wt" "Todo" - entry (file+headline ,(concat org-directory "/inbox.org") "Work Tasks") - "** TODO [#B] %?\n:CREATED: %U\n%i\n%a\nNotes: " - :empty-lines 1))) + ) + ) ;; Headline appearance (custom-set-faces @@ -133,92 +178,328 @@ ("f" . org-shiftmetaright) ("b" . org-shiftmetaleft) ("v" . org-tags-view) + ("y" . org-todo-yesterday) + ("l" . org-toggle-link-display) ("C-n" . org-priority-down) ("C-p" . org-priority-up))) +(use-feature org-agenda + :after (org) + :bind + (:map org-agenda-mode-map + ("C-c C-r" . as/org-agenda-reconcile-inbox-item)) + :config (setopt org-agenda-start-with-log-mode '(clock closed state)) + :init + (defun as/org-agenda-reconcile-inbox-item () + "Process and refile an org-agenda item" + (interactive) + (org-with-wide-buffer + (org-agenda-set-tags) + (org-agenda-priority) + (if (y-or-n-p "Schedule item?") + (org-agenda-schedule nil nil)) + (org-agenda-refile nil nil t)))) + (use-package org-appear :config (setopt org-appear-autoentities t - org-appear-autolinks t + org-appear-autolinks nil org-appear-autosubmarkers t org-appear-trigger 'on-change) :hook (org-mode . org-appear-mode)) -(use-package org-modern - :disabled +(use-feature org-clock + :after (org) :config - (setopt org-modern-symbol "Hack") + (setopt org-clock-in-resume t + org-clock-in-switch-to-state "PROG" + org-clock-persist t + org-clock-rounding-minutes 5)) + +(use-package org-edna + :config (setopt org-edna-finder-use-cache t) + :hook (org-mode . org-edna-mode)) + +(use-feature org-habit + :after (org) + ;; REVIEW: do I need to add to modules if it loads after org anyway? + :init (add-to-list 'org-modules 'org-habit t)) + +(use-feature org-id + :after (org) + :config + (setopt org-id-link-to-org-use-id t + org-id-locations-file (locate-user-emacs-file "org-id-locations") + org-id-method 'ts + org-id-ts-format "%Y%m%dT%H%M%S.%2N%z") + ;; REVIEW: do I need to add to modules if it loads after org anyway? + :init (add-to-list 'org-modules 'org-id t)) + +(use-package org-modern + :disabled t + :config + (setopt org-modern-block-name nil + org-modern-priority nil + org-modern-symbol "DejaVu Sans" + org-modern-todo nil + org-modern-tag nil + org-modern-timestamp nil) :hook (org-mode . org-modern-mode) (org-agenda-finalize . org-modern-agenda)) +(use-package org-noter + :after (org) + :config + (setopt org-noter-notes-search-path (list (concat org-directory "/roam"))) + (org-noter-enable-org-roam-integration)) + +(use-package org-pomodoro + :after (org-clock) + :commands (org-pomodoro) + :config + ;; (defun as/org-pomodoro-started-todo-update () + ;; "Update todo state when starting a pomodoro" + ;; (when (or (string= org-state "NEXT") + ;; (string= org-state "TODO")) + ;; (org-todo "PROG"))) + ;; (defun as/org-todo-state-maybe-update-pomodoro () + ;; "Update or start a pomodoro when changing todo state + + ;; REVIEW: This could be updated to always trigger a pomodoro change, like when + ;; changing state to PROG. This has the added benefit of making the above function + ;; redundent, as I could just use `org-todo' as a single start and end point for + ;; all pomodoros" + ;; (when (and (or (string= org-state "TODO") + ;; (string= org-state "DONE")) + ;; (org-pomodoro-active-p)) + ;; (org-pomodoro-kill))) + ;; (add-to-list 'org-after-todo-state-change-hook #'as/org-todo-state-change-maybe-kill-pomodoro) + ;; (add-to-list 'org-pomodoro-started-hook #'as/org-pomodoro-started-todo-update) + (setopt alert-user-configuration ; Send mesages to libnotify + '((((:category . "org-pomodoro")) libnotify nil)) + org-pomodoro-audio-player (executable-find "pw-cat") + org-pomodoro-keep-killed-pomodoro-time t + org-pomodoro-length 50 + org-pomodoro-short-break-length 10 + org-pomodoro-manual-break t) + :init + ;; (defun as/org-pomodoro-todo-state-trigger () + ;; "Starts or stops a pomodoro when todo state changes" + ;; (cond + ;; ((when (string= org-state "PROG") + ;; (if (org-pomodoro-active-p) + ;; ((org-pomodoro-kill) + ;; (org-pomodoro nil)) + ;; (org-pomodoro nil))) + ;; ((when (and (or (string= org-state "DONE") (string= org-state "TODO")) + ;; (org-pomodoro-active-p)) + ;; (org-pomodoro-kill)))))) + ;; (with-eval-after-load 'org-clock + ;; (add-to-list 'org-after-todo-state-change-hook #'as/org-pomodoro-todo-state-trigger)) + ) + +(use-package org-roam + :after (org) + :bind + (:map as/org-prefix-map + ("r i" . org-roam-node-insert) + ("r f" . org-roam-node-find) + ("r c" . org-roam-capture) + ;; REVIEW: Not sure if I really need these? + ;; ("r b" . org-roam-buffer-toggle) + ;; ("r d" . org-roam-buffer-display-dedicated) + ;; ("r a a" . org-roam-alias-add) + ;; ("r a r" . org-roam-alias-remove) + ;; ("r r a" . org-roam-ref-add) + ;; ("r r r" . org-roam-ref-remove) + ) + :config + (setopt org-roam-database-connector 'sqlite-builtin) + (setq org-roam-db-gc-threshold most-positive-fixnum) ; doesn't like the new `setopt' + (add-to-list 'display-buffer-alist + '("\\*org-roam\\*" + (display-buffer-in-direction) + (direction . right) + (window-width . 0.33) + (window-height . fit-window-to-buffer))) + (add-to-list 'org-roam-capture-templates + '("r" "reference" plain + "%?" + :target + (file+head "references/${citar-citekey}.org" + "#+title: Notes on: ${note-title}\n") + :empty-lines 1 + :unnarrowed t + )) + (add-to-list 'org-roam-capture-templates + `("p" "project" plain (file ,(concat org-roam-directory + "/templates/projects.org")) + :target + (file+head "projects/%<%Y%m%d%H%M%S>-${slug}.org" + "#+title: ${title}\n#+filetags: projects") + :empty-lines 1 + :unnarrowed t)) + (org-roam-db-autosync-mode) + :ensure (org-roam :files (:defaults "extensions/*")) + :init + (setopt org-roam-directory (file-name-as-directory (expand-file-name "roam" org-directory)))) + (use-package org-super-agenda :hook (org-agenda-mode . org-super-agenda-mode) :init (setq org-agenda-custom-commands - '(("u" "Super agenda" + '(("w" "Work agenda" ((agenda "" ((org-agenda-prefix-format " %?-12t %s") - ;; (org-agenda-prefix-format " %-20:c%?-12t %s") (org-agenda-remove-tags t) (org-agenda-skip-function '(org-agenda-skip-entry-if - 'todo '("DONE" "WONT-DO"))) - (org-agenda-span 5))) + 'todo '("WONT"))) + (org-agenda-span 'day) + (org-habit-show-habits nil))) (alltodo "" ((org-agenda-overriding-header "") (org-agenda-prefix-format " %?-12t %s") (org-agenda-remove-tags t) (org-super-agenda-groups - '((:name "Important" - :priority "A" - :order 0) + '((:discard + (:deadline future + :scheduled future + :category ("house" "me" "notes"))) - (:name "In-Progress" - :todo "IN-PROGRESS" - :order 2) - - (:name "Blocked" - :todo "BLOCKED" - :order 3) + (:name "Done" + :log state + :todo "DONE" + :order 90) (:name "Overdue" :deadline past :scheduled past :order 1) - ;; TODO: Figure out how to organize chores/personal - (:name "Chores" - :tag "chores" - :order 19) + (:name "Inbox" + :category ("inbox" "phone_inbox") + :order 9) - (:name "Personal" - :habit t - :order 17) + (:name "Important" + :priority "A" + :order 0) + + (:name "In-Progress" + :todo "PROG" + :order 2) + + (:name "Next" + :todo "NEXT" + :order 3) + + (:name "Today" + :deadline today + :scheduled today + :time-grid t + :order 4) (:name "Research" :tag "research" :order 7) - (:name "Upcoming" - :deadline future - :scheduled future - :order 18) - (:name "Project Backlog" - :and (:todo "TODO" :tag "project") + :and (:todo "TODO" :tag "projects") :order 5) (:name "General Backlog" :and (:todo "TODO" :priority "B") - :order 6) + :order 8) (:name "Less Important" :priority<= "C" - :order 20)))))))))) + :order 80) + )))))) + ("y" "My agenda" + + ((agenda "" ((org-agenda-prefix-format " %?-12t %s") + (org-agenda-remove-tags t) + (org-agenda-skip-function '(org-agenda-skip-entry-if + 'todo '("WONT") 'regexp '("job"))) + (org-agenda-span 3))) + + (alltodo "" ((org-agenda-overriding-header "") + (org-agenda-prefix-format " %?-12t %s") + (org-agenda-remove-tags t) + (org-super-agenda-groups + '((:discard + (:not + (:category ("house" "me" "notes")))) + + ;; (:discard + ;; (:deadline future :scheduled future)) + + (:name "Overdue" + :deadline past + :scheduled past + :order 1) + + (:name "Upcoming" + :deadline future + :scheduled future + :order 99) + + (:name "Inbox" + :category ("inbox" "phone_inbox") + :order 8) + + (:name "Done" + :todo "DONE" + :order 99) + + (:name "Important" + :priority "A" + :order 0) + + (:name "In-Progress" + :todo "PROG" + :order 2) + + (:name "Next" + :todo "NEXT" + :order 3) + + (:name "Household" + :tag "chores" + :order 6) + + (:name "Routine" + :and (:habit t :not (:tag "chores")) + :order 5) + + (:name "Today" + :deadline today + :scheduled today + :time-grid t + :order 4) + + ;; (:name "Personal projects" + ;; :categery "me" + ;; :order 9) + + (:name "Research" + :tag "research" + :order 7) + + (:name "General Backlog" + :and (:todo "TODO" :priority "B") + :order 50) + + (:name "Less Important" + :priority<= "C" + :order 85) + ))))))))) (use-package org-superstar + ;; :disabled t :config (setopt org-indent-mode-turns-on-hiding-stars nil + org-superstar-headline-bullets-list '("◉" "🞛" "○" "▷") org-superstar-leading-bullet ?\s org-superstar-special-todo-items t) :hook (org-mode . org-superstar-mode))