Commit tangled versions of my website code
This makes this project self-contained for others to steal.
This commit is contained in:
parent
2dcac6e761
commit
2380d70508
4 changed files with 622 additions and 0 deletions
107
elisp/beep.el
Normal file
107
elisp/beep.el
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
;;; beep.el --- A literate programming for alerting after long projects. -*- lexical-binding: t; -*-
|
||||||
|
;;
|
||||||
|
;; Copyright (C) 2020 Howard X. Abrams
|
||||||
|
;;
|
||||||
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
|
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
|
||||||
|
;; Created: December 23, 2020
|
||||||
|
;;
|
||||||
|
;; This file is not part of GNU Emacs.
|
||||||
|
;;
|
||||||
|
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||||
|
;; ~/website/Technical/Emacs/beep-for-emacs.org
|
||||||
|
;; And tangle the file to recreate this one.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defvar beep-alert-sound-file
|
||||||
|
(expand-file-name "~/other/hamacs/beep-notify.wav")
|
||||||
|
"A WAV or AU file used at the completion of a function.")
|
||||||
|
|
||||||
|
;; My replacement in case we can't play internal sounds:
|
||||||
|
(defun beep--beep ()
|
||||||
|
"Play a default notification sound file.
|
||||||
|
Customize the variable, `beep-alert-sound-file' to adjust the sound."
|
||||||
|
(if (fboundp 'play-sound-internal)
|
||||||
|
(play-sound-file beep-alert-sound-file)
|
||||||
|
(call-process-shell-command (format "afplay %s &" beep-alert-sound-file) nil 0)))
|
||||||
|
|
||||||
|
(defvar beep-speech-executable "say %s"
|
||||||
|
"An OS-dependent shell string to speak. Replaces `%s' with a phrase.")
|
||||||
|
|
||||||
|
(defun beep--speak (phrase)
|
||||||
|
"Call a program to speak the string, PHRASE.
|
||||||
|
Customize the variable, `beep-speech-executable'."
|
||||||
|
(let ((command (format beep-speech-executable phrase)))
|
||||||
|
(shell-command command)))
|
||||||
|
|
||||||
|
(defun beep--when-finished (phrase)
|
||||||
|
"Notify us with string, PHRASE, to grab our attention.
|
||||||
|
Useful after a long process has completed, but use sparingly,
|
||||||
|
as this can be pretty distracting."
|
||||||
|
(message phrase)
|
||||||
|
(when (functionp 'alert)
|
||||||
|
(alert phrase :title "Completed"))
|
||||||
|
(beep--beep)
|
||||||
|
(beep--speak phrase))
|
||||||
|
|
||||||
|
(defun compile-and-notify ()
|
||||||
|
"Call `counsel-compile' and notify us when finished.
|
||||||
|
See `beep--when-finished' for details."
|
||||||
|
(interactive)
|
||||||
|
(let ((root (projectile-project-root)))
|
||||||
|
(counsel-compile root)
|
||||||
|
(beep--when-finished "The compile command has finished.")))
|
||||||
|
|
||||||
|
(defvar beep-func-too-long-time 5
|
||||||
|
"The number of seconds a function runs before it is considered taking too much time, and needing to be alerted when it has finished.")
|
||||||
|
|
||||||
|
(defun beep--after-function (func)
|
||||||
|
"Call the function, FUNC, interactively, and notify us when completed."
|
||||||
|
(let ((start-time (current-time))
|
||||||
|
duration)
|
||||||
|
(call-interactively func)
|
||||||
|
(setq duration (thread-first
|
||||||
|
(current-time)
|
||||||
|
(time-subtract start-time)
|
||||||
|
decode-time
|
||||||
|
first))
|
||||||
|
(when (> duration beep-func-too-long-time)
|
||||||
|
(beep--when-finished (format "The function, %s, has finished." func)))))
|
||||||
|
|
||||||
|
(defun recompile-and-notify ()
|
||||||
|
"Call `recompile' and notify us when finished.
|
||||||
|
See `beep--when-finished' for details."
|
||||||
|
(interactive)
|
||||||
|
(beep--after-function 'recompile))
|
||||||
|
|
||||||
|
(global-set-key (kbd "C-c c") 'recompile-and-notify)
|
||||||
|
(global-set-key (kbd "C-c C") 'compile-and-notify)
|
||||||
|
|
||||||
|
(defun beep-when-runs-too-long (orig-function &rest args)
|
||||||
|
"Notifies us about the completion of ORIG-FUNCTION.
|
||||||
|
Useful as after advice to long-running functions, for instance:
|
||||||
|
|
||||||
|
(advice-add 'org-publish :around #'beep-when-runs-too-long)"
|
||||||
|
(let ((start-time (current-time))
|
||||||
|
duration)
|
||||||
|
(apply orig-function args)
|
||||||
|
(setq duration (thread-first
|
||||||
|
(current-time)
|
||||||
|
(time-subtract start-time)
|
||||||
|
decode-time
|
||||||
|
first))
|
||||||
|
(when (> duration beep-func-too-long-time)
|
||||||
|
(beep--when-finished (format "The function, %s, has finished."
|
||||||
|
(beep--extract-function-name orig-function))))))
|
||||||
|
|
||||||
|
(defun beep--extract-function-name (expr)
|
||||||
|
"Extracts the original function from a lambda expression, EXPR."
|
||||||
|
(if (listp expr)
|
||||||
|
(if (equal (car expr) 'lambda)
|
||||||
|
(car (cadr expr))
|
||||||
|
expr)
|
||||||
|
expr))
|
||||||
|
|
||||||
|
(provide 'beep)
|
||||||
|
;;; beep.el ends here
|
203
elisp/boxes-extras.el
Normal file
203
elisp/boxes-extras.el
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
;;; BOXES-EXTRAS --- Refiling Subtrees to Proper Org Files
|
||||||
|
;;
|
||||||
|
;; Author: Howard Abrams <howard@howardabrams.com>
|
||||||
|
;; Copyright © 2019, Howard Abrams, all rights reserved.
|
||||||
|
;; Created: 7 January 2019
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; WARNING: This file is tangled from its original essay.
|
||||||
|
;;
|
||||||
|
;; For a thorough explanation of this code, see the online essay:
|
||||||
|
;; http://www.howardism.org/Technical/Emacs/getting-even-more-boxes-done.html
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;; 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, 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; see the file COPYING. If not, write to
|
||||||
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
||||||
|
;; Floor, Boston, MA 02110-1301, USA.
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defun 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 (org-get-subtree-tags props)
|
||||||
|
:properties (org-get-subtree-properties attrs)
|
||||||
|
:body (org-get-subtree-content attrs)))))
|
||||||
|
|
||||||
|
(defun 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 (if org-get-subtree-tags-inherited "ALLTAGS" "TAGS")))
|
||||||
|
(-some->> props
|
||||||
|
(assoc tag-label)
|
||||||
|
cdr
|
||||||
|
substring-no-properties
|
||||||
|
(s-split ":")
|
||||||
|
(--filter (not (equalp "" it))))))
|
||||||
|
|
||||||
|
(defvar org-get-subtree-tags-inherited t
|
||||||
|
"Returns a subtree's tags, and all tags inherited (from tags
|
||||||
|
specified in parents headlines or on the file itself). Defaults
|
||||||
|
to true.")
|
||||||
|
|
||||||
|
(defun 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 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 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 (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 (org-filename-from-title head))
|
||||||
|
(filepath (format "%s/%s.org" dir filename)))
|
||||||
|
(apply #'delete-region area)
|
||||||
|
(org-create-org-file filepath head body tags properties)))
|
||||||
|
|
||||||
|
(defun 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)
|
||||||
|
(org-set-file-property "TITLE" header t)
|
||||||
|
(when tags
|
||||||
|
(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 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 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 org-refile-to-projects-dir ()
|
||||||
|
"Move the current subtree to a file in the `projects' directory."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-subtree-to-file org-default-projects-dir))
|
||||||
|
|
||||||
|
(defun org-refile-to-technical-dir ()
|
||||||
|
"Move the current subtree to a file in the `technical' directory."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-subtree-to-file org-default-technical-dir))
|
||||||
|
|
||||||
|
(define-auto-insert "/personal/*\\.org" ["personal.org" ha/autoinsert-yas-expand])
|
||||||
|
|
||||||
|
(defun org-refile-to-personal-dir ()
|
||||||
|
"Move the current subtree to a file in the `personal' directory."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-subtree-to-file org-default-personal-dir))
|
||||||
|
|
||||||
|
(provide 'boxes-extras)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; boxes-extras.el ends here
|
238
elisp/boxes.el
Normal file
238
elisp/boxes.el
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
;;; BOXES --- An opinionated approach to a GTD workflow
|
||||||
|
;;
|
||||||
|
;; Author: Howard Abrams <howard@howardabrams.com>
|
||||||
|
;; Copyright © 2019, Howard Abrams, all rights reserved.
|
||||||
|
;; Created: 7 January 2019
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; WARNING: This file is tangled from its original essay.
|
||||||
|
;;
|
||||||
|
;; For a thorough explanation of this code, see the online essay:
|
||||||
|
;; http://www.howardism.org/Technical/Emacs/getting-more-boxes-done.html
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;; 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, 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; see the file COPYING. If not, write to
|
||||||
|
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
|
||||||
|
;; Floor, Boston, MA 02110-1301, USA.
|
||||||
|
;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defvar org-default-projects-dir "~/projects" "Primary GTD directory")
|
||||||
|
(defvar org-default-technical-dir "~/technical" "Directory of shareable notes")
|
||||||
|
(defvar org-default-personal-dir "~/personal" "Directory of un-shareable, personal notes")
|
||||||
|
(defvar org-default-completed-dir "~/projects/trophies" "Directory of completed project files")
|
||||||
|
(defvar org-default-inbox-file "~/projects/breathe.org" "New stuff collects in this file")
|
||||||
|
(defvar org-default-tasks-file "~/projects/tasks.org" "Tasks, TODOs and little projects")
|
||||||
|
(defvar org-default-incubate-file "~/projects/incubate.org" "Ideas simmering on back burner")
|
||||||
|
(defvar org-default-completed-file nil "Ideas simmering on back burner")
|
||||||
|
(defvar org-default-notes-file "~/personal/general-notes.org" "Non-actionable, personal notes")
|
||||||
|
(defvar org-default-media-file "~/projects/media.org" "White papers and links to other things to check out")
|
||||||
|
|
||||||
|
(defvar org-capture-templates (list))
|
||||||
|
|
||||||
|
(add-to-list 'org-capture-templates
|
||||||
|
`("t" "Task Entry" entry
|
||||||
|
(file ,org-default-inbox-file)
|
||||||
|
"* %?\n:PROPERTIES:\n:CREATED:%U\n:END:\n\n%i\n\nFrom: %a"
|
||||||
|
:empty-lines 1))
|
||||||
|
|
||||||
|
(defhydra hydra-org-refiler (org-mode-map "C-c s" :hint nil)
|
||||||
|
"
|
||||||
|
^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 _i_/_I_: incubate _m P_: personal _S_: schedule _g i_: incubate _g P_: personal
|
||||||
|
_c_: archive _p_: personal _m T_: technical _D_: deadline _g x_: inbox _g T_: technical
|
||||||
|
_d_: delete _r_: refile _R_: rename _g n_: notes _g C_: completed
|
||||||
|
"
|
||||||
|
("<up>" org-previous-visible-heading)
|
||||||
|
("<down>" org-next-visible-heading)
|
||||||
|
("k" org-previous-visible-heading)
|
||||||
|
("j" org-next-visible-heading)
|
||||||
|
("c" org-archive-subtree-as-completed)
|
||||||
|
("d" org-cut-subtree)
|
||||||
|
("t" org-refile-to-task)
|
||||||
|
("i" org-refile-to-incubate)
|
||||||
|
("I" org-refile-to-another-incubator)
|
||||||
|
("p" org-refile-to-personal-notes)
|
||||||
|
("r" org-refile)
|
||||||
|
("m X" org-refile-to-projects-dir)
|
||||||
|
("m P" org-refile-to-personal-dir)
|
||||||
|
("m T" 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")
|
||||||
|
("<tab>" (org-cycle) "toggle")
|
||||||
|
("q" nil "quit"))
|
||||||
|
|
||||||
|
(setq org-refile-use-outline-path 'file
|
||||||
|
org-refile-allow-creating-parent-nodes t
|
||||||
|
org-outline-path-complete-in-steps nil)
|
||||||
|
|
||||||
|
(setq org-refile-targets
|
||||||
|
(append `((,(expand-file-name org-default-media-file) :level . 1)
|
||||||
|
(,(expand-file-name 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))
|
||||||
|
(--remove (s-ends-with? "_archive" it))
|
||||||
|
(--map (format "%s/%s" (expand-file-name org-default-projects-dir) it))
|
||||||
|
(--map `(,it :level . 0)))))
|
||||||
|
|
||||||
|
(setq org-refile-target-table nil)
|
||||||
|
|
||||||
|
(defun 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 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 (org-subtree-region))
|
||||||
|
(contents (buffer-substring (first region) (second region))))
|
||||||
|
(apply 'kill-region region)
|
||||||
|
(if org-refile-directly-show-after
|
||||||
|
(save-current-buffer (dump-it file-dest contents))
|
||||||
|
(save-window-excursion (dump-it file-dest contents))))))
|
||||||
|
|
||||||
|
(defvar org-refile-directly-show-after nil
|
||||||
|
"When refiling directly (using the `org-refile-directly'
|
||||||
|
function), show the destination buffer afterwards if this is set
|
||||||
|
to `t', otherwise, just do everything in the background.")
|
||||||
|
|
||||||
|
(defun org-refile-to-incubate ()
|
||||||
|
"Refile (move) the current Org subtree to `org-default-incubate-fire'."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-directly org-default-incubate-file))
|
||||||
|
|
||||||
|
(defun org-refile-to-task ()
|
||||||
|
"Refile (move) the current Org subtree to `org-default-tasks-file'."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-directly org-default-tasks-file))
|
||||||
|
|
||||||
|
(defun org-refile-to-personal-notes ()
|
||||||
|
"Refile (move) the current Org subtree to `org-default-notes-file'."
|
||||||
|
(interactive)
|
||||||
|
(org-refile-directly org-default-notes-file))
|
||||||
|
|
||||||
|
(defun org-refile-to-completed ()
|
||||||
|
"Refile (move) the current Org subtree to `org-default-completed-file',
|
||||||
|
unless it doesn't exist, in which case, refile to today's journal entry."
|
||||||
|
(interactive)
|
||||||
|
(if (and org-default-completed-file (file-exists-p org-default-completed-file))
|
||||||
|
(org-refile-directly org-default-completed-file)
|
||||||
|
(org-refile-directly (get-journal-file-today))))
|
||||||
|
|
||||||
|
(defun org-rename-header (label)
|
||||||
|
"Rename the current section's header to LABEL, and moves the
|
||||||
|
point to the end of the line."
|
||||||
|
(interactive (list
|
||||||
|
(read-string "Header: "
|
||||||
|
(substring-no-properties (org-get-heading t t t t)))))
|
||||||
|
(org-back-to-heading)
|
||||||
|
(replace-string (org-get-heading t t t t) label))
|
||||||
|
|
||||||
|
(defun org-archive-subtree-as-completed ()
|
||||||
|
"Archives the current subtree to today's current journal entry."
|
||||||
|
(interactive)
|
||||||
|
;; According to the docs for `org-archive-subtree', the state should be
|
||||||
|
;; automatically marked as DONE, but I don't notice it, so let's force:
|
||||||
|
(when (not (equal "DONE" (org-get-todo-state)))
|
||||||
|
(org-todo "DONE"))
|
||||||
|
|
||||||
|
(let* ((org-archive-file (or org-default-completed-file
|
||||||
|
(todays-journal-entry)))
|
||||||
|
(org-archive-location (format "%s::" org-archive-file)))
|
||||||
|
(org-archive-subtree)))
|
||||||
|
|
||||||
|
(defun todays-journal-entry ()
|
||||||
|
"Return the full pathname to the day's journal entry file.
|
||||||
|
Granted, this assumes each journal's file entry to be formatted
|
||||||
|
with year/month/day, as in `20190104' 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-time-string "%Y%m%d"))
|
||||||
|
(file-name (concat org-journal-dir daily-name)))
|
||||||
|
(expand-file-name file-name)))
|
||||||
|
|
||||||
|
;; Attempt to load the extra library functions tangled from a different essay:
|
||||||
|
(condition-case nil
|
||||||
|
(load-library "boxes-extras")
|
||||||
|
(error
|
||||||
|
(defun org-refile-to-projects-dir ()
|
||||||
|
(interactive)
|
||||||
|
(message "Need to load the 'boxes-extra project first."))
|
||||||
|
(defun org-refile-to-personal-dir ()
|
||||||
|
(interactive)
|
||||||
|
(message "Need to load the 'boxes-extra project first."))
|
||||||
|
(defun org-refile-to-technical-dir ()
|
||||||
|
(interactive)
|
||||||
|
(message "Need to load the 'boxes-extra project first."))))
|
||||||
|
|
||||||
|
(defun org-boxes-workflow ()
|
||||||
|
"Load the default tasks file and start our hydra on the first task shown."
|
||||||
|
(interactive)
|
||||||
|
(let ((org-startup-folded nil))
|
||||||
|
(find-file org-default-inbox-file)
|
||||||
|
(delete-other-windows)
|
||||||
|
(ignore-errors
|
||||||
|
(ha/org-agenda))
|
||||||
|
(delete-other-windows)
|
||||||
|
(split-window-right-and-focus)
|
||||||
|
(pop-to-buffer (get-file-buffer org-default-inbox-file))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(org-next-visible-heading 1)
|
||||||
|
(hydra-org-refiler/body)))
|
||||||
|
|
||||||
|
(defun ha/org-agenda ()
|
||||||
|
"Displays my favorite agenda perspective."
|
||||||
|
(interactive)
|
||||||
|
(org-agenda nil "a")
|
||||||
|
(get-buffer "*Org Agenda*")
|
||||||
|
(execute-kbd-macro (kbd "A t")))
|
||||||
|
|
||||||
|
(provide 'boxes)
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;; boxes.el ends here
|
74
elisp/ha-focus.el
Normal file
74
elisp/ha-focus.el
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
;;; ha-focus.el --- Emacs lisp code to focus my thoughts. -*- lexical-binding: t; -*-
|
||||||
|
;;
|
||||||
|
;; Copyright (C) 2021 Howard X. Abrams
|
||||||
|
;;
|
||||||
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
|
;; Maintainer: Howard X. Abrams <howard.abrams@workday.com>
|
||||||
|
;; Created: May 28, 2021
|
||||||
|
;;
|
||||||
|
;; This file is not part of GNU Emacs.
|
||||||
|
;;
|
||||||
|
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||||
|
;; ~/website/Technical/Emacs/focused-work.org
|
||||||
|
;; And tangle the file to recreate this one.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(defvar ha-focus-timer nil "A timer reference for the ha-focus functions")
|
||||||
|
|
||||||
|
(defun ha-focus-countdown-timer (minutes fun)
|
||||||
|
(let ((the-future (* minutes 60)))
|
||||||
|
(run-at-time the-future nil fun)))
|
||||||
|
|
||||||
|
(defun ha-focus-begin ()
|
||||||
|
"Start a concerted, focused effort, ala Pomodoro Technique.
|
||||||
|
We first clock into the current org-mode header (or last one),
|
||||||
|
start some music to indicate we are working, and set a timer.
|
||||||
|
|
||||||
|
Call `ha-focus-break' when finished."
|
||||||
|
(interactive)
|
||||||
|
(ha-focus-countdown-timer 25 'ha-focus-break)
|
||||||
|
(ha-focus--command "tell application \"Spotify\" to play")
|
||||||
|
(if (eq major-mode 'org-mode)
|
||||||
|
(org-clock-in)
|
||||||
|
(org-clock-in-last)))
|
||||||
|
|
||||||
|
(defun ha-focus-break ()
|
||||||
|
"Stop the focused time by stopping the music.
|
||||||
|
This also starts another break timer, that calls
|
||||||
|
`ha-focus-break-over' when finished."
|
||||||
|
(interactive)
|
||||||
|
(run-with-idle-timer 5 nil 'ha-focus-capture)
|
||||||
|
(ha-focus--command "tell application \"Spotify\" to pause")
|
||||||
|
(message "Time to take a break."))
|
||||||
|
|
||||||
|
(defun ha-focus-capture ()
|
||||||
|
(ignore-errors
|
||||||
|
(org-capture nil "cc")
|
||||||
|
(sit-for 1)
|
||||||
|
(org-clock-out))
|
||||||
|
(ha-focus-countdown-timer 5 'ha-focus-break-over)
|
||||||
|
(message "Taking a much needed break..."))
|
||||||
|
|
||||||
|
(defun ha-focus-break-over ()
|
||||||
|
"Message me to know that the break time is over. Notice that
|
||||||
|
this doesn't start anything automatically, as I may have simply
|
||||||
|
wandered off."
|
||||||
|
(ha-focus--command "set v to output volume of (get volume settings)
|
||||||
|
set volume output volume 1
|
||||||
|
say \"Break time over. Back on your head.\"
|
||||||
|
set volume output volume v"))
|
||||||
|
|
||||||
|
(defun ha-focus--command (osascript)
|
||||||
|
"Runs OSASCRIPT by passing to the `osascript' command asynchronously."
|
||||||
|
(async-start-process "focus-os" "osascript" 'ha-focus--command-callback "-e" osascript))
|
||||||
|
|
||||||
|
(defun ha-focus--command-callback (proc)
|
||||||
|
"Asynchronously called when the `osascript' process finishes."
|
||||||
|
(message "Finished calling osascript."))
|
||||||
|
|
||||||
|
(global-set-key (kbd "<f15>") 'ha-focus-begin)
|
||||||
|
(global-set-key (kbd "S-<f15>") 'ha-focus-break)
|
||||||
|
|
||||||
|
(provide 'ha-focus)
|
||||||
|
;;; ha-focus.el ends here
|
Loading…
Reference in a new issue