;;; config.el -*- lexical-binding: t; -*- ;; ============================================================================ ;; Evil - Esc exits insert mode (like real vim) ;; ============================================================================ (define-key evil-insert-state-map (kbd "") 'evil-normal-state) ;; ============================================================================ ;; Server - Allow emacsclient to connect to this GUI frame ;; ============================================================================ (unless (daemonp) (require 'server) (unless (server-running-p) (server-start))) ;; ============================================================================ ;; User Info ;; ============================================================================ (setq user-full-name "Kris Yotam" user-mail-address "kris@krisyotam.com") ;; ============================================================================ ;; Theme & Appearance - Plan9 (light default, dark toggle with nibelung) ;; ============================================================================ (setq doom-theme 'plan9) ;; Disable syntax highlighting (global-font-lock-mode -1) ;; Toggle between light and dark mode (defvar kris/dark-mode nil "Track current theme state.") (defun kris/toggle-dark-mode () "Toggle between plan9 (light) and nibelung-dark themes." (interactive) (if kris/dark-mode (progn (load-theme 'plan9 t) (setq kris/dark-mode nil) (message "Light mode (Plan9)")) (progn (load-theme 'nibelung-dark t) (setq kris/dark-mode t) (message "Dark mode (Nibelung)")))) ;; Keybind: SPC t d (toggle dark mode) (map! :leader :desc "Toggle dark mode" "t d" #'kris/toggle-dark-mode) ;; Force fully opaque frame (no transparency) (set-frame-parameter nil 'alpha-background 100) (add-to-list 'default-frame-alist '(alpha-background . 100)) (set-frame-parameter nil 'alpha '(100 . 100)) (add-to-list 'default-frame-alist '(alpha . (100 . 100))) ;; Font configuration (setq doom-font (font-spec :family "JetBrainsMono Nerd Font" :size 14) doom-variable-pitch-font (font-spec :family "JetBrainsMono Nerd Font" :size 14) doom-big-font (font-spec :family "JetBrainsMono Nerd Font" :size 20)) ;; Line numbers (absolute, not relative) (setq display-line-numbers-type t) ;; Larger fringe for aesthetics (setq-default left-fringe-width 16 right-fringe-width 16) ;; ============================================================================ ;; Custom Dashboard Banner ;; ============================================================================ (defun kris/dashboard-ascii-banner () "Custom ASCII banner for doom dashboard." (let* ((banner '( " " " ██╗ ██╗██████╗ ██╗███████╗ ██╗ ██╗ ██████╗ ████████╗ █████╗ ███╗ ███╗" " ██║ ██╔╝██╔══██╗██║██╔════╝ ╚██╗ ██╔╝██╔═══██╗╚══██╔══╝██╔══██╗████╗ ████║" " █████╔╝ ██████╔╝██║███████╗ ╚████╔╝ ██║ ██║ ██║ ███████║██╔████╔██║" " ██╔═██╗ ██╔══██╗██║╚════██║ ╚██╔╝ ██║ ██║ ██║ ██╔══██║██║╚██╔╝██║" " ██║ ██╗██║ ██║██║███████║ ██║ ╚██████╔╝ ██║ ██║ ██║██║ ╚═╝ ██║" " ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝" " ")) (longest-line (apply #'max (mapcar #'length banner)))) (put-text-property (point) (dolist (line banner (point)) (insert (+doom-dashboard--center +doom-dashboard--width (concat line (make-string (max 0 (- longest-line (length banner))) 32))) "\n")) 'face 'doom-dashboard-banner))) (setq +doom-dashboard-ascii-banner-fn #'kris/dashboard-ascii-banner) ;; Plan9 theme colors for dashboard (custom-set-faces! '(doom-dashboard-menu-title :foreground "#B8996F") ; Muted yellow '(doom-dashboard-menu-desc :foreground "#4A6FA5")) ; Blue - keybindings like SPC f r ;; Custom dashboard menu items (setq +doom-dashboard-menu-sections '(("Open notes" :icon (nerd-icons-faicon "nf-fa-book" :face 'doom-dashboard-menu-title) :when (featurep! :lang org +roam) :action org-roam-node-find) ("Open project" :icon (nerd-icons-octicon "nf-oct-repo" :face 'doom-dashboard-menu-title) :action projectile-switch-project) ("Recently opened files" :icon (nerd-icons-faicon "nf-fa-file_text" :face 'doom-dashboard-menu-title) :action recentf-open-files) ("Open paper feeds" :icon (nerd-icons-faicon "nf-fa-rss" :face 'doom-dashboard-menu-title) :when (featurep! :app elfeed) :action elfeed) ("Open private configuration" :icon (nerd-icons-octicon "nf-oct-gear" :face 'doom-dashboard-menu-title) :when (file-directory-p doom-user-dir) :action doom/open-private-config) ("Open documentation" :icon (nerd-icons-faicon "nf-fa-book" :face 'doom-dashboard-menu-title) :action doom/help))) ;; ============================================================================ ;; Org Mode Base Configuration ;; ============================================================================ (setq org-directory "~/notes/") ;; Pretty org-mode settings (after! org (setq org-hide-emphasis-markers t org-link-descriptive t org-pretty-entities t org-startup-indented t org-startup-folded 'showall ; Show everything expanded org-startup-with-inline-images t org-image-actual-width '(400) org-cycle-open-archived-trees t ; Don't hide archived org-catch-invisible-edits 'show-and-error org-hide-drawer-startup nil ; Never hide drawers on startup org-cycle-hide-drawers nil) ; Never collapse drawers with TAB ;; Force drawers to stay open (remove-hook 'org-cycle-hook #'org-cycle-hide-drawers)) ;; org-superstar for beautiful bullets (after! org-superstar (setq org-superstar-headline-bullets-list '("◉" "○" "◈" "◇" "▣") org-superstar-item-bullet-alist '((?- . ?•) (?+ . ?➤)))) ;; ============================================================================ ;; Org-Roam Configuration (Obsidian-like functionality) ;; ============================================================================ (after! org-roam (setq org-roam-directory "~/notes/" org-roam-dailies-directory "0 - Journal/" org-roam-db-gc-threshold most-positive-fixnum org-roam-completion-everywhere t) ;; Define allowed values for constrained properties (setq org-global-properties '(("STATUS_ALL" . "seed growing evergreen wilted") ("CERTAINTY_ALL" . "certain highly-likely likely possible unlikely highly-unlikely remote impossible") ("IMPORTANCE_ALL" . "1 2 3 4 5 6 7 8 9 10"))) ;; Templates for new notes with full metadata (setq org-roam-capture-templates '(("d" "default" plain "%?" :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ":PROPERTIES: :ID: ${slug} :START: %<%Y-%m-%d> :FINISH: :CERTAINTY: possible :STATUS: seed :IMPORTANCE: 5 :PREVIEW: :END: #+title: ${title} #+filetags: ") :unnarrowed t) ("z" "zettel" plain "%?" :target (file+head "6 - Zettlekasten/%<%Y%m%d%H%M%S>-${slug}.org" ":PROPERTIES: :ID: ${slug} :START: %<%Y-%m-%d> :FINISH: :CERTAINTY: possible :STATUS: seed :IMPORTANCE: 5 :PREVIEW: :END: #+title: ${title} #+filetags: :zettel: * Source * Content * References ") :unnarrowed t) ("t" "topic" plain "%?" :target (file+head "3 - Topics/${slug}.org" ":PROPERTIES: :ID: ${slug} :START: %<%Y-%m-%d> :FINISH: :CERTAINTY: possible :STATUS: seed :IMPORTANCE: 5 :PREVIEW: :END: #+title: ${title} #+filetags: :topic: ") :unnarrowed t))) ;; Daily notes templates (setq org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n#+filetags: :journal:\n\n"))))) ;; ============================================================================ ;; Org-Roam-UI (Graph Visualization) ;; ============================================================================ (use-package! org-roam-ui :after org-roam :config (setq org-roam-ui-sync-theme t org-roam-ui-follow t org-roam-ui-update-on-save t org-roam-ui-open-on-start nil)) ;; ============================================================================ ;; MD-Roam (Support for existing Markdown files) ;; ============================================================================ (use-package! md-roam :after org-roam :config (setq md-roam-file-extension "md") (md-roam-mode 1) ;; Include .md files in org-roam (setq org-roam-file-extensions '("org" "md"))) ;; ============================================================================ ;; LaTeX & Math Rendering ;; ============================================================================ (after! org ;; Use dvisvgm for crisp SVG previews (setq org-preview-latex-default-process 'dvisvgm) ;; Styling: scale to match prose, pure black, NO background (plist-put org-format-latex-options :scale 0.85) (plist-put org-format-latex-options :foreground 'default) (plist-put org-format-latex-options :background 'default) ;; Extended LaTeX packages for complex equations (setq org-latex-packages-alist '(("" "amsmath" t) ; Advanced math environments ("" "amssymb" t) ; Math symbols ("" "amsthm" t) ; Theorem environments ("" "mathtools" t) ; Extensions to amsmath ("" "physics" t) ; Physics notation (bra-ket, derivatives) ("" "siunitx" t) ; SI units ("" "mhchem" t) ; Chemical formulas ("" "cancel" t) ; Strike-through in equations ("" "mathrsfs" t) ; Script fonts (\mathscr) ("" "bm" t) ; Bold math ("" "bbm" t) ; Blackboard bold ("" "esint" t) ; More integral signs ("" "tikz" t) ; Diagrams ("" "pgfplots" t))) ; Plots ;; Custom LaTeX preamble for extra commands (pure black math - no blue) (setq org-format-latex-header (concat org-format-latex-header "\n\\newcommand{\\R}{\\mathbb{R}}" "\n\\newcommand{\\N}{\\mathbb{N}}" "\n\\newcommand{\\Z}{\\mathbb{Z}}" "\n\\newcommand{\\Q}{\\mathbb{Q}}" "\n\\newcommand{\\C}{\\mathbb{C}}" "\n\\newcommand{\\norm}[1]{\\left\\lVert#1\\right\\rVert}" "\n\\newcommand{\\abs}[1]{\\left|#1\\right|}" "\n\\DeclareMathOperator{\\Tr}{Tr}" "\n\\DeclareMathOperator{\\rank}{rank}" "\n\\DeclareMathOperator{\\sgn}{sgn}"))) ;; Auto-toggle LaTeX fragments (use-package! org-fragtog :after org :hook (org-mode . org-fragtog-mode)) ;; ============================================================================ ;; Split Pane Preview (Raw Left, Rendered Right) ;; ============================================================================ (defun kris/org-split-preview () "Open current org file in split view: raw on left, rendered HTML on right." (interactive) (let ((org-file (buffer-file-name))) (delete-other-windows) ;; Left side: raw org (disable latex preview here) (org-latex-preview-clear-overlays) ;; Right side: export and view (split-window-right) (other-window 1) (org-export-to-file 'html (concat (file-name-sans-extension org-file) ".html") nil nil nil nil nil (lambda (file) (browse-url-of-file file))))) (defun kris/org-preview-in-browser () "Export current org buffer to HTML and open in browser." (interactive) (let ((html-file (concat (file-name-sans-extension (buffer-file-name)) ".html"))) (org-export-to-file 'html html-file nil nil nil nil nil) (browse-url-of-file html-file))) ;; ============================================================================ ;; Writing Environment ;; ============================================================================ ;; Olivetti for centered writing - only when single window (use-package! olivetti :config (setq olivetti-body-width 100) (defun kris/olivetti-single-window () "Enable olivetti only when there's a single window." (if (= (count-windows) 1) (olivetti-mode 1) (olivetti-mode -1))) (defun kris/update-olivetti-all-windows () "Update olivetti state in all windows." (dolist (win (window-list)) (with-current-buffer (window-buffer win) (when (derived-mode-p 'org-mode 'markdown-mode 'LaTeX-mode 'text-mode) (kris/olivetti-single-window))))) (add-hook 'window-configuration-change-hook #'kris/update-olivetti-all-windows) (add-hook 'org-mode-hook #'kris/olivetti-single-window) (add-hook 'markdown-mode-hook #'kris/olivetti-single-window) (add-hook 'LaTeX-mode-hook #'kris/olivetti-single-window) (add-hook 'text-mode-hook #'kris/olivetti-single-window)) ;; Mixed pitch mode for prose (use-package! mixed-pitch :hook (org-mode . mixed-pitch-mode)) ;; Markdown rendering with LaTeX math support (after! markdown-mode (setq markdown-enable-math t markdown-fontify-code-blocks-natively t markdown-command "pandoc -f markdown -t html5 --mathjax --standalone")) ;; texfrag — inline LaTeX preview in markdown buffers (like org-fragtog for org) ;; Renders \(...\) inline and \[...\] display math as SVG overlays (use-package! texfrag :after markdown-mode :config (setq texfrag-preview-buffer-at-start t) ;; Use same dvisvgm pipeline and scale as org-mode previews (setq texfrag-LaTeX-frag-alist '(("\\\\(" "\\\\)" "\\(" "\\)") ("\\\\\\[" "\\\\\\]" "\\[" "\\]") ("\\\\begin{equation}" "\\\\end{equation}" "\\begin{equation}" "\\end{equation}") ("\\\\begin{equation\\*}" "\\\\end{equation\\*}" "\\begin{equation*}" "\\end{equation*}") ("\\\\begin{align}" "\\\\end{align}" "\\begin{align}" "\\end{align}") ("\\\\begin{align\\*}" "\\\\end{align\\*}" "\\begin{align*}" "\\end{align*}"))) ;; Inherit LaTeX packages from org config (setq texfrag-header-default (concat "\\documentclass{article}\n" "\\usepackage{amsmath}\n" "\\usepackage{amssymb}\n" "\\usepackage{amsthm}\n" "\\usepackage{mathtools}\n" "\\usepackage{physics}\n" "\\usepackage{mathrsfs}\n" "\\usepackage{bm}\n" "\\usepackage{bbm}\n" "\\usepackage{cancel}\n" "\\newcommand{\\R}{\\mathbb{R}}\n" "\\newcommand{\\N}{\\mathbb{N}}\n" "\\newcommand{\\Z}{\\mathbb{Z}}\n" "\\newcommand{\\Q}{\\mathbb{Q}}\n" "\\newcommand{\\C}{\\mathbb{C}}\n" "\\newcommand{\\norm}[1]{\\left\\lVert#1\\right\\rVert}\n" "\\newcommand{\\abs}[1]{\\left|#1\\right|}\n" "\\DeclareMathOperator{\\Tr}{Tr}\n" "\\DeclareMathOperator{\\rank}{rank}\n" "\\DeclareMathOperator{\\sgn}{sgn}\n" "\\pagestyle{empty}\n" "\\begin{document}\n")) :hook (markdown-mode . texfrag-mode)) ;; Markdown preview inside Emacs using eww (defun kris/markdown-preview () "Preview current markdown buffer in eww with MathJax support." (interactive) (unless (derived-mode-p 'markdown-mode) (user-error "Not in markdown-mode")) (let ((output-file (concat (make-temp-file "md-preview-") ".html")) (input-file (buffer-file-name))) (if input-file ;; File exists - use pandoc on file (progn (save-buffer) (shell-command (format "pandoc '%s' -f markdown -t html5 --mathjax --standalone -o '%s'" input-file output-file)) (eww-open-file output-file)) ;; Buffer not saved - use buffer content (let ((content (buffer-string))) (with-temp-file output-file (insert (shell-command-to-string (format "echo %s | pandoc -f markdown -t html5 --mathjax --standalone" (shell-quote-argument content))))) (eww-open-file output-file))))) ;; Bind to SPC m P (capital P to avoid conflicts) (map! :after markdown-mode :map markdown-mode-map :localleader "P" #'kris/markdown-preview) ;; LaTeX / AUCTeX configuration (after! tex (setq TeX-auto-save t TeX-parse-self t TeX-save-query nil TeX-PDF-mode t TeX-view-program-selection '((output-pdf "PDF Tools")) TeX-source-correlate-mode t TeX-source-correlate-start-server t) (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer)) ;; LaTeX keybindings - use b for build, V for view (map! :after latex :map LaTeX-mode-map :localleader "b" #'TeX-command-run-all ; build/compile "V" #'TeX-view ; View PDF (capital V) "L" #'TeX-recenter-output-buffer) ;; Also add global keybindings for LaTeX (map! :after latex :map LaTeX-mode-map "C-c b" #'TeX-command-run-all "C-c v" #'TeX-view) ;; ============================================================================ ;; Keybindings ;; ============================================================================ (map! :leader (:prefix ("n" . "notes") :desc "Find node" "f" #'org-roam-node-find :desc "Insert link" "i" #'org-roam-node-insert :desc "Capture" "c" #'org-roam-capture :desc "Buffer toggle" "b" #'org-roam-buffer-toggle :desc "Dailies today" "t" #'org-roam-dailies-goto-today :desc "Dailies date" "d" #'org-roam-dailies-goto-date :desc "Graph UI" "g" #'org-roam-ui-open :desc "Sync database" "s" #'org-roam-db-sync) (:prefix ("m" . "math/preview") :desc "Live preview (split)" "p" #'org-preview-html-mode :desc "Preview in browser" "b" #'kris/org-preview-in-browser :desc "Render all LaTeX" "l" #'org-latex-preview :desc "Clear LaTeX renders" "c" #'org-latex-preview-clear-overlays) (:prefix ("p" . "panes") :desc "Open pane (split right)" "o" #'split-window-right :desc "Close pane" "c" #'delete-window :desc "Balance panes" "b" #'balance-windows :desc "Grow pane" "g" #'enlarge-window-horizontally :desc "Shrink pane" "s" #'shrink-window-horizontally :desc "Pane left" "h" #'windmove-left :desc "Pane down" "j" #'windmove-down :desc "Pane up" "k" #'windmove-up :desc "Pane right" "l" #'windmove-right ;; Legacy pane functions :desc "Open note in new pane" "n" #'kris/open-note-in-new-pane :desc "Reset to single pane" "r" #'kris/reset-to-single-pane)) ;; ============================================================================ ;; Sliding Panes / Kris Yotam - Mode ;; ============================================================================ ;; Open notes side-by-side like stacked tabs in Obsidian (defvar kris/pane-width 80 "Width of each sliding pane.") (defun kris/open-note-in-new-pane () "Open a note in a new vertical pane to the right (sliding panes style)." (interactive) (split-window-right kris/pane-width) (other-window 1) (org-roam-node-find)) (defun kris/open-link-in-new-pane () "Open the link at point in a new pane to the right." (interactive) (let ((link (org-element-context))) (when (eq (org-element-type link) 'link) (split-window-right kris/pane-width) (other-window 1) (org-open-at-point)))) (defun kris/close-rightmost-pane () "Close the rightmost pane." (interactive) (let ((win (window-at (- (frame-width) 2) (/ (frame-height) 2)))) (when (and win (not (eq win (selected-window)))) (delete-window win)))) (defun kris/scroll-panes-left () "Scroll all panes left (close leftmost, keep focus)." (interactive) (delete-window (window-at 0 (/ (frame-height) 2)))) (defun kris/reset-to-single-pane () "Close all other panes, keep current note." (interactive) (delete-other-windows)) ;; Follow links into new panes by default (setq org-link-frame-setup '((vm . vm-visit-folder-other-frame) (vm-istrm . vm-visit-imap-folder-other-frame) (gnus . org-gnus-no-new-news) (file . find-file-other-window) ; Opens links in other window (wl . wl-other-frame))) ;; Horizontal scroll/swipe to switch panes (mouse/trackpad) (global-set-key (kbd "") (lambda () (interactive) (other-window -1))) (global-set-key (kbd "") (lambda () (interactive) (other-window 1))) (global-set-key (kbd "") (lambda () (interactive) (other-window -1))) (global-set-key (kbd "") (lambda () (interactive) (other-window 1))) (global-set-key (kbd "") (lambda () (interactive) (other-window -1))) (global-set-key (kbd "") (lambda () (interactive) (other-window 1))) ;; Only show scrollbar on focused window (scroll-bar-mode -1) ; Disable globally first (defun kris/toggle-scrollbar-on-focus () "Show scrollbar only on selected window." (walk-windows (lambda (win) (set-window-scroll-bars win nil nil nil nil)) ; Hide all nil t) (set-window-scroll-bars (selected-window) 12 'right nil nil)) ; Show on current (add-hook 'window-selection-change-functions (lambda (_) (kris/toggle-scrollbar-on-focus))) (add-hook 'window-configuration-change-hook #'kris/toggle-scrollbar-on-focus) ;; ============================================================================ ;; Performance Optimizations ;; ============================================================================ (setq gc-cons-threshold (* 100 1024 1024) ; 100MB read-process-output-max (* 1024 1024) ; 1MB company-idle-delay 0.2 which-key-idle-delay 0.3) ;; ============================================================================ ;; Treemacs (Sidebar file tree) ;; ============================================================================ (after! treemacs (setq treemacs-width 30 treemacs-position 'left)) ;; ============================================================================ ;; Elfeed - Academic Research Feed Reader ;; ============================================================================ (after! elfeed (setq elfeed-feeds '( ;; ======================================== ;; LLMs & AI (PRIMARY FOCUS) ;; Training methods, alignment, advances ;; ======================================== ("http://export.arxiv.org/rss/cs.LG" llm ml arxiv) ; Machine Learning ("http://export.arxiv.org/rss/cs.CL" llm nlp arxiv) ; Computation & Language ("http://export.arxiv.org/rss/cs.AI" llm ai arxiv) ; Artificial Intelligence ("http://export.arxiv.org/rss/cs.NE" llm neural arxiv) ; Neural & Evolutionary ("http://export.arxiv.org/rss/stat.ML" llm stats arxiv) ; Statistical ML ;; ======================================== ;; Marine Biology ;; Cetaceans, orcas, migration, acoustics ;; ======================================== ;; bioRxiv feeds ("http://connect.biorxiv.org/biorxiv_xml.php?subject=zoology" marine bio biorxiv) ("http://connect.biorxiv.org/biorxiv_xml.php?subject=animal_behavior_and_cognition" marine bio biorxiv) ("http://connect.biorxiv.org/biorxiv_xml.php?subject=ecology" marine bio biorxiv) ;; PubMed custom searches (cetaceans, orcas, marine acoustics) ("https://pubmed.ncbi.nlm.nih.gov/rss/search/1234567/?limit=50&utm_campaign=pubmed-2&fc=20231201000000" marine cetacean pubmed) ;; PLOS ONE Marine Mammals ("https://journals.plos.org/plosone/feed/atom?filterSubjects=Marine+mammals" marine plos) ;; Nature Marine Biology ("http://feeds.nature.com/subjects/marine-biology.rss" marine nature) ;; ======================================== ;; Pedagogy & Education ;; STEAM and mathematics education ;; ======================================== ("http://export.arxiv.org/rss/physics.ed-ph" pedagogy physics-ed arxiv) ; Physics Education ("http://export.arxiv.org/rss/math.HO" pedagogy math-ed arxiv) ; Math History/Overview ;; Education journals ("https://www.tandfonline.com/feed/rss/ujme20" pedagogy math-ed journal) ; Journal of Math Ed ("https://stemeducationjournal.springeropen.com/articles/most-recent/rss.xml" pedagogy stem journal))) ;; Default filter - show all unread (setq elfeed-search-filter "@2-weeks-ago +unread") ;; ======================================== ;; Quick filters for each research area ;; ======================================== (defun kris/elfeed-llm () "Show only LLM/AI papers." (interactive) (elfeed-search-set-filter "@6-months-ago +unread +llm")) (defun kris/elfeed-marine () "Show only marine biology papers." (interactive) (elfeed-search-set-filter "@6-months-ago +unread +marine")) (defun kris/elfeed-pedagogy () "Show only pedagogy/education papers." (interactive) (elfeed-search-set-filter "@6-months-ago +unread +pedagogy")) (defun kris/elfeed-all () "Show all unread papers." (interactive) (elfeed-search-set-filter "@2-weeks-ago +unread")) ;; Keybindings for filters (in elfeed search mode) (map! :map elfeed-search-mode-map :n "L" #'kris/elfeed-llm :n "M" #'kris/elfeed-marine :n "P" #'kris/elfeed-pedagogy :n "A" #'kris/elfeed-all ;; Fetch/update feeds :n "g r" #'elfeed-search-fetch :n "g R" #'elfeed-search-update--force :n "R" #'elfeed-update) ;; Entry display settings (setq elfeed-show-entry-switch #'pop-to-buffer elfeed-show-entry-delete #'elfeed-kill-buffer) ;; Larger entry display for reading abstracts (add-hook 'elfeed-show-mode-hook (lambda () (setq-local shr-max-width 80) (visual-line-mode 1)))) ;; ============================================================================ ;; Elfeed-Score - Numerical scoring for entries ;; ============================================================================ (use-package! elfeed-score :after elfeed :config (elfeed-score-enable) (setq elfeed-score-serde-score-file "~/.config/doom/elfeed-score.el") ;; Sort by score in search view (setq elfeed-search-sort-function #'elfeed-score-sort) ;; Show score in search format (setq elfeed-search-print-entry-function #'elfeed-score-print-entry) ;; Keybindings for scoring (map! :map elfeed-search-mode-map :n "=" #'elfeed-score-set-score :n "+" #'(lambda () (interactive) (elfeed-score-set-score (+ (elfeed-score-get-score (elfeed-search-selected :single)) 10))) :n "-" #'(lambda () (interactive) (elfeed-score-set-score (- (elfeed-score-get-score (elfeed-search-selected :single)) 10))))) ;; Elfeed keybindings (map! :leader (:prefix ("e" . "elfeed") :desc "Open Elfeed" "e" #'elfeed :desc "Update feeds" "u" #'elfeed-update :desc "Update feed" "f" #'elfeed-search-fetch :desc "Score explain" "s" #'elfeed-score-scoring-explain-entry)) ;; ============================================================================ ;; Research Workflow - Bibliography & Paper Management ;; Based on Ahmed Khaled's EmacsConf 2021 talk ;; ============================================================================ ;; Library paths (defconst kris/bib-libraries '("~/bib-lib/library.bib" "~/bib-lib/temp-lib.bib")) (defconst kris/main-bib-library (nth 0 kris/bib-libraries)) (defconst kris/pdf-library-paths '("~/bib-lib/pdfs/" "~/bib-lib/temp-pdfs/")) (defconst kris/main-pdf-library (nth 0 kris/pdf-library-paths)) (defconst kris/bib-notes-dir "~/bib-lib/roam/") ;; ============================================================================ ;; arXiv Import Functions (defined globally, not inside use-package) ;; Uses org-ref style completing-read for library selection ;; ============================================================================ ;; Custom arXiv import with library prompt (like org-ref) (defun kris/arxiv-to-library (arxiv-id) "Fetch arXiv paper by ARXIV-ID, prompt for library, download PDF, add BibTeX." (interactive (list (read-string "arXiv ID: " (when (car kill-ring) (let ((kill (car kill-ring))) (when (string-match "\\([0-9]\\{4\\}\\.[0-9]\\{4,5\\}\\)" kill) (match-string 1 kill))))))) (let* ((bibfile (completing-read "Bibfile: " kris/bib-libraries nil t nil nil kris/main-bib-library)) (pdfdir (if (string-match "temp" bibfile) (expand-file-name "temp-pdfs/" "~/bib-lib/") kris/main-pdf-library))) (kris/arxiv-fetch-and-add arxiv-id bibfile pdfdir))) ;; Import arXiv paper from current elfeed entry (press 'a') (defun kris/elfeed-arxiv-to-library () "Import arXiv paper from current elfeed entry to local library. Prompts for which bibliography file to use (like org-ref)." (interactive) (let* ((link (elfeed-entry-link elfeed-show-entry)) (match-idx (string-match "arxiv.org/abs/\\([0-9v.]+\\)" link)) (arxiv-id (when match-idx (replace-regexp-in-string "v[0-9]+$" "" (match-string 1 link))))) (if arxiv-id (let* ((bibfile (completing-read "Bibfile: " kris/bib-libraries nil t nil nil kris/main-bib-library)) (pdfdir (if (string-match "temp" bibfile) (expand-file-name "temp-pdfs/" "~/bib-lib/") kris/main-pdf-library))) (kris/arxiv-fetch-and-add arxiv-id bibfile pdfdir)) (message "No arXiv ID found in this entry")))) ;; Core fetch function - downloads PDF and adds BibTeX entry (defun kris/arxiv-fetch-and-add (arxiv-id bibfile pdfdir) "Fetch arXiv paper ARXIV-ID, save PDF to PDFDIR, add entry to BIBFILE." (let* ((arxiv-id (replace-regexp-in-string "^https?://arxiv.org/abs/" "" arxiv-id)) (url (format "http://export.arxiv.org/api/query?id_list=%s" arxiv-id)) (pdf-url (format "https://arxiv.org/pdf/%s.pdf" arxiv-id))) (message "Fetching arXiv:%s..." arxiv-id) (url-retrieve url (lambda (_status arxiv-id pdf-url bibfile pdfdir) (goto-char (point-min)) (re-search-forward "\n\n") (let* ((xml (libxml-parse-xml-region (point) (point-max))) (entry (car (dom-by-tag xml 'entry))) (title (string-trim (dom-text (car (dom-by-tag entry 'title))))) (authors (mapcar (lambda (a) (dom-text (car (dom-by-tag a 'name)))) (dom-by-tag entry 'author))) (published (dom-text (car (dom-by-tag entry 'published)))) (year (substring published 0 4)) (abs-url (format "https://arxiv.org/abs/%s" arxiv-id)) (first-author (car (split-string (car authors)))) (title-word (car (split-string (downcase title)))) (key (format "%s%s_%s" (downcase first-author) (substring year 2) title-word)) (pdf-file (expand-file-name (concat key ".pdf") pdfdir)) (bib-entry (format "@article{%s, author = {%s}, url = {%s}, year = {%s}, journal = {arXiv}, title = {%s}, } " key (mapconcat #'identity authors " and ") abs-url year title))) (message "Downloading PDF: %s" arxiv-id) (url-copy-file pdf-url pdf-file t) (with-temp-buffer (insert bib-entry) (append-to-file (point-min) (point-max) bibfile)) (message "Added: %s -> %s (bib: %s)" key pdf-file (file-name-nondirectory bibfile)))) (list arxiv-id pdf-url bibfile pdfdir)))) ;; Function to reformat bibliography using biber & rebiber (defun kris/reformat-bib-library (&optional filename) "Format bibliography using biber & rebiber, update with conference versions." (interactive) (or filename (setq filename kris/main-bib-library)) (let ((cmnd (concat (format "rebiber -i %s ; " filename) (format "biber --tool --output_align --output_indent=2 --output_fieldcase=lower --output_file=%s %s" filename filename)))) (async-shell-command cmnd "*bib-reformat*"))) ;; Keybinding for elfeed - press 'a' to add arXiv paper (map! :after elfeed :map elfeed-show-mode-map :n "a" #'kris/elfeed-arxiv-to-library) ;; ============================================================================ ;; org-ref - Bibliography management ;; ============================================================================ (use-package! org-ref :after org :config (setq bibtex-completion-bibliography kris/bib-libraries bibtex-completion-library-path kris/pdf-library-paths bibtex-completion-notes-path kris/bib-notes-dir bibtex-dialect 'biblatex)) ;; ============================================================================ ;; org-roam-bibtex - Link notes to bibliography entries ;; ============================================================================ (use-package! org-roam-bibtex :after org-roam :config (require 'org-ref) (org-roam-bibtex-mode 1) (setq orb-preformat-keywords '("citekey" "title" "url" "author-or-editor" "keywords" "file") orb-process-file-keyword t orb-file-field-extensions '("pdf")) ;; Capture template for bibliography notes ;; Stored in ~/notes/references/ (symlinked to ~/bib-lib/roam/references/) (add-to-list 'org-roam-capture-templates '("r" "bibliography reference" plain "%?" :target (file+head "references/${citekey}.org" ":PROPERTIES: :ROAM_REFS: cite:${citekey} :END: #+title: Notes on ${title} #+filetags: :reference: * Summary * Key Points * Quotes * My Thoughts ") :unnarrowed t))) ;; ============================================================================ ;; Citar - Enhanced citation management ;; ============================================================================ (after! citar (setq citar-bibliography kris/bib-libraries citar-library-paths kris/pdf-library-paths citar-notes-paths (list kris/bib-notes-dir) citar-file-extensions '("pdf" "org" "md")) ;; Use with org-roam-bibtex (setq citar-open-note-function 'orb-citar-edit-note) ;; Open PDFs externally with zathura by default (setq citar-file-open-functions '(("pdf" . (lambda (file) (start-process "zathura" nil "zathura" file))) (t . find-file)))) ;; Function to open PDF inside Emacs with pdf-tools (defun kris/citar-open-pdf-in-emacs () "Open a PDF from the bibliography inside Emacs using pdf-tools." (interactive) (let ((citar-file-open-functions '(("pdf" . find-file) (t . find-file)))) (citar-open-files))) ;; ============================================================================ ;; Zotero Integration via zotxt ;; ============================================================================ (use-package! zotxt :after org :config ;; Enable in org-mode (add-hook 'org-mode-hook #'org-zotxt-mode)) ;; ============================================================================ ;; Research Keybindings ;; ============================================================================ (map! :leader (:prefix ("r" . "research") :desc "Import arXiv paper" "a" #'kris/arxiv-to-library :desc "Open Zotero item" "z" #'org-zotxt-insert-reference-link :desc "Insert citation" "c" #'citar-insert-citation :desc "Open PDF (zathura)" "p" #'citar-open-files :desc "Open PDF (emacs)" "P" #'kris/citar-open-pdf-in-emacs :desc "Open notes" "n" #'citar-open-notes :desc "Reformat bibliography" "r" #'kris/reformat-bib-library :desc "Search bibliography" "s" #'citar-open))