I'd like to keep a personal log of all the files I'm editing and how long I'm spending in each file. I feel like I'm soooo close to getting the right incantation of the function, but keep running into one specific problem.
In short, I'd like the function to do this: whenever I switch buffers, Emacs runs this function which should take the underlying filename of the buffer, open an org file arranged as a datetree, create a (or go to an already existing) heading based on the filename, and clock in. If that heading is the currently-clocked item, don't clock out and in again. Use the top-level directory name of the project as the file tag (for later filtering).
This seems to work _if_ I've already opened the org file and `org-mode` has been run. It fails on the first run when opening Emacs (because I haven't opened that file). The reason it fails is because `org-complex-heading-regexp-format` is not yet defined for that org file (it is `nil` by default which is not a string and causes the crash) and I use it in my search to find an already-existing headline for the filename. I noticed that it is defined in the function `org-set-regexps-and-options`. I tried running that manually if the variable was `nil` but that function has a guard to ensure the file is in `org-mode`. If I manually run `(org-mode)` in my function, things get _really_ wonky.
I'm fairly new to writing functions such as this and figured I'd bring it to you all anyway to see if you had any tips on how to do this a better way. Can you help me figure this out?
(defun geo/org-record-editing-of-file ()
"Clock in editing the current file."
(interactive)
(let ((filename (or (buffer-file-name) (buffer-name)))
(project (geo/project-name-from-directory-of-file))
(debug-on-error t))
;; don't record visiting "*" buffers (e.g. *Messages*, *scratch*, etc)
;; as it gets too noisy (minibuffers seem to start with a space)
(when (and filename
(not (string-prefix-p "*" filename))
(not (string-prefix-p " *" filename)))
(with-current-buffer
(find-file-noselect (concat org-directory "/auto-time-tracker.org") t t nil)
;; if this is the first time we've loaded up this file (or maybe haven't actually
;; loaded it for real like with `find-file`), org-complex-heading-regexp-format
;; hasn't yet been defined as it is a local variable. This ensures that if it
;; isn't defined, we define it and other local things required but without
;; having to completely load up org-mode.
(when (not org-complex-heading-regexp-format)
(message "org-complex-heading-regexp-format not set")
(org-set-regexps-and-options)
(message "org-complex-heading-regexp-format is now %s" org-complex-heading-regexp-format))
(org-datetree-find-iso-week-create (calendar-current-date))
(goto-char (point-at-eol))
(org-narrow-to-subtree)
(when (not (re-search-forward
(format org-complex-heading-regexp-format
(regexp-quote filename)) nil t))
(org-insert-subheading 0)
(insert filename)
(when project (org-set-tags project)))
;; prevent stopping and starting the same file task
(cond
((and (boundp 'org-clock-current-task)
(not (equal org-clock-current-task filename)))
(org-clock-out nil t)
(org-clock-in))
((not (boundp 'org-clock-current-task))
(org-clock-in)))
(widen)
(save-buffer)))))
(add-hook 'window-state-change-hook #'geo/org-record-editing-of-file)
edit: indenting by 4 spaces instead of using triple backticks