;;; logos.el --- A workflow for org-mode -*- lexical-binding: t -*- ;; Author: Jakub ;; Maintainer: Jakub ;; Version: 1.0 ;; Package-Requires: (straight) ;; Homepage: homepage ;; Keywords: org ;; This file is not part of GNU Emacs ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ;;; Code: (require 'transient) (require 'subr-x) (defun org-mode-init () "This is called through `org-mode-hook', so that I don't need to `add-hook' 20 million times." ;; I want org to display pictures inline. (org-display-inline-images) ;; I don't want monospace text by default. (variable-pitch-mode) ) (add-hook 'org-mode-hook #'org-mode-init) ;; Change a bunch of default org-mode variables. (setq org-log-done 'time ;; I want to keep track of the time tasks are completed. org-return-follows-link t ;; I want to be able to follow links easily. org-hide-emphasis-markers t ;; This is a big one: hide markup syntax like ** and //. org-pretty-entities t ;; These two variables together render latex entities as UTF8, which means that Greek letters, subscript, and superscript are all rendered correctly. org-pretty-entities-include-sub-superscripts t org-enforce-todo-dependencies t ;; Prevent changing a parent to DONE if the children aren't org-enforce-todo-checkbox-dependencies t org-agenda-skip-scheduled-if-done t ;; Don't show done stuff in the agenda view org-agenda-skip-deadline-if-done t org-agenda-skip-timestamp-if-done t org-todo-keywords '((sequence "TODO" "WIP" "DELEGATED" "WAITING" "|" "CANCELLED" "DONE")) ;; Some extra keywords in addition to TODO and DONE org-refile-targets '((org-agenda-files :maxlevel . 2)) ;; Allow any agenda files to be refile targets.z org-agenda-files (apply 'append (mapcar (lambda (dir) (directory-files-recursively dir org-agenda-file-regexp)) '("~/.org/tasks"))) ;; Agenda files location - gathered recursively org-cite-global-bibliography '("~/.org/global.bib") ;; Global bibliography location org-startup-indented t ;; Start indented - this fixed org-indent mode org-attach-id-dir "~/.org/lore/assets" ) ;; Org modern and other interface enhancements (use-package org-modern :hook ((org-mode . org-modern-mode) (org-agenda-finalize . org-modern-agenda))) (use-package org-modern-indent :straight (org-modern-indent :type git :host github :repo "jdtsmith/org-modern-indent") :hook ((org-mode . org-modern-indent-mode))) (use-package wc-mode :hook ((org-mode . wc-mode)) :config (setq wc-modeline-format "WC[%tw]")) ;; Sentence and word navigation and marking (setq sentence-end-double-space nil) ;; Otherwise, M-e is broken in normal writing. ;;; Generally useful things ;; Mark word and sentence (defun mark-whole-word () "Mark the whole word underneath the cursor." (interactive) (forward-word) (set-mark-command nil) (backward-word)) (defun mark-sentence () "Mark the entire sentence underneath the cursor." (interactive) (forward-sentence) (set-mark-command nil) (backward-sentence)) (define-key org-mode-map (kbd "C-@") #'mark-whole-word) (define-key org-mode-map (kbd "M-@") #'mark-sentence) (transient-define-prefix mark-menu-transient () "Transient menu for marking units of text." ["Mark" ("w" "Word" mark-whole-word) ("s" "Sentence" mark-sentence) ("p" "Paragraph" mark-paragraph) ("b" "Buffer" mark-whole-buffer)]) (define-key org-mode-map (kbd "C-c o SPC") #'mark-menu-transient) ;; Section navigation and reordering (defun move-sentence-right (&optional arg) "Move the whole sentence to the right of the next sentence, `ARG' times." (interactive "^p") (or arg (setq arg 1)) (while (> arg 0) (forward-sentence) (backward-sentence) (left-char) ;; Capture previous space (kill-sentence) (forward-sentence) (yank) (backward-sentence) (setq arg (1- arg)))) (defun move-sentence-left (&optional arg) "Move the whole sentence to the left of the previous sentence, `ARG' times." (interactive "^p") (or arg (setq arg 1)) (while (> arg 0) (forward-sentence) (backward-sentence) (left-char) ;; Capture previous space (kill-sentence) (backward-sentence) (left-char) ;; Capture previous space (yank) (backward-sentence) (setq arg (1- arg)))) (defun move-paragraph-up (&optional arg) "Move the whole paragraph above the previous paragraph, `ARG' times." (interactive "^p") (or arg (setq arg 1)) (while (> arg 0) (forward-paragraph) (backward-paragraph) (kill-paragraph nil) (backward-paragraph) (yank) (backward-paragraph) (setq arg (1- arg)))) (defun move-paragraph-down (&optional arg) "Move the whole paragraph above the previous paragraph, `ARG' times." (interactive "^p") (or arg (setq arg 1)) (while (> arg 0) (forward-paragraph) (backward-paragraph) (kill-paragraph nil) (forward-paragraph) (yank) (backward-paragraph) (setq arg (1- arg)))) (define-key org-mode-map (kbd "C-M-") #'move-sentence-left) (define-key org-mode-map (kbd "C-M-") #'move-sentence-right) (define-key org-mode-map (kbd "C-M-") #'move-paragraph-up) (define-key org-mode-map (kbd "C-M-") #'move-paragraph-down) (transient-define-prefix reorder-transient () "Transient menu for text re-ordering commands." ["Move sentence..." ("l" "Left" move-sentence-left) ("r" "Right" move-sentence-right)] ["Move paragraph..." ("u" "Up" move-paragraph-up) ("d" "Down" move-paragraph-down)]) (define-key org-mode-map (kbd "C-c o r") #'reorder-transient) ;; Focused rewriting (defun break-out-sentence () "Breaks a sentence out into it's own buffer for editing." (interactive) (backward-sentence) (kill-sentence) (switch-to-buffer-other-window "*break-out*") (erase-buffer) (yank)) (defun break-out-choose-sentence () "Chooses a sentence from the break-out buffer." (interactive) (backward-sentence) (kill-sentence) (other-window -1) (kill-buffer "*break-out*") (yank)) (defun break-out-dwim () "Either break-out or choose sentency depending on buffer name." (interactive) (if (equal (buffer-name) "*break-out*") (break-out-choose-sentence) (break-out-sentence))) (define-key org-mode-map (kbd "C-c o b") #'break-out-dwim) ;; Powerthesaurus (use-package powerthesaurus :bind ("C-x t" . powerthesaurus-transient)) ;; Harper (when (and (not (equal system-type 'windows-nt)) (locate-file "harper-ls" exec-path)) (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(org-mode . ("harper-ls" "--stdio")))) (setq-default eglot-workspace-configuration '(:harper-ls (:dialect "Australian"))) (add-hook 'org-mode-hook 'eglot-ensure)) ;; Company (defun org-mode-company-hook () "Set company backends for `org-mode' usage." (setq-local company-backends '((company-capf company-dabbrev company-files company-ispell)))) (add-hook 'org-mode-hook #'org-mode-company-hook) ;;; Exobrain ;;; Task management ;; Maintenance and project file creation shortcuts (defun new-maintenance-file (maintenance-area) "Create a new maintenance file for a given `MAINTENANCE-AREA'." (interactive "sMaintenance area is: ") (find-file (concat "~/.org/tasks/maintenance/" maintenance-area ".org")) (insert "\n\n* Calendar\n\n* TO-DO\n\n* Routines\n\n* Archive\n") (goto-char (point-min))) (defun new-project-file (project-name acceptance-criteria) "Create a new project file for a given `PROJECT-NAME', with some `ACCEPTANCE-CRITERIA'." (find-file (concat "~/.org/tasks/projects/" project-name ".org")) (insert (concat "\n\n* Acceptance Criteria\n\n" acceptance-criteria "\n\n* Ideas\n\n* TODO Work\n\n** Calendar\n\n** TO-DO\n\n** Routines\n\n** Archive\n")) (goto-char (point-min))) (defun new-generic-project (project-name acceptance-criteria) "Interactive wrapper for `new-project-file', for generic projects." (interactive "sProject name is: \nsAcceptance criteria is: ") (new-project-file project-name acceptance-criteria)) (defun new-career-project (project-name acceptance-criteria) "Interactive wrapper for `new-project-file', for career projects." (interactive "sProject name is: \nsAcceptance criteria is: ") (new-project-file (concat "career/" project-name) acceptance-criteria)) ;; Captures (defun select-task-area (location headline) "Prompt for a file in ~/.org/tasks/maintenance/ to insert the capture." (let* ((files (directory-files (concat "~/.org/tasks/" location) t "\\.org$")) (choices (mapcar (lambda (x) (string-remove-suffix ".org" x)) (mapcar #'file-name-nondirectory files))) (selected (completing-read "Choose a file: " choices nil t))) (set-buffer (org-capture-target-buffer (concat "~/.org/tasks/" location selected ".org"))) (org-capture-put-target-region-and-position) (widen) (goto-char (point-min)) (setq headline (org-capture-expand-headline headline)) (re-search-forward (format org-complex-heading-regexp-format (regexp-quote headline)) nil t) (forward-line 0))) (defun select-maintenance-area (headline) (select-task-area "maintenance/" headline)) (defun select-project (headline) (select-task-area "projects/" headline)) (defun select-career-project (headline) (select-task-area "projects/career/" headline)) (setq org-capture-templates '(("i" "Idea") ("ii" "Generic" entry (file "~/.org/inbox.org") "* %^{TITLE} :idea: \n:Created: %T\n%?" :empty-lines 1) ("ip" "Project" entry (function (lambda () (select-project "Ideas"))) "* %^{TITLE} \n:Created: %T\n%?" :empty-lines 1) ("ic" "Career" entry (function (lambda () (select-career-project "Ideas"))) "* %^{TITLE} \n:Created: %T\n%?" :empty-lines 1) ("t" "Task") ("tt" "Generic") ("ttt" "Raw" entry (file "~/.org/inbox.org") "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :task:\n:Created: %T\n%?" :empty-lines 1) ("ttm" "Maintenance" entry (function (lambda () (select-maintenance-area "TO-DO"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?" :empty-lines 1) ("ttp" "Project" entry (function (lambda () (select-project "TO-DO"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?" :empty-lines 1) ("ttc" "Career" entry (function (lambda () (select-career-project "TO-DO"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?" :empty-lines 1) ("ts" "Scheduled") ("tss" "Raw" entry (file "~/.org/inbox.org") "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :scheduled:\n:SCHEDULED: %^T\n%?" :empty-lines 1 :time-prompt t) ("tsm" "Maintenance" entry (function (lambda () (select-maintenance-area "Calendar"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?" :empty-lines 1 :time-prompt t) ("tsp" "Project" entry (function (lambda () (select-project "Calendar"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?" :empty-lines 1 :time-prompt t) ("tsc" "Career" entry (function (lambda () (select-career-project "Calendar"))) "* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?" :empty-lines 1 :time-prompt t) ("n" "Note") ("nn" "Raw" entry (file "~/.org/inbox.org") "* %^{TITLE} :note: \n:Created: %T\n%?" :empty-lines 1) ("nm" "Meeting") ("nmm" "Raw" entry (file "~/.org/inbox.org") "* %^{TITLE} :meeting: \n:Created: %T\n%?" :clock-in t :clock-resume t) ("nmc" "Career" entry (function (lambda () (select-career-project "Calendar"))) "* %^{TITLE} \n:Created: %T\n%?" :empty-lines 1 :clock-in t :clock-resume t))) (defun open-inbox () "Opens `~/.org/inbox.org'" (interactive) (find-file "~/.org/inbox.org")) (defun open-maintenance () "Opens `~/.org/tasks/maintenance/'" (interactive) (find-file "~/.org/tasks/maintenance/")) (defun open-projects () "Opens `~/.org/tasks/projects/'" (interactive) (find-file "~/.org/tasks/projects/")) (defun open-career () "Opens `~/.org/tasks/projects/career/'" (interactive) (find-file "~/.org/tasks/projects/career/")) (transient-define-prefix task-management-menu () "Transient menu for task management shortcuts." ["Go To" ("i" "Inbox" open-inbox) ("m" "Maintenance Directory" open-maintenance) ("p" "Projects Directory" open-projects) ("c" "Career Directory" open-career)] ["Create New" ("M" "Maintenance File" new-maintenance-file) ("P" "Project" new-generic-project) ("C" "Career Project" new-career-project)]) (define-key global-map (kbd "C-c o t") #'task-management-menu) (define-key global-map (kbd "C-c a") #'org-agenda) (define-key global-map (kbd "C-c c") #'org-capture) ;; Org roam (use-package org-roam :straight nil :ensure t :init (setq org-roam-v2-ack t) :custom (org-roam-directory (file-truename "~/.org/lore/")) (org-dailies-directory (file-truename "~/.org/lore/refined/journal/")) (org-roam-file-exclude-regexp "\\.git/.*\\|logseq/.*$") (org-roam-completion-everywhere) (org-roam-capture-templates '(("c" "Raw" plain "%?" :target (file+head "raw/${slug}.org" "#+title: ${title}\n") :unnarrowed t) ("r" "Refined") ("rm" "Mini-essay" plain "%?" :target (file+head "refined/wiki/${slug}.org" "#+title: ${title}\n#+author: Jakub Nowak") :unnarrowed t))) (org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :target (file+head "%<%Y-%m-%d>.org" ;; format matches Logseq "#+title: %<%Y-%m-%d>\n")))) :bind (("C-c n l" . org-roam-buffer-toggle) ("C-c n f" . org-roam-node-find) ("C-c n g" . org-roam-graph) ("C-c n i" . org-roam-node-insert) ("C-c n c" . org-roam-capture) :map org-roam-dailies-map ("Y" . org-roam-dailies-capture-yesterday) ("T" . org-roam-dailies-capture-tomorrow)) :bind-keymap ("C-c n d" . org-roam-dailies-map) :config (require 'org-roam-dailies) ;; Ensure the keymap is available (org-roam-db-autosync-mode)) (defun org-roam-create-node-from-headline () "Create an Org-roam note from the current headline and jump to it. Normally, insert the headline’s title using the ’#title:’ file-level property and delete the Org-mode headline. However, if the current headline has a Org-mode properties drawer already, keep the headline and don’t insert ‘#+title:'. Org-roam can extract the title from both kinds of notes, but using ‘#+title:’ is a bit cleaner for a short note, which Org-roam encourages." (interactive) (let ((title (nth 4 (org-heading-components))) (has-properties (org-get-property-block))) (org-cut-subtree) (org-roam-find-file title nil nil 'no-confirm) (org-paste-subtree) (unless has-properties (kill-line) (while (outline-next-heading) (org-promote))) (goto-char (point-min)) (when has-properties (kill-line) (kill-line)))) (transient-define-prefix org-roam-menu () "Transient menu for task management shortcuts." ["Node" ("c" "Capture" org-roam-node-find) ("i" "Insert" org-roam-node-insert) ("r" "Refile" org-roam-create-node-from-headline) ("v" "Visit" org-roam-node-visit) ] ["Dailies" ("t" "Today" org-roam-dailies-capture-today) ("y" "Yesterday" org-roam-dailies-capture-yesterday) ("n" "Tomorrow" org-roam-dailies-capture-tomorrow)] ["Logseq" ("R" "Fix links" logseq-org-roam)]) (define-key global-map (kbd "C-c o n") #'org-roam-menu) ;; Bibliography stuff (setq bibtex-dialect 'biblatex) ;; Use biblatex instead of bibtex. (use-package biblio) (defun bibtex-online-entry () (interactive) (bibtex-entry "Online")) (defun bibtex-misc-entry () (interactive) (bibtex-entry "Misc")) (defun bibtex-software-entry () (interactive) (bibtex-entry "Software")) (transient-define-prefix bibtex-transient-menu () "Transient menu for task management shortcuts." ["Biblio" ("d" "doi.org" doi-insert-bibtex) ("x" "arXiv" arxiv-lookup)] ["Templates" ("o" "Online Resource" bibtex-online-entry) ("m" "Misc" bibtex-misc-entry) ("s" "Software" bibtex-software-entry)]) (define-key bibtex-mode-map (kbd "C-c r") #'bibtex-transient-menu) ;;; logos.el ends here