You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
13 KiB
350 lines
13 KiB
;;; dot-org-mode.el --- -*- lexical-binding: t; -*- |
|
|
|
;;; Commentary: |
|
|
|
;; Load org-mode modules. |
|
|
|
;;; Code: |
|
|
|
;; ----------------------------------------- |
|
;; LaTeX Configuration |
|
|
|
(elpaca-nil (setup tex-mode ; built-in |
|
(:when-loaded |
|
(defun dot/tex-mode-init () "" |
|
(setq indent-tabs-mode t) |
|
(setq tab-width 4) |
|
(setq tex-indent-basic 4)) |
|
(:hook dot/tex-mode-init) |
|
|
|
(with-eval-after-load 'project |
|
(defun compile-latex () |
|
"Compile LaTeX project." |
|
(interactive) |
|
(let ((default-directory (dot/find-project-root))) |
|
(dot/project-save-project-buffers) |
|
(shell-command "make"))))))) |
|
|
|
;; ----------------------------------------- |
|
;; Org Configuration |
|
|
|
;; Base Org. |
|
|
|
(elpaca-nil (setup org ; built-in |
|
(setq org-directory (expand-file-name "documents/org" (getenv "HOME"))) |
|
(setq org-default-notes-file (expand-file-name "notes.org" org-directory)) |
|
(:with-mode before-save |
|
(:hook dot/org-set-last-modified)) |
|
(:when-loaded |
|
(setq org-adapt-indentation nil) |
|
(setq org-ellipsis "⤵") |
|
(setq org-image-actual-width nil) |
|
(setq org-startup-folded nil) |
|
|
|
;; Enable structured template completion |
|
(add-to-list 'org-modules 'org-tempo t) |
|
(add-to-list 'org-structure-template-alist |
|
'("el" . "src emacs-lisp")) |
|
|
|
(defun dot/org-find-or-create-heading (heading level) |
|
"Find or create heading" |
|
(if (re-search-forward (format org-complex-heading-regexp-format |
|
(regexp-quote heading)) |
|
nil t) |
|
(beginning-of-line) |
|
(goto-char (point-max)) |
|
(unless (bolp) (insert "\n")) |
|
(insert (concat (make-string level ?*) " ") heading "\n") |
|
(beginning-of-line 0))) |
|
|
|
(defun dot/org-find-month-week-day () |
|
"Find or create the journal tree for the current week under current month" |
|
(unless (derived-mode-p 'org-mode) |
|
(error "Target buffer \"%s\" for dot/find-org-week should be in Org mode" |
|
(current-buffer))) |
|
(org-capture-put-target-region-and-position) |
|
(widen) |
|
(goto-char (point-min)) |
|
(dot/org-find-or-create-heading "Worklog" 1) |
|
(org-narrow-to-subtree) |
|
(dot/org-find-or-create-heading (format-time-string "%B") 2) ;; month |
|
(org-narrow-to-subtree) |
|
(dot/org-find-or-create-heading (format-time-string "Week %W") 3) |
|
(org-narrow-to-subtree) |
|
(dot/org-find-or-create-heading (format-time-string "%A") 4) ;; day |
|
(org-end-of-subtree)) |
|
|
|
;; Capture templates |
|
(setq org-capture-templates |
|
`(("i" "Clock in" plain (file+function ,(expand-file-name "worklog.org" org-directory) dot/org-find-month-week-day) |
|
"| %<%Y-%m-%d> | IN | %<%H:%M> | %^{Location|Office|Home|Office|Visit} |\n===================================%?" |
|
:immediate-finish t) |
|
("o" "Clock out" plain (file+function ,(expand-file-name "worklog.org" org-directory) dot/org-find-month-week-day) |
|
"===================================\n| %<%Y-%m-%d> | OUT | %<%H:%M> |%?" |
|
:immediate-finish t) |
|
("s" "Switch task" plain (file+function ,(expand-file-name "worklog.org" org-directory) dot/org-find-month-week-day) |
|
"| %<%Y-%m-%d> | %<%H:%M> | %^{Item ID} | X | %^{Description|-} |%?" |
|
:immediate-finish t))) |
|
|
|
;; Keybind functions |
|
(defun dot/org-clock-in () (interactive) (org-capture nil "i")) |
|
(defun dot/org-clock-out () (interactive) (org-capture nil "o")) |
|
(defun dot/org-switch-task () (interactive) (org-capture nil "s")) |
|
|
|
(with-eval-after-load 'evil-commands |
|
(defun dot/org-ret-at-point () |
|
"Org return key at point. |
|
|
|
If point is on: |
|
checkbox -- toggle it |
|
link -- follow it |
|
table -- go to next row |
|
otherwise -- run the default (evil-ret) expression" |
|
(interactive) |
|
(let ((type (org-element-type (org-element-context)))) |
|
(pcase type |
|
('link (if org-return-follows-link (org-open-at-point) (evil-ret))) |
|
((guard (org-at-item-checkbox-p)) (org-toggle-checkbox)) |
|
('table-cell (org-table-next-row)) |
|
(_ (evil-ret)) |
|
)))) |
|
|
|
(defun dot/org-find-file-property (property &optional anywhere) |
|
"Return the position of the file PROPERTY if it exists. |
|
|
|
When ANYWHERE is non-nil, search beyond the preamble." |
|
(save-excursion |
|
(goto-char (point-min)) |
|
(let ((first-heading |
|
(save-excursion |
|
(re-search-forward org-outline-regexp-bol nil t)))) |
|
(when (re-search-forward (format "^#\\+%s:" property) |
|
(if anywhere nil first-heading) |
|
t) |
|
(point))))) |
|
|
|
(defun dot/org-set-file-property (property value) |
|
"Set the file PROPERTY in the preamble." |
|
(when-let ((pos (dot/org-find-file-property property))) |
|
(save-excursion |
|
(goto-char pos) |
|
(if (looking-at-p " ") |
|
(forward-char) |
|
(insert " ")) |
|
(delete-region (point) (line-end-position)) |
|
(insert value)))) |
|
|
|
(defun dot/org-set-last-modified () |
|
"Update the LAST_MODIFIED file property in the preamble." |
|
(when (derived-mode-p 'org-mode) |
|
(dot/org-set-file-property "LAST_MODIFIED" |
|
(format-time-string "[%Y-%m-%d %a %H:%M]"))))))) |
|
|
|
;; Org agenda. |
|
|
|
(elpaca-nil (setup org-agenda ; built-in |
|
(:load-after evil org) |
|
(:when-loaded |
|
(setq org-agenda-files `(,org-directory ,user-emacs-directory)) |
|
(setq org-agenda-span 14) |
|
(setq org-agenda-window-setup 'current-window) |
|
(evil-set-initial-state 'org-agenda-mode 'motion)))) |
|
|
|
;; Org capture. |
|
|
|
(elpaca-nil (setup org-capture ; built-in |
|
;; Org-capture in new tab, rather than split window |
|
(:hook delete-other-windows))) |
|
|
|
;; Org keys. |
|
|
|
(elpaca-nil (setup org-keys ; built-in |
|
(:when-loaded |
|
(setq org-return-follows-link t)))) |
|
|
|
;; Org links. |
|
|
|
(elpaca-nil (setup ol ; built-in |
|
(:when-loaded |
|
;; Do not open links to .org files in a split window |
|
(add-to-list 'org-link-frame-setup '(file . find-file))))) |
|
|
|
;; Org source code blocks. |
|
|
|
(elpaca-nil (setup org-src ; built-in |
|
(:when-loaded |
|
(setq org-edit-src-content-indentation 0) |
|
(setq org-src-fontify-natively t) |
|
(setq org-src-preserve-indentation t) |
|
(setq org-src-tab-acts-natively t) |
|
(setq org-src-window-setup 'current-window)))) |
|
|
|
;; Org exporter. |
|
|
|
(elpaca-nil (setup ox ; built-in |
|
(:when-loaded |
|
(setq org-export-coding-system 'utf-8-unix)))) |
|
|
|
;; Org latex exporter. |
|
|
|
(elpaca-nil (setup ox-latex ; built-in |
|
(:when-loaded |
|
;; Define how minted (highlighted src code) is added to src code blocks |
|
(setq org-latex-listings 'minted) |
|
(setq org-latex-minted-options '(("frame" "lines") ("linenos=true"))) |
|
;; Set 'Table of Contents' layout |
|
(setq org-latex-toc-command "\\newpage \\tableofcontents \\newpage") |
|
;; Add minted package to every LaTeX header |
|
(add-to-list 'org-latex-packages-alist '("" "minted")) |
|
;; Add -shell-escape so pdflatex exports minted correctly |
|
(setcar org-latex-pdf-process (replace-regexp-in-string |
|
"-%latex -interaction" |
|
"-%latex -shell-escape -interaction" |
|
(car org-latex-pdf-process)))))) |
|
|
|
;;; Org Bullets |
|
|
|
(elpaca-setup org-bullets |
|
(:hook-into org-mode)) |
|
|
|
;;; Org Export Packages |
|
|
|
;; HTML exporter. |
|
|
|
(elpaca-setup htmlize |
|
(:when-loaded (setq org-export-html-postamble nil))) |
|
;;org-export-html-postamble-format ; TODO |
|
|
|
;; GitHub flavored Markdown exporter. |
|
|
|
(elpaca-setup ox-gfm) |
|
|
|
;;; Org Roam |
|
|
|
(elpaca-setup emacsql-sqlite-builtin) |
|
|
|
(elpaca-setup org-roam |
|
(:autoload org-roam-node-find) ;; TODO, is this enough? |
|
(setq org-roam-v2-ack t) |
|
(setq org-roam-database-connector 'sqlite-builtin) |
|
(:when-loaded |
|
(setq org-roam-db-location (expand-file-name "org-roam.db" dot-cache-dir)) |
|
(setq org-roam-directory org-directory) |
|
;; Exclude Syncthing backup directory |
|
(setq org-roam-file-exclude-regexp "\\.stversions") |
|
(setq org-roam-verbose nil) |
|
|
|
(setq org-roam-capture-templates |
|
'(("d" "default" plain |
|
"%?" |
|
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+TITLE: ${title}\n#+LAST_MODIFIED:\n#+FILETAGS: %^{File tags||:structure:}\n") |
|
:unnarrowed t))) |
|
|
|
(defun dot/org-roam-node-insert-immediate (arg &rest args) |
|
(interactive "P") |
|
(let ((args (push arg args)) |
|
(org-roam-capture-templates (list (append (car org-roam-capture-templates) |
|
'(:immediate-finish t))))) |
|
(apply #'org-roam-node-insert args))) |
|
|
|
(cl-defmethod org-roam-node-slug ((node org-roam-node)) |
|
"Return the slug of NODE, strip out common words." |
|
(let* ((title (org-roam-node-title node)) |
|
(words (split-string title " ")) |
|
(common-words '("a" "an" "and" "as" "at" "by" "is" "it" "of" "the" "to")) |
|
(title (string-join (seq-remove (lambda (element) (member element common-words)) words) "_")) |
|
(pairs '(("c\\+\\+" . "cpp") ;; convert c++ -> cpp |
|
("c#" . "cs") ;; convert c# -> cs |
|
("[^[:alnum:][:digit:]]" . "_") ;; convert anything not alphanumeric |
|
("__*" . "_") ;; remove sequential underscores |
|
("^_" . "") ;; remove starting underscore |
|
("_$" . "")))) ;; remove ending underscore |
|
(cl-flet ((cl-replace (title pair) |
|
(replace-regexp-in-string (car pair) (cdr pair) title))) |
|
(downcase (-reduce-from #'cl-replace title pairs))))) |
|
|
|
;; Right-align org-roam-node-tags in the completion menu without a length limit |
|
;; Source: https://github.com/org-roam/org-roam/issues/1775#issue-971157225 |
|
(setq org-roam-node-display-template "${title} ${tags:0}") |
|
(setq org-roam-node-annotation-function #'dot/org-roam-annotate-tag) |
|
(defun dot/org-roam-annotate-tag (node) |
|
(let ((tags (mapconcat 'identity (org-roam-node-tags node) " #"))) |
|
(unless (string-empty-p tags) |
|
(concat |
|
(propertize " " 'display `(space :align-to (- right ,(+ 2 (length tags))))) |
|
(propertize (concat "#" tags) 'face 'bold))))) |
|
|
|
(org-roam-setup))) |
|
|
|
;; Enable https://www.orgroam.com/manual.html#org_002droam_002dprotocol, needed to process org-protocol:// links |
|
|
|
(elpaca-nil (setup org-roam-protocol ; org-roam-protocol.el is part of org-roam |
|
(:load-after org-roam) |
|
(:when-loaded |
|
|
|
;; Templates used when creating a new file from a bookmark |
|
(setq org-roam-capture-ref-templates |
|
'(("r" "ref" plain |
|
"%?" |
|
:target (file+head "${slug}.org" "#+TITLE: ${title}\n \n${body}") |
|
:unnarrowed t)))))) |
|
|
|
;; The roam-ref protocol bookmarks to add: |
|
|
|
;; javascript:location.href = |
|
;; 'org-protocol://roam-ref?template=r' |
|
;; + '&ref=' + encodeURIComponent(location.href) |
|
;; + '&title=' + encodeURIComponent(document.title) |
|
;; + '&body=' + encodeURIComponent(window.getSelection()) |
|
|
|
;; Setup org-roam-ui, runs at http://127.0.0.1:35901. |
|
|
|
(elpaca-setup org-roam-ui |
|
(:load-after org-roam) |
|
(:when-loaded |
|
(setq org-roam-ui-follow t) |
|
(setq org-roam-ui-open-on-start t) |
|
(setq org-roam-ui-sync-theme nil) ;; FIXME: Make this work (org-roam-ui-get-theme) |
|
(setq org-roam-ui-update-on-save t))) |
|
|
|
;; Easily searchable .org files via Deft. |
|
|
|
(elpaca-setup deft |
|
(:hook dot/hook-disable-line-numbers) |
|
(:when-loaded |
|
(setq deft-auto-save-interval 0) |
|
(setq deft-default-extension "org") |
|
(setq deft-directory org-directory) |
|
(setq deft-file-naming-rules '((noslash . "-") |
|
(nospace . "-") |
|
(case-fn . downcase))) |
|
(setq deft-new-file-format "%Y%m%d%H%M%S-deft") |
|
(setq deft-recursive t) |
|
;; Exclude Syncthing backup directory |
|
(setq deft-recursive-ignore-dir-regexp (concat "\\.stversions\\|" deft-recursive-ignore-dir-regexp)) |
|
;; Remove file variable -*- .. -*- and Org Mode :PROPERTIES: lines |
|
(setq deft-strip-summary-regexp (concat "\\(^.*-\\*-.+-\\*-$\\|^:[[:alpha:]_]+:.*$\\)\\|" deft-strip-summary-regexp)) |
|
(setq deft-use-filename-as-title nil) |
|
(setq deft-use-filter-string-for-filename t) |
|
|
|
(add-to-list 'deft-extensions "tex") |
|
|
|
;; Start filtering immediately |
|
(with-eval-after-load 'evil |
|
(evil-set-initial-state 'deft-mode 'insert)) |
|
|
|
(defun deft-parse-title (file contents) |
|
"Parse the given FILE and CONTENTS and determine the title." |
|
(if (string-match "#\\+\\(TITLE\\|title\\):\s*\\(.*\\)$" contents) |
|
(match-string 2 contents) |
|
(deft-base-filename file))))) |
|
|
|
;;; Org "Table of Contents" |
|
|
|
;; Generate table of contents without exporting. |
|
(elpaca-setup toc-org |
|
(:hook-into org-mode)) |
|
|
|
(provide 'dot-org-mode) |
|
|
|
;;; dot-org-mode.el ends here
|
|
|