summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--emacs.d/custom/fcuny-defuns.el225
-rw-r--r--emacs.d/custom/fcuny-org.el182
2 files changed, 335 insertions, 72 deletions
diff --git a/emacs.d/custom/fcuny-defuns.el b/emacs.d/custom/fcuny-defuns.el
index 40bd4ce..88e73a3 100644
--- a/emacs.d/custom/fcuny-defuns.el
+++ b/emacs.d/custom/fcuny-defuns.el
@@ -74,4 +74,229 @@
(decode-coding-string title 'utf-8))
(concat "[[" url "][" title "]]"))))
+(defun fcuny/org-archive-subtree-as-completed ()
+ "Archives the current subtree to today's current journal entry."
+ (interactive)
+ (ignore-errors
+ ;; According to the docs for `org-archive-subtree', the state should be
+ ;; automatically marked as DONE, but I don't notice that:
+ (when (not (equal "DONE" (org-get-todo-state)))
+ (org-todo "DONE")))
+
+ (let* ((org-archive-file (or org-default-completed-file
+ (fcuny/this-month-archive-entry)))
+ (org-archive-location (format "%s::" org-archive-file)))
+ (org-archive-subtree)))
+
+(defun fcuny/this-month-archive-entry ()
+ "Return the full pathname to the month's archive entry file.
+Granted, this assumes each journal's file entry to be formatted
+with year/month, as in `201901' for January 4th.
+
+Note: `org-journal-dir' variable must be set to the directory
+where all good journal entries live, e.g. ~/journal."
+ (let* ((daily-name (format "%s-archive.org" (format-time-string "%Y-%m")))
+ (file-name (concat org-archive-dir daily-name)))
+ (expand-file-name file-name)))
+
+(defun fcuny/org-subtree-region ()
+ "Return a list of the start and end of a subtree."
+ (save-excursion
+ (list (progn (org-back-to-heading) (point))
+ (progn (org-end-of-subtree) (point)))))
+
+(defun fcuny/org-refile-directly (file-dest)
+ "Move the current subtree to the end of FILE-DEST.
+If SHOW-AFTER is non-nil, show the destination window,
+otherwise, this destination buffer is not shown."
+ (interactive "fDestination: ")
+
+ (defun dump-it (file contents)
+ (find-file-other-window file-dest)
+ (goto-char (point-max))
+ (insert "\n" contents))
+
+ (save-excursion
+ (let* ((region (fcuny/org-subtree-region))
+ (contents (buffer-substring (first region) (second region))))
+ (apply 'kill-region region)
+ (save-window-excursion (dump-it file-dest contents)))))
+
+(defun fcuny/org-refile-to-task ()
+ "Refile (move) the current Org subtree to `org-default-tasks-file'."
+ (interactive)
+ (fcuny/org-refile-directly org-default-tasks-file))
+
+(defun fcuny/org-refile-to-task ()
+ "Refile (move) the current Org subtree to `org-default-tasks-file'."
+ (interactive)
+ (fcuny/org-refile-directly org-default-tasks-file))
+
+(defun fcuny/org-refile-to-personal-notes ()
+ "Refile (move) the current Org subtree to `org-default-notes-file'."
+ (interactive)
+ (fcuny/org-refile-directly org-default-notes-file))
+
+(defun fcuny/org-refile-subtree-to-file (dir)
+ "Archive the org-mode subtree and create an entry in the
+directory folder specified by DIR. It attempts to move as many of
+the subtree's properties and other features to the new file."
+ (interactive "DDestination: ")
+ (let* ((props (fcuny/org-subtree-metadata))
+ (head (plist-get props :header))
+ (body (plist-get props :body))
+ (tags (plist-get props :tags))
+ (properties (plist-get props :properties))
+ (area (plist-get props :region))
+ (filename (fcuny/org-filename-from-title head))
+ (filepath (format "%s/%s.org" dir filename)))
+ (apply #'delete-region area)
+ (fcuny/org-create-org-file filepath head body tags properties)))
+
+(defun fcuny/org-set-file-property (key value &optional spot)
+ "Make sure file contains a top-level, file-wide property.
+KEY is something like `TITLE' or `FILETAGS'. This function makes
+sure that the property contains the contents of VALUE, and if the
+file doesn't have the property, it is inserted at either SPOT, or
+if nil,the top of the file."
+ (save-excursion
+ (goto-char (point-min))
+ (let ((case-fold-search t))
+ (if (re-search-forward (format "^#\\+%s:\s*\\(.*\\)" key) nil t)
+ (replace-match value nil nil nil 1)
+
+ (cond
+ ;; if SPOT is a number, go to it:
+ ((numberp spot) (goto-char spot))
+ ;; If SPOT is not given, jump to first blank line:
+ ((null spot) (progn (goto-char (point-min))
+ (re-search-forward "^\s*$" nil t)))
+ (t (goto-char (point-min))))
+
+ (insert (format "#+%s: %s\n" (upcase key) value))))))
+
+(defun fcuny/org-create-org-file (filepath header body tags properties)
+ "Create a new Org file by FILEPATH. The contents of the file is
+pre-populated with the HEADER, BODY and any associated TAGS."
+ (find-file-other-window filepath)
+ (fcuny/org-set-file-property "TITLE" header t)
+ (when tags
+ (fcuny/org-set-file-property "FILETAGS" (s-join " " tags)))
+
+ ;; Insert any drawer properties as #+PROPERTY entries:
+ (when properties
+ (goto-char (point-min))
+ (or (re-search-forward "^\s*$" nil t) (point-max))
+ (--map (insert (format "#+PROPERTY: %s %s" (first it) (second it))) properties))
+
+ ;; My auto-insert often adds an initial headline for a subtree, and in this
+ ;; case, I don't want that... Yeah, this isn't really globally applicable,
+ ;; but it shouldn't cause a problem for others.
+ (when (re-search-forward "^\\* [0-9]$" nil t)
+ (replace-match ""))
+
+ (delete-blank-lines)
+ (goto-char (point-max))
+ (insert "\n")
+ (insert body))
+
+(defun fcuny/org-subtree-metadata ()
+ "Return a list of key aspects of an org-subtree. Includes the
+following: header text, body contents, list of tags, region list
+of the start and end of the subtree."
+ (save-excursion
+ ;; Jump to the parent header if not already on a header
+ (when (not (org-at-heading-p))
+ (org-previous-visible-heading 1))
+
+ (let* ((context (org-element-context))
+ (attrs (second context))
+ (props (org-entry-properties)))
+
+ (list :region (list (plist-get attrs :begin) (plist-get attrs :end))
+ :header (plist-get attrs :title)
+ :tags (fcuny/org-get-subtree-tags props)
+ :properties (fcuny/org-get-subtree-properties attrs)
+ :body (fcuny/org-get-subtree-content attrs)))))
+
+(defun fcuny/org-filename-from-title (title)
+ "Creates a useful filename based on a header string, TITLE.
+For instance, given the string: What's all this then?
+ This function will return: whats-all-this-then"
+ (let* ((no-letters (rx (one-or-more (not alphanumeric))))
+ (init-try (->> title
+ downcase
+ (replace-regexp-in-string "'" "")
+ (replace-regexp-in-string no-letters "-"))))
+ (string-trim init-try "-+" "-+")))
+
+(defun fcuny/org-get-subtree-content (attributes)
+ "Return the contents of the current subtree as a string."
+ (let ((header-components '(clock diary-sexp drawer headline inlinetask
+ node-property planning property-drawer section)))
+
+ (goto-char (plist-get attributes :contents-begin))
+
+ ;; Walk down past the properties, etc.
+ (while
+ (let* ((cntx (org-element-context))
+ (elem (first cntx))
+ (props (second cntx)))
+ (when (member elem header-components)
+ (goto-char (plist-get props :end)))))
+
+ ;; At this point, we are at the beginning of what we consider
+ ;; the contents of the subtree, so we can return part of the buffer:
+ (buffer-substring-no-properties (point) (org-end-of-subtree))))
+
+(defun fcuny/org-get-subtree-properties (attributes)
+ "Return a list of tuples of a subtrees properties where the keys are strings."
+
+ (defun symbol-upcase? (sym)
+ (let ((case-fold-search nil))
+ (string-match-p "^:[A-Z]+$" (symbol-name sym))))
+
+ (defun convert-tuple (tup)
+ (let ((key (first tup))
+ (val (second tup)))
+ (list (substring (symbol-name key) 1) val)))
+
+ (->> attributes
+ (-partition 2) ; Convert plist to list of tuples
+ (--filter (symbol-upcase? (first it))) ; Remove lowercase tuples
+ (-map 'convert-tuple)))
+
+(defun fcuny/org-get-subtree-tags (&optional props)
+ "Given the properties, PROPS, from a call to
+`org-entry-properties', return a list of tags."
+ (unless props
+ (setq props (org-entry-properties)))
+ (let ((tag-label "ALLTAGS" ))
+ (-some->> props
+ (assoc tag-label)
+ cdr
+ substring-no-properties
+ (s-split ":")
+ (--filter (not (equalp "" it))))))
+
+(defun fcuny/org-refile-to-projects-dir ()
+ "Move the current subtree to a file in the `projects' directory."
+ (interactive)
+ (fcuny/org-refile-subtree-to-file org-default-projects-dir))
+
+(defun fcuny/org-refile-to-personal-dir ()
+ "Move the current subtree to a file in the `personal' directory."
+ (interactive)
+ (fcuny/org-refile-subtree-to-file org-default-personal-dir))
+
+(defun fcuny/org-refile-to-incubate ()
+ "Refile (move) the current Org subtree to `org-default-incubate-fire'."
+ (interactive)
+ (fcuny/org-refile-directly org-default-incubate-file))
+
+(defun fcuny/org-refile-to-technical-dir ()
+ "Move the current subtree to a file in the `technical' directory."
+ (interactive)
+ (fcuny/org-refile-subtree-to-file org-default-technical-dir))
+
(provide 'fcuny-defuns)
diff --git a/emacs.d/custom/fcuny-org.el b/emacs.d/custom/fcuny-org.el
index 0bdac7b..9f10720 100644
--- a/emacs.d/custom/fcuny-org.el
+++ b/emacs.d/custom/fcuny-org.el
@@ -1,13 +1,19 @@
(require 'fcuny-vars)
+(use-package dash
+ :ensure t)
+
(use-package org
+ :after (dash)
:ensure t
- :hook ((org-mode . visual-line-mode)
- (org-mode . org-indent-mode))
+ :hook ((org-mode . visual-line-mode)
+ (org-mode . org-indent-mode)
+ (org-capture-mode-hook . delete-other-windows))
:bind (("C-c c" . org-capture)
- ("C-c a" . org-agenda))
+ ("C-c a" . org-agenda)
+ ("C-c o" . hydra-org-menu/body))
:config
(progn ()
@@ -18,32 +24,49 @@
(emacs-lisp . t))))
:custom
- ;; priorities. I use:
- ;; -1 important + urgent
- ;; -2 important + non-urgent
- ;; -3 non-important + urgent
- ;; -4 non-important + non-urgent
- (org-highest-priority ?1)
- (org-default-priority ?4)
- (org-lowest-priority ?4)
-
+ ;; cosmetic
(org-pretty-entities t)
- (org-src-fontify-natively t)
- ;; prevent the conversion of spaces into tabs (necessary for Python code exports)
- (org-src-preserve-indentation t)
- (org-edit-src-content-indentation t)
-
(org-startup-indented t)
+ (org-tags-column -120)
+
+ ;; files
(org-directory (expand-file-name "~/Documents/notebooks"))
- (org-default-inbox-file (concat org-directory "/gtd.org"))
- (org-default-notes-file (concat org-directory "/gtd.org"))
+ (org-archive-dir (concat org-directory "/archive/"))
+
+ (org-default-projects-dir (concat org-directory "/projects"))
+ (org-default-personal-dir (concat org-directory "/personal"))
+ (org-default-completed-dir (concat org-directory "/completed"))
+ (org-default-technical-dir (concat org-directory "/technical"))
+
+ (org-default-completed-file nil)
+ (org-default-inbox-file (concat org-directory "/inbox.org"))
+ (org-default-tasks-file (concat org-directory "/tasks.org"))
+ (org-default-incubate-file (concat org-directory "/incubate.org"))
+ (org-default-media-file (concat org-directory "/media.org"))
+ (org-default-notes-file (concat org-directory "/notes.org"))
+
+ ;; how to manage tasks
+ (org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)")
+ (sequence "|" "CANCELED(c)")))
+
+ ;; agenda related
(org-agenda-start-on-weekday 1)
- (org-tags-column -120)
+ (org-agenda-files `(,org-default-projects-dir
+ ,org-default-inbox-file
+ ,org-default-tasks-file))
+
+ ;; org babel related
+ ;; prevent the conversion of spaces into tabs (necessary for Python code exports)
+ (org-src-fontify-natively t)
+ (org-src-preserve-indentation t)
+ (org-edit-src-content-indentation t)
+ ;; behavior
;; I want to follow links on RET
(org-return-follows-link t)
- (org-blank-before-new-entry (quote ((heading . nil)
- (plain-list-item . nil))))
+ (org-enforce-todo-dependencies t)
+ (org-export-with-toc nil)
+ (org-export-with-section-numbers nil)
;; A few abbreviations I use regularly
(org-link-abbrev-alist
@@ -52,62 +75,28 @@
("ph" . "https://phabricator.twitter.biz/%s")
("go" . "http://go/%s")))
- ;; The sequence I want to use to navigate tasks
- (org-todo-keywords
- '((sequence "TODO(t)" "NEXT(n)" "STARTED(s)" "|" "DONE(d)" "CANCELED(c)")))
-
- (org-enforce-todo-dependencies t)
-
- ;; list of files to use for the agenda
- (org-agenda-files (list (expand-file-name "tasks.org" org-directory)
- (expand-file-name "projects.org" org-directory)
- (expand-file-name "tw-journal.org" org-directory)))
-
- (org-export-with-toc nil)
- (org-export-with-section-numbers nil)
+ ;; entries
+ (org-blank-before-new-entry (quote ((heading . nil)
+ (plain-list-item . nil))))
;; see https://github.com/abo-abo/swiper/issues/986
(org-goto-interface 'outline-path-completion)
- (org-outline-path-complete-in-steps nil)
+
+ ;; refile and capture
(org-refile-use-outline-path 'file)
+ (org-outline-path-complete-in-steps nil)
(org-refile-allow-creating-parent-nodes 'confirm)
- (org-refile-targets '((org-agenda-files :maxlevel . 4)))
-
- ;; for the agenda, I want to see tasks in order of priorities.
- (org-agenda-custom-commands
- '(("c" "Agenda by priorities"
- ((tags-todo "PRIORITY=\"1\""
- ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
- (org-agenda-overriding-header "important and urgent:")))
- (tags-todo "PRIORITY=\"2\""
- ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
- (org-agenda-overriding-header "important and non-urgent:")))
- (tags-todo "PRIORITY=\"3\""
- ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
- (org-agenda-overriding-header "non-important and urgent:")))
- (agenda "" ((org-agenda-ndays 1)))
- (alltodo "" ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done)))))
- ((org-agenda-compact-blocks t)))))
+ (org-refile-targets (append '((org-default-media-file :level . 1)
+ (org-default-notes-file :level . 0))
+ (->>
+ (directory-files org-default-projects-dir nil ".org$")
+ (-remove-item (file-name-base org-default-media-file))
+ (--remove (s-starts-with? "." it))
+ (--map (format "%s/%s" org-default-projects-dir it))
+ (--map (cons it (cons :level 1))))))
(org-capture-templates
- `(;; templates for general references, links, etc. They can be relevant for both work and personal learning.
- ("r" "Reference" entry
- (file ,(concat org-directory "/kb.org") "Reference")
- "* %^{TITLE} %^G\n:PROPERTIES:\n:Created: %U\n:END:\n%?")
- ("e" "Event" entry
- (file+headline ,(concat org-directory "/notes.org") "Event")
- "* %^{EVENT}\n:PROPERTIES:\n:Created: %U\n:Location: %^{prompt}\n:END:\n%?")
- ("b" "Bookmark" entry
- (file+headline ,(concat org-directory "/notes.org") "Bookmark")
- "* %(fcuny/get-page-title (current-kill 0)) %^g\n:PROPERTIES:\n:Created: %U\n:ReadLater: %^{read later|Yes|No}\n:Effort: %^{effort|1:00|0:05|0:15|0:30|2:00|4:00|5:00}\n:END:\n%?")
- ("q" "Quotes" entry
- (file+headline ,(concat org-directory "/notes.org") "Quote")
- "* %^{TITLE}\n:PROPERTIES:\n:Created: %U\n:Page: %^{page}\n:END:\n%?")
- ("d" "Debug" entry
- (file ,(concat org-directory "/debug.org") "Debug")
- "* %^{TITLE}\n:PROPERTIES:\n:Created: %U\n:END:\n%?")
-
- ;; templates for personal things only.
+ `(;; templates for personal things only.
("j" "Personal Journal" entry
(file+olp+datetree ,(concat org-directory "/journal.org"))
"* %U %?\n"
@@ -133,9 +122,58 @@
("t" "task entry" entry
(file ,org-default-inbox-file )
- "* TODO [#4] %? %^G\n:PROPERTIES:\n:CREATED: %U\n:END:\n\n%i\n\nfrom: %a"))))
+ "* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n%i\n\nfrom: %a")))
+
+ :config
+ (defhydra hydra-org-menu (:columns 6)
+ "
+ ^Navigate^ ^Refile^ ^Move^ ^Update^ ^Go To^ ^Dired^
+ ^^^^^^^^^^---------------------------------------------------------------------------------------
+ _k_: ↑ previous _t_: tasks _m X_: projects _T_: todo task _g t_: tasks _g X_: projects
+ _j_: ↓ next _p_: personal _m P_: personal _S_: schedule _g x_: inbox _g P_: personal
+ _c_: archive _r_: refile _m T_: technical _D_: deadline _g n_: notes _g T_: technical
+ _d_: delete _i_: incubate _R_: rename _g C_: completed
+ "
+ ("<up>" org-previous-visible-heading)
+ ("<down>" org-next-visible-heading)
+ ("k" org-previous-visible-heading)
+ ("j" org-next-visible-heading)
+ ("c" fcuny/org-archive-subtree-as-completed)
+ ("d" org-cut-subtree)
+ ("t" fcuny/org-refile-to-task)
+ ("i" fcuny/org-refile-to-incubate)
+ ("p" fcuny/org-refile-to-personal-notes)
+ ("r" org-refile)
+ ("m X" fcuny/org-refile-to-projects-dir)
+ ("m P" fcuny/org-refile-to-personal-dir)
+ ("m T" fcuny/org-refile-to-technical-dir)
+ ("T" org-todo)
+ ("S" org-schedule)
+ ("D" org-deadline)
+ ("R" org-rename-header)
+ ("g t" (find-file-other-window org-default-tasks-file))
+ ("g i" (find-file-other-window org-default-incubate-file))
+ ("g x" (find-file-other-window org-default-inbox-file))
+ ("g c" (find-file-other-window org-default-completed-file))
+ ("g n" (find-file-other-window org-default-notes-file))
+ ("g X" (dired org-default-projects-dir))
+ ("g P" (dired org-default-personal-dir))
+ ("g T" (dired org-default-technical-dir))
+ ("g C" (dired org-default-completed-dir))
+ ("[\t]" (org-cycle))
+ ("s" (org-save-all-org-buffers) "save")
+ ("q" nil "quit")))
(use-package htmlize
:ensure t)
+(use-package org-journal
+ :ensure t
+ :after (org)
+ :custom
+ (org-journal-file-format "%Y-%m.org")
+ (org-journal-dir "~/Documents/notebooks/journal/")
+ (org-journal-date-format "#+TITLE: Monthly Journal Entry - %e %b %Y (%A)")
+ (org-journal-file-type `monthly))
+
(provide 'fcuny-org)