diff options
author | Raúl Benencia <id@rbenencia.name> | 2024-11-07 08:02:52 -0800 |
---|---|---|
committer | Raúl Benencia <id@rbenencia.name> | 2024-11-07 08:02:52 -0800 |
commit | 6651890bf0732223e06cd168341c99115e42c88c (patch) | |
tree | 23c72b6d730b4c50f11559f88eebb2e93d737eb2 /.emacs.d | |
parent | 70310eb66d1b7104c7422b77f79eb1ad45a14845 (diff) |
emacs: add org module literate configuration
Diffstat (limited to '.emacs.d')
-rw-r--r-- | .emacs.d/rul-emacs.org | 586 | ||||
-rw-r--r-- | .emacs.d/rul-lisp/packages/rul-org-agenda.el | 1 | ||||
-rw-r--r-- | .emacs.d/rul-lisp/packages/rul-org.el | 3 |
3 files changed, 588 insertions, 2 deletions
diff --git a/.emacs.d/rul-emacs.org b/.emacs.d/rul-emacs.org index d2bb964..873e890 100644 --- a/.emacs.d/rul-emacs.org +++ b/.emacs.d/rul-emacs.org @@ -884,3 +884,589 @@ Specific to the current window's mode line.") (provide 'rul-modeline) #+end_src +** The =org= module + +My org mode configuration is quite big, so I split it across multiple files. + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org.el" +;;; rul-org.el --- Org configuration +(require 'org) +(require 'org-capture) +(require 'org-protocol) +(require 'org-habit) + +(require 'rul-org-agenda) +(require 'rul-org-journal) + +(setq org-attach-use-inheritance t) +(setq org-cycle-separator-lines 0) +(setq org-hide-leading-stars nil) +(setq org-startup-indented t) +(setq org-edit-src-content-indentation 0) + +(use-package org-modern :ensure t) +(use-package org-pomodoro :ensure t) + +;; (add-hook 'org-mode-hook 'turn-off-auto-fill) +;; (add-hook 'auto-save-hook 'org-save-all-org-buffers) +(add-hook 'org-mode-hook 'visual-line-mode) + +(use-package org-download + :ensure t + :config + (add-hook 'dired-mode-hook 'org-download-enable)) + +(setq org-startup-indented t + org-pretty-entities nil + org-hide-emphasis-markers t + ;; show actually italicized text instead of /italicized text/ + org-fontify-whole-heading-line t + org-fontify-done-headline t + org-fontify-quote-and-verse-blocks t) + +;; ORG BINDINGS ;; +(global-set-key (kbd "C-c l") #'org-store-link) +(global-set-key (kbd "C-c c") #'org-capture) +(global-set-key (kbd "C-c s") #'org-schedule) + +;; ORG STATES ;; +(setq org-todo-keywords + (quote ((sequence "TODO(t)" "MAYBE(m)" "NEXT(n)" "|" "DONE(d)") + (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "MEETING")))) + +(setq org-use-fast-todo-selection t) + +(setq org-todo-state-tags-triggers + (quote (("CANCELLED" ("CANCELLED" . t)) + ("WAITING" ("WAITING" . t)) + ("HOLD" ("WAITING") ("HOLD" . t)) + (done ("WAITING") ("HOLD")) + ("TODO" ("WAITING") ("CANCELLED") ("HOLD")) + ("NEXT" ("WAITING") ("CANCELLED") ("HOLD")) + ("DONE" ("WAITING") ("CANCELLED") ("HOLD"))))) + +(setq org-enforce-todo-dependencies t) +(setq org-log-done (quote time)) +(setq org-log-redeadline (quote time)) +(setq org-log-reschedule (quote time)) + +;; CAPTURE ;; +(setq org-capture-templates + (quote + ( + + ("w" "Todo" entry + (file+headline org-refile-path "Tasks") + "* TODO %?" + :empty-lines 1) + + ("m" + "Capture incoming email" + entry + (file+headline org-refile-path "Incoming") + "* TODO Re: %:description\n\n Source: %u, %a\n" + :empty-lines 1) + + ("e" "Elfeed entry" entry + (file+headline org-refile-path "Read later") + "* %? [[%:link][%:description]]\n %U\n %:description\n") + + ("L" "Web Link" entry + (file+headline org-refile-path "Read later") + "* %?[[%:link][%:description]] \"\")\n %:initial\n \nCaptured On: %U" + ) + + ("l" "Web Link with Selection" entry + (file+headline org-refile-path "Read later") + "* [[%:link][%:description]] \n %:initial\n \nCaptured On: %U") + + ))) + +;; REFILE ;; + +; Targets include this file and any file contributing to the agenda - up to 3 levels deep +(setq org-refile-targets + '((nil :maxlevel . 3) + (org-agenda-files :maxlevel . 3))) + +; Targets complete directly with IDO +(setq org-outline-path-complete-in-steps nil) + +; Allow refile to create parent tasks with confirmation +(setq org-refile-allow-creating-parent-nodes (quote confirm)) + + + +;; ORG REPORTS ;; +; Set default column view headings: Task Effort Clock_Summary +(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM") + +(defun my-org-clocktable-indent-string (level) + (if (= level 1) + "" + (let ((str "^")) + (while (> level 2) + (setq level (1- level) + str (concat str "--"))) + (concat str "-> ")))) + +(advice-add 'org-clocktable-indent-string :override #'my-org-clocktable-indent-string) + +(setq org-clock-clocktable-default-properties '(:maxlevel 4 :scope file :formula %)) + +; global Effort estimate values +; global STYLE property values for completion +(setq org-global-properties (quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00") + ("STYLE_ALL" . "habit")))) + +;; TAGS ;; +; Tags with fast selection keys +(setq org-tag-alist (quote ((:startgroup) + ("@errand" . ?e) + ("@office" . ?o) + ("@home" . ?H) + (:endgroup) + ("WAITING" . ?w) + ("HOLD" . ?h) + ("CANCELLED" . ?c) + ("FLAGGED" . ??)))) + +(setq org-stuck-projects + '("+LEVEL=2+PROJECT/-MAYBE-DONE" ("NEXT") ("@shop") + "\\<IGNORE\\>")) + +; Allow setting single tags without the menu +(setq org-fast-tag-selection-single-key (quote expert)) + +;; org-modern +(add-hook 'org-mode-hook 'org-modern-mode) +(add-hook 'org-agenda-finalize-hook #'org-modern-agenda) + +;; Honor ATTR_ORG attribute. Defaults to image's width if not set. +(setq org-image-actual-width nil) + +(provide 'rul-org) +#+end_src + +#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org-agenda.el" +;;; rul-org-agenda.el --- Org agenda configuration +(require 'org) + +(global-set-key (kbd "<f12>") #'org-agenda) +(global-set-key (kbd "C-c a") #'org-agenda) + +(defun bh/is-project-p () + "Any task with a todo keyword subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task has-subtask)))) + +(defun bh/is-project-subtree-p () + "Any task with a todo keyword that is in a project subtree. +Callers of this function already widen the buffer view." + (let ((task (save-excursion (org-back-to-heading 'invisible-ok) + (point)))) + (save-excursion + (bh/find-project-task) + (if (equal (point) task) + nil + t)))) + +(defun bh/is-task-p () + "Any task with a todo keyword and no subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task (not has-subtask))))) + +(defun bh/is-subproject-p () + "Any task which is a subtask of another project" + (let ((is-subproject) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (while (and (not is-subproject) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq is-subproject t)))) + (and is-a-task is-subproject))) + +(defun bh/list-sublevels-for-projects-indented () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels 'indented) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defun bh/list-sublevels-for-projects () + "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks. + This is normally used by skipping functions where this variable is already local to the agenda." + (if (marker-buffer org-agenda-restrict-begin) + (setq org-tags-match-list-sublevels t) + (setq org-tags-match-list-sublevels nil)) + nil) + +(defvar bh/hide-scheduled-and-waiting-next-tasks t) + +(defun bh/toggle-next-task-display () + (interactive) + (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks)) + (when (equal major-mode 'org-agenda-mode) + (org-agenda-redo)) + (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show"))) + +(defun bh/skip-stuck-projects () + "Skip trees that are not stuck projects" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + nil + next-headline)) ; a stuck project, has subtasks but no next task + nil)))) + +(defun bh/skip-non-stuck-projects () + "Skip trees that are not stuck projects" + ;; (bh/list-sublevels-for-projects-indented) + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (if (bh/is-project-p) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (has-next )) + (save-excursion + (forward-line 1) + (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t)) + (unless (member "WAITING" (org-get-tags-at)) + (setq has-next t)))) + (if has-next + next-headline + nil)) ; a stuck project, has subtasks but no next task + next-headline)))) + +(defun bh/skip-non-projects () + "Skip trees that are not projects" + ;; (bh/list-sublevels-for-projects-indented) + (if (save-excursion (bh/skip-non-stuck-projects)) + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + nil) + ((and (bh/is-project-subtree-p) (not (bh/is-task-p))) + nil) + (t + subtree-end)))) + (save-excursion (org-end-of-subtree t)))) + +(defun bh/skip-non-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-task-p) + nil) + (t + next-headline))))) + +(defun bh/skip-project-trees-and-habits () + "Skip trees that are projects" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits-and-single-tasks () + "Skip trees that are projects, tasks that are habits, single non-project tasks" + (save-restriction + (widen) + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((org-is-habit-p) + next-headline) + ((and bh/hide-scheduled-and-waiting-next-tasks + (member "WAITING" (org-get-tags-at))) + next-headline) + ((bh/is-project-p) + next-headline) + ((and (bh/is-task-p) (not (bh/is-project-subtree-p))) + next-headline) + (t + nil))))) + +(defun bh/skip-project-tasks-maybe () + "Show tasks related to the current restriction. +When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks. +When not restricted, skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (limit-to-project (marker-buffer org-agenda-restrict-begin))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (not limit-to-project) + (bh/is-project-subtree-p)) + subtree-end) + ((and limit-to-project + (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + (t + nil))))) + +(defun bh/skip-project-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + ((bh/is-project-subtree-p) + subtree-end) + ((not (org-entry-is-todo-p)) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-project-tasks () + "Show project tasks. +Skip project and sub-project tasks, habits, and loose non-project tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t))) + (next-headline (save-excursion (or (outline-next-heading) (point-max))))) + (cond + ((bh/is-project-p) + next-headline) + ((org-is-habit-p) + subtree-end) + ((and (bh/is-project-subtree-p) + (member (org-get-todo-state) (list "NEXT"))) + subtree-end) + ((not (bh/is-project-subtree-p)) + subtree-end) + (t + nil))))) + +(defun bh/skip-projects-and-habits () + "Skip trees that are projects and tasks that are habits" + (save-restriction + (widen) + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + (t + nil))))) + +(defun bh/skip-non-subprojects () + "Skip trees that are not projects" + (let ((next-headline (save-excursion (outline-next-heading)))) + (if (bh/is-subproject-p) + nil + next-headline))) + +;; CLOCKING ;; +;; Resume clocking task when emacs is restarted +(org-clock-persistence-insinuate) +;; +;; Show lot of clocking history so it's easy to pick items off the C-F11 list +(setq org-clock-history-length 23) +;; Resume clocking task on clock-in if the clock is open +(setq org-clock-in-resume t) +;; Change tasks to NEXT when clocking in +(setq org-clock-in-switch-to-state 'bh/clock-in-to-next) +;; Separate drawers for clocking and logs +(setq org-drawers (quote ("PROPERTIES" "LOGBOOK"))) +;; Save clock data and state changes and notes in the LOGBOOK drawer +(setq org-clock-into-drawer t) +;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration +(setq org-clock-out-remove-zero-time-clocks t) +;; Clock out when moving task to a done state +(setq org-clock-out-when-done t) +;; Save the running clock and all clock history when exiting Emacs, load it on startup +(setq org-clock-persist t) +;; Do not prompt to resume an active clock +(setq org-clock-persist-query-resume nil) +;; Enable auto clock resolution for finding open clocks +(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running)) +;; Include current clocking task in clock reports +(setq org-clock-report-include-clocking-task t) + + +(setq bh/keep-clock-running nil) + +(defun bh/clock-in-to-next (kw) + "Switch a task from TODO to NEXT when clocking in. +Skips capture tasks, projects, and subprojects. +Switch projects and subprojects from NEXT back to TODO" + (when (not (and (boundp 'org-capture-mode) org-capture-mode)) + (cond + ((and (member (org-get-todo-state) (list "TODO")) + (bh/is-task-p)) + "NEXT") + ((and (member (org-get-todo-state) (list "NEXT")) + (bh/is-project-p)) + "TODO")))) + +(defun bh/find-project-task () + "Move point to the parent (project) task if any" + (save-restriction + (widen) + (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point)))) + (while (org-up-heading-safe) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (goto-char parent-task) + parent-task))) + +(defun bh/punch-in (arg) + "Start continuous clocking and set the default task to the +selected task. If no task is selected set the Organization task +as the default task." + (interactive "p") + (setq bh/keep-clock-running t) + (if (equal major-mode 'org-agenda-mode) + ;; + ;; We're in the agenda + ;; + (let* ((marker (org-get-at-bol 'org-hd-marker)) + (tags (org-with-point-at marker (org-get-tags-at)))) + (if (and (eq arg 4) tags) + (org-agenda-clock-in '(16)) + (bh/clock-in-organization-task-as-default))) + ;; + ;; We are not in the agenda + ;; + (save-restriction + (widen) + ; Find the tags on the current task + (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4)) + (org-clock-in '(16)) + (bh/clock-in-organization-task-as-default))))) + +(defun bh/punch-out () + (interactive) + (setq bh/keep-clock-running nil) + (when (org-clock-is-active) + (org-clock-out)) + (org-agenda-remove-restriction-lock)) + +(defun bh/clock-in-default-task () + (save-excursion + (org-with-point-at org-clock-default-task + (org-clock-in)))) + +(defun bh/clock-in-parent-task () + "Move point to the parent (project) task if any and clock in" + (let ((parent-task)) + (save-excursion + (save-restriction + (widen) + (while (and (not parent-task) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (if parent-task + (org-with-point-at parent-task + (org-clock-in)) + (when bh/keep-clock-running + (bh/clock-in-default-task))))))) + +(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9") + +;; https://stackoverflow.com/a/10091330 +(defun zin/org-agenda-skip-tag (tag &optional others) + "Skip all entries that correspond to TAG. + +If OTHERS is true, skip all entries that do not correspond to TAG." + (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))) + (current-headline (or (and (org-at-heading-p) + (point)) + (save-excursion (org-back-to-heading))))) + (if others + (if (not (member tag (org-get-tags-at current-headline))) + next-headline + nil) + (if (member tag (org-get-tags-at current-headline)) + next-headline + nil)))) + +(defun bh/clock-in-organization-task-as-default () + (interactive) + (org-with-point-at (org-id-find bh/organization-task-id 'marker) + (org-clock-in '(16)))) + +(defun bh/clock-out-maybe () + (when (and bh/keep-clock-running + (not org-clock-clocking-in) + (marker-buffer org-clock-default-task) + (not org-clock-resolving-clocks-due-to-idleness)) + (bh/clock-in-parent-task))) + +(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) + +;; AGENDA VIEW ;; + +;; Do not dim blocked tasks +(setq org-agenda-compact-blocks nil) +(setq org-agenda-dim-blocked-tasks nil) +(setq org-agenda-block-separator 61) + +;; Agenda log mode items to display (closed and state changes by default) +(setq org-agenda-log-mode-items (quote (closed state))) + +; For tag searches ignore tasks with scheduled and deadline dates +(setq org-agenda-tags-todo-honor-ignore-options t) + +(setq org-icalendar-include-body nil) +(setq org-icalendar-include-bbdb-anniversaries t) +(setq org-icalendar-include-todo t) +(setq org-icalendar-use-scheduled '(todo-start event-if-not-todo event-if-todo-not-done)) + +(provide 'rul-org-agenda) +#+end_src diff --git a/.emacs.d/rul-lisp/packages/rul-org-agenda.el b/.emacs.d/rul-lisp/packages/rul-org-agenda.el index bfdc8d3..d280d95 100644 --- a/.emacs.d/rul-lisp/packages/rul-org-agenda.el +++ b/.emacs.d/rul-lisp/packages/rul-org-agenda.el @@ -1,3 +1,4 @@ +;;; rul-org-agenda.el --- Org agenda configuration (require 'org) (global-set-key (kbd "<f12>") #'org-agenda) diff --git a/.emacs.d/rul-lisp/packages/rul-org.el b/.emacs.d/rul-lisp/packages/rul-org.el index 2d22cc7..a498076 100644 --- a/.emacs.d/rul-lisp/packages/rul-org.el +++ b/.emacs.d/rul-lisp/packages/rul-org.el @@ -1,5 +1,4 @@ -;; Debian packages: elpa-org -;; Elpa packages: org-modern +;;; rul-org.el --- Org configuration (require 'org) (require 'org-capture) (require 'org-protocol) |