org-logos/logos.el
2025-07-10 09:04:26 +08:00

548 lines
18 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'org)
(require 'org-agenda)
(require 'org-attach)
(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]"))
(define-key org-mode-map (kbd "C-c <left>") 'windmove-left)
(define-key org-mode-map (kbd "C-c <right>") 'windmove-right)
(define-key org-mode-map (kbd "C-c <up>") 'windmove-up)
(define-key org-mode-map (kbd "C-c <down>") 'windmove-down)
;; 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-<left>") #'move-sentence-left)
(define-key org-mode-map (kbd "C-M-<right>") #'move-sentence-right)
(define-key org-mode-map (kbd "C-M-<up>") #'move-paragraph-up)
(define-key org-mode-map (kbd "C-M-<down>") #'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-roam-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)
("i" "Index" plain
"%?"
:target (file+head "raw/${slug}.org" "#+title: ${title} - Index\n#+filetags: :index:")
:unnarrowed t)
("r" "Refined")
("rm" "Mini-essay" plain
"%?"
:target (file+head "refined/wiki/${slug}.org" "#+title: ${title}\n#+author: Jakub Nowak\n#+filetags: :mini-essay:")
: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 headlines 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 dont 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))))
(use-package logseq-org-roam
:straight (:host github
:repo "sbougerel/logseq-org-roam"
:files ("*.el")))
(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)
("f" "Find" org-roam-node-find) ]
["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)]
["Org Roam UI" ("U" "Enable org-roam-ui-mode" org-roam-ui-mode)])
(define-key global-map (kbd "C-c o n") #'org-roam-menu)
(use-package org-roam-ui
:straight
(:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
:after org-roam
;; normally we'd recommend hooking orui after org-roam, but since org-roam does not have
;; a hookable mode anymore, you're advised to pick something yourself
;; if you don't care about startup time, use
;; :hook (after-init . org-roam-ui-mode)
: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 t))
;; 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)
;;; Literate Programming
;; Racket
(use-package ob-racket
:after org
:config
(add-hook 'ob-racket-pre-runtime-library-load-hook
#'ob-racket-raco-make-runtime-library)
:straight (ob-racket
:type git :host github :repo "hasu/emacs-ob-racket"
:files ("*.el" "*.rkt")))
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(racket . t)
))
;;; logos.el ends here