Emacs - The God King

Here is a copy of my config.el file broken out into sections. I'll package some of these custom functions into a library at some point, i.e. probably never but a man can dream. There is a high probability that I reinvented the wheel with some of these functions.

I use Emacs as a project management solution, and as my life is dictated by projects, a life management solution. There is something incredibly alluring about being able to customize every aspect of your digital environment. The Auto-Scheduler is the culmination of that idea. I needed a tool to keep me on task and prevent me from blowing days on side projects when work deadlines were approaching and no other tool did the trick. To be able to will the specifics of what you need into existence is a pretty satisfying result. Was this just another side projects I did instead of real work... yes, yes it was. But hopefully it was the last big distracting one.

PACKAGE MANAGEMENT

Setup straight

When I set this up the config I was following used straight so that's what I've stuck with.

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
	(url-retrieve-synchronously
	 "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
	 'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(setq straight-use-pakage-by-default t)
(setq package-enable-at-startup nil)
(use-package org
  :straight (:type built-in))
(use-package package
  :straight t
       :config
       (package-initialize))

(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(setenv "PATH" (concat (getenv "PATH") ":/home/cam/.local/bin"))

Helpers

As I've gotten better at writing elisp, I started breaking out common functionality to reduce repeated code.

Print

For debugging purposes, I got sick of typing %s.

(defun cz/print(item)
  (message "%s" item)
)

Get Property

One of the ways I have started keeping track of internal data is through properties either under a header or at the top of a file.

* Heading
:PROPERTIES:
:EFFORT: 10:00
:END:

To grab that data I have this function. It takes the file you wish to grab data from, the property you wish to grab and an optional header. If the header is left nil then it searched for properties at the top of the file, otherwise it looks for properties under the given header.

(defun cz/get-property(file property &optional header)
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (let ((property-list ()))
      (if (not (null header))
        (when (search-forward-regexp header nil t))
      )
      (when (search-forward-regexp ":PROPERTIES:" nil t)
          (while (not (eobp))
            (let ((line (buffer-substring (line-beginning-position) (line-end-position))))
                (when (string-match (concat ":" property ":\\s-+\\(.*\\)$") line)
                  (let* ((props (match-string 1 line))
                        (props-split (string-split props ", ")))
                    (dolist (prop props-split)
                      (push prop property-list)
                    )
                  )
                )
                (if (string-match ":END:" line)
                  (goto-char (point-max))
                  (forward-line 1)
                )
              )
            )
        (nreverse property-list)
      )
    )
  )
)

Get All Properties

Likewise if you want to grab all of the properties of a given file/header.

(defun cz/get-all-properties(file &optional header)

  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (let ((property-alist '()))
      (if (not (null header))
        (when (search-forward-regexp header nil t))
      )
      (when (search-forward-regexp ":PROPERTIES:" nil t)
          (while (not (eobp))
            (let ((line (buffer-substring (line-beginning-position) (line-end-position))))
                (when (string-match (concat ":\\(.*\\):\\s-+\\(.*\\)") line)
                  (let* ((property-list ())
                         (property (match-string 1 line))
                         (value (match-string 2 line))
                        (props-split (string-split value ", ")))
                    (dolist (prop props-split)
                      (push prop property-list)
                    )
                    (if (assoc property property-alist)
                      (let ((existing-list (cdr (assoc property property-alist))))
                        (dolist (p property-list)
                          (push p existing-list)
                        )
                        (setcdr (assoc property property-alist) existing-list)  
                      )
                      (add-to-list 'property-alist (cons property property-list) t)
                    )
                  )
                )
                (if (string-match ":END:" line)
                  (goto-char (point-max))
                  (forward-line 1)
                )
              )
            )
        property-alist
      )
    )
  )
)

Set Property

Then we have the 'set-property' function. Inefficiently written with the assumption the buffer is open. What more could you ask for from free code on the internet!

(defun cz/set-property(file property value &optional header nth)
  (let ((original-buffer (current-buffer))
        (split (string-split file "/"))) 
    (when file
      (set-buffer (nth (- (length split) 1) split))
      (goto-char (point-min))
      (when (null nth)
        (setq nth 1)
        )
      (let ((property-alist (cz/get-all-properties file header)))
        (if (not (null header))
            (when (search-forward-regexp header nil t)
              ;;(cz/print property-alist)
              )
          )
        (when (search-forward-regexp ":PROPERTIES:" nil t)
          (let ((start (point))
                (n 0)
                (d 0))
            (search-forward-regexp ":END:" nil t)
            (delete-region start (point))
            (insert "\n")
            (dolist (pair property-alist)
              (dolist (item (reverse (cdr pair)))
                (if (string= property (car pair))
                    (progn
                      (setq n (+ n 1))
                      (if (= n nth)
                          (progn (insert (concat ":" (car pair) ": " value)) (setq d 1))
                        (insert (concat ":" (car pair) ": " item))
                        )
                      )
                  (progn
                    (when (and (= nth 0) (not (= n 0)))
                      (insert (concat ":" property ": " value "\n"))
                    (setq d 1)
                    )
                    (insert (concat ":" (car pair) ": " item))
                    )
                  )
                
                (insert "\n")
                )            
              )
            (when (= d 0)
              (insert (concat ":" property ": " value "\n"))
              )
            )
          (insert ":END:")
          (write-file file)
          (save-buffer)
          ) 
        )
      )
    (set-buffer original-buffer)
  )
)

Remove Property

To finish off the property related functions we include the ability to remove properties.

(defun cz/remove-property(file property &optional header)
  (let ((original-buffer (current-buffer))
        (split (string-split file "/"))) 
    (when file
      (set-buffer (nth (- (length split) 1) split))
      (goto-char (point-min))
      (let ((property-alist (cz/get-all-properties file header)))
        (when (not (null header))
          (when (search-forward-regexp header nil t)
              ;;(cz/print property-alist)
          )
        )
        (setq property-alist (remove (assoc property property-alist) property-alist))
        (when (search-forward-regexp ":PROPERTIES:" nil t)
          (let ((start (point)))
            (search-forward-regexp ":END:" nil t)
            (delete-region start (point))
            (insert "\n")
            (dolist (pair property-alist)
              (dolist (item (reverse (cdr pair)))
                (insert (concat ":" (car pair) ": " item))
                (insert "\n")
                )            
              )
            )
          (insert ":END:")
          (write-file file)
          (save-buffer)
          ) 
        )
      )
    (set-buffer original-buffer)
  )
)

EVIL MODE

I use evil because I am evil.

Evil

(setq evil-disable-insert-state-bindings t)
(use-package evil
  :straight (evil
             :host github
             :repo "emacs-evil/evil"
             :branch "master")
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  (setq evil-vsplit-window-right t)
  (setq evil-split-window-below t)
  (evil-mode))
(use-package evil-collection
  :after evil
  :straight t
  :config
  (evil-collection-init))


(with-eval-after-load 'evil-maps
  (define-key evil-normal-state-map (kbd "M-.") nil))

Leader

These hot-keys felt natural when I added them. Some I use, some I've forgotten.

(use-package evil-leader
  :after evil
  :straight (evil-leader
	     :host github
	     :repo "cofi/evil-leader"
	     :branch "master")
  :init
  (global-evil-leader-mode))
(define-key evil-motion-state-map (kbd "RET") nil)
(evil-leader/set-leader "<SPC>")
(evil-leader/set-key
  "." 'find-file
  "," 'switch-to-buffer
  "/" 'consult-find
  "k" 'kill-buffer
  "<SPC>" 'projectile-find-dir
  "<RET>" 'org-todo
  "o" 'org-open-at-point
  "a" 'org-agenda
  "w" 'wttrin
  "q" 'wttrin-exit
  "f" 'hide-drawers)

THEME

Because who wants a blindingly white editor.

Color

I have a customized doom-theme you can get here, jokes on you if this text is still here I haven't made that a link yet. The theme is activated in a later function because I am using a server/daemon setup.

(use-package doom-themes
  :straight t)
(setq doom-themes-enable-bold t
      doom-themes-enable-italics t)

Font

You will need to add these fonts to your system if you want to use this. Like the color theme above I will add a link to download these directly as well as the command to install them.

(set-face-attribute 'default nil
		    :font "RobotoMono"
		    :weight 'medium)
(set-face-attribute 'variable-pitch nil
		    :font "Ubuntu"
		    :weight 'medium)
(set-face-attribute 'fixed-pitch nil
		    :font "RobotoMono"
		    :weight 'medium)
(add-to-list 'default-frame-alist '(font . "RobotoMono"))

(custom-set-faces
  '(org-level-1 ((t (:inherit outline-1 :height 1.5))))
  '(org-level-2 ((t (:inherit outline-2 :height 1.25))))
  '(org-level-3 ((t (:inherit outline-3 :height 1.15))))
  '(org-level-4 ((t (:inherit outline-4 :height 1.05))))
  '(org-level-5 ((t (:inherit outline-5 :height 1.0))))
)

(custom-set-faces '(org-drawer ((t (:height 0.7)))))

Centering

Occasionally I disable this when I write bad lines of code that are well over 60 characters or whatever the limit is these days, but I do think it looks nice.

(use-package sublimity
  :straight t
  :config
  (require 'sublimity)
  ;;(require 'sublimity-scroll)
  ;; (require 'sublimity-map) ;; experimental
  (require 'sublimity-attractive)
  (sublimity-mode 1)
  (setq sublimity-attractive-centering-width 110)
  ;;(setq sublimity-scroll-weight 2
  ;;sublimity-scroll-drift-length 10)
  )

Modeline

(I don't remember exactly what this adds but I know I like it.)

(use-package doom-modeline
  :straight t)

Rainbow Parentheses

Because adding colors to anything makes them better.

(use-package rainbow-delimiters
  :straight t)
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)

Time

Pretty self explanatory.

  (display-time)

Pretty Header Bullets

This updates the * characters in org files to be different symbols.

(use-package org-superstar
  :straight t
  :init
  (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1)))
  (with-eval-after-load 'org-superstar
    (set-face-attribute 'org-superstar-item nil :height 1.2)
    (set-face-attribute 'org-superstar-header-bullet nil :height 1.2)
    (set-face-attribute 'org-superstar-leading nil :height 1.3)
  )
  (setq org-hide-leading-stars nil)
  (setq org-superstar-leading-bullet ?\s)
  ;; Stop cycling bullets to emphasize hierarchy of headlines.
  (setq org-superstar-cycle-headline-bullets nil)
  ;; Hide away leading stars on terminal.
  (setq org-superstar-leading-fallback ?\s)
  (setq inhibit-compacting-font-caches t))

All-The-Icons

Adds fun little icons. Requires you to run the command all-the-icons-install-fonts.

(use-package all-the-icons
  :straight t
  :if (display-graphic-p))

Precision Scrolling

I think this is nice.

(pixel-scroll-precision-mode)

GENERAL

Flycheck

General syntax checking.

(use-package flycheck
  :straight t
  :init (global-flycheck-mode))

Flyspell

General spell checking.

(use-package flycheck-aspell
  :straight t
  :init
  (add-to-list 'flycheck-checkers 'tex-aspell-dynamic))
(flyspell-mode)

(add-hook 'text-mode-hook 'flyspell-mode)

Completion at point

Adds a little box where the curser is that suggests the rest of the word.

(use-package company
  :straight t
  :bind (:map company-active-map
	      ("C-n" . company-select-next)
	      ("C-p" . company-select-previous))
  :config
  (setq company-minimum-prefix-length 1
	company-idle-delay 0.3))

(add-hook 'after-init-hook 'global-company-mode)
(setq company-tooltip-limit 10)

Vertico

Also does M-x function completion.

  (use-package vertico
      :straight t
      :custom
      (vertico-cycle t)
      :init
      (vertico-mode))

Consult

Suggests M-x functions based on the text entered thus far.

(use-package consult
  :straight t
  :hook (completion-list-mode . consult-preview-at-point-mode)
  :init
  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (setq register-preview-delay 0.3
        register-preview-function #'consult-register-format)
  
  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  (advice-add #'register-preview :override #'consult-register-window)
  
  ;; Optionally replace `completing-read-multiple' with an enhanced version.
  (advice-add #'completing-read-multiple :override #'consult-completing-read-multiple)
  
  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref))

Marginalia

Adds some flavor to the M-x function completion.

(use-package marginalia
  :after vertico
  :straight t
  :custom
    (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  :init
    (marginalia-mode))

Whichkey

Provides a readout of commands that follow from an already entered keystroke. For example if you press C-c, itll fill the minibuffer with all the commands that begin C-c.

(use-package which-key
  :straight t
  :init (which-key-mode)
  :config
  (setq which-key-idle-delay 0.3)
  (setq which-key-popup-type 'minibuffer))

Savehist

Saves your minibuffer histories.

(use-package savehist
  :straight nil
  :init
  (savehist-mode))

Landing Page

Adds a nice dashboard when you first open Emacs. This can contain bookmarks, projects, recent files. I find it a usefull homepage to jump back to.

  (use-package page-break-lines
	:straight t)
	;; :hook (dashboard-mode-hook . page-break-lines-mode))


  (use-package dashboard
    :straight t
    :config
    (dashboard-setup-startup-hook))
  (setq initial-buffer-choice (lambda () (get-buffer "*dashboard*")))
  (setq dashboard-startup-banner "~/banner.txt")
  (setq dashboard-items '((projects  . 10)
			  (bookmarks . 10)
			  (recents . 10)))
  (setq dashboard-set-footer nil)
  (setq dashboard-set-heading-icons t)
  (setq dashboard-set-file-icons t)
  (setq dashboard-center-content t)
  (setq dashboard-set-navigator t)
  (setq dashboard-set-init-info t)
  (setq dashboard-week-agenda t)
  (setq dashboard-page-separator "\n\f\n")

Zooming In and Out

Because we all go blind one day.

    (global-set-key (kbd "C-=") 'text-scale-increase)
    (global-set-key (kbd "C--") 'text-scale-decrease)
    (global-set-key (kbd "<C-wheel-down>") 'text-scale-decrease)
    (global-set-key (kbd "<C-wheel-up>") 'text-scale-increase)

Comments

I crave that C-/ to comment but its taken by something else, this will do.

  (use-package evil-nerd-commenter
    :straight t
    :bind ("M-/" . evilnc-comment-or-uncomment-lines))

Undo Fu

Usefull.

  (use-package undo-fu
    :straight t)
  (define-key evil-normal-state-map "u" 'undo-fu-only-undo)
  (define-key evil-normal-state-map "\C-r" 'undo-fu-only-redo)

PROGRAMMING

Next are a suite of packages associated with different languages and programming functionallity.

Tabs

I don't think I'm using the full functionality of this anymore but it was for setting the number of spaces a tab is by default for different languages.

(defun my-setup-indent (n)
  ;; java/c/c++
  (setq-local c-basic-offset n)
  ;; web development
  (setq-local coffee-tab-width n) ; coffeescript
  (setq-local javascript-indent-level n) ; javascript-mode
  (setq-local js-indent-level n) ; js-mode
  (setq-local js2-basic-offset n) ; js2-mode, in latest js2-mode, it's alias of js-indent-level
  (setq-local web-mode-markup-indent-offset n) ; web-mode, html tag in html file
  (setq-local web-mode-css-indent-offset n) ; web-mode, css in html file
  (setq-local web-mode-code-indent-offset n) ; web-mode, js code in html file
  (setq-local css-indent-offset n) ; css-mode
)

(defun my-office-code-style ()
  (interactive)
  (message "Office code style!")
  ;; use tab instead of space
  (setq-local indent-tabs-mode t)
  ;; indent 4 spaces width
  (my-setup-indent 4))

(defun my-personal-code-style ()
  (interactive)
  (message "My personal code style!")
  ;; use space instead of tab
  (setq indent-tabs-mode nil)
  ;; indent 2 spaces width
  (my-setup-indent 2))

;; (defun my-setup-develop-environment ()
;;   (interactive)
;;   (let ((proj-dir (file-name-directory (buffer-file-name))))
;;     ;; if hobby project path contains string "hobby-proj1"
;;     (if (string-match-p "hobby-proj1" proj-dir)
;;         (my-personal-code-style))

;;     ;; if commericial project path contains string "commerical-proj"
;;     (if (string-match-p "commerical-proj" proj-dir)
;;         (my-office-code-style))))
(defun my-setup-develop-environment ()

  (setq-local python-indent-offset 4)  
  (my-personal-code-style))



;; prog-mode-hook requires emacs24+
(add-hook 'prog-mode-hook 'my-setup-develop-environment)

;; a few major-modes does NOT inherited from prog-mode
(add-hook 'lua-mode-hook 'my-setup-develop-environment)
(add-hook 'web-mode-hook 'my-setup-develop-environment)

Folding

Need to emulate all the features of VScode.

(straight-use-package '(origami :host github
                                :repo "gregsexton/origami.el"
                                :branch "master"))
(global-set-key (kbd "C-c o") 'origami-open-node)
(global-set-key (kbd "C-c c") 'origami-close-node)
(add-hook 'prog-mode-hook 'origami-mode)

Formatting

Honestly this is very nice.

(straight-use-package 'apheleia)
(apheleia-global-mode +1)

Python

Because everyone uses python at some point. Though this might be slightly broken.

(use-package eglot
  :straight t
  :defer t
  :bind (:map eglot-mode-map
              ("M-." . xref-find-definitions)
              ("C-c C-d" . eldoc)
              ("C-c C-e" . eglot-rename)
              ("C-c C-o" . python-sort-imports)
              ("C-c C-f" . eglot-format-buffer))
  :hook ((python-ts-mode . eglot-ensure)
         (python-ts-mode . flyspell-prog-mode)
         (python-ts-mode . superword-mode)
         (python-ts-mode . hs-minor-mode)
         (python-ts-mode . (lambda () (set-fill-column 88))))
  :config
  (setq-default eglot-workspace-configuration
                '((:pylsp . (:configurationSources ["flake8"]
                             :plugins (
                                       :pycodestyle (:enabled :json-false)
                                       :mccabe (:enabled :json-false)
                                       :pyflakes (:enabled :json-false)
                                       :flake8 (:enabled :json-false
                                                :maxLineLength 88)
                                       :ruff (:enabled t
                                              :lineLength 88)
                                       :pydocstyle (:enabled t
                                                    :convention "numpy")
                                       :yapf (:enabled :json-false)
                                       :autopep8 (:enabled :json-false)
                                       :black (:enabled t
                                               :line_length 88
                                               :cache_config t)))))))

Typescript

This has been nice for website design.

(use-package tide
  :straight t)

(defun setup-tide-mode ()
    (interactive)
    (tide-setup)
    (flycheck-mode +1)
    (setq flycheck-check-syntax-automatically '(save mode-enabled))
    (eldoc-mode +1)
    (tide-hl-identifier-mode +1)
    ;; company is an optional dependency. You have to
    ;; install it separately via package-install
    ;; `M-x package-install [ret] company`
    (company-mode +1))

  ;; aligns annotation to the right hand side
  (setq company-tooltip-align-annotations t)

  ;; formats the buffer before saving
  (add-hook 'before-save-hook 'tide-format-before-save)

  (add-hook 'typescript-mode-hook #'setup-tide-mode)
;; (setq tramp-default-method "ssh")
;; (setq tide-tsserver-executable "/home/drmrsirteachersir/.nvm/versions/node/v16.19.1/bin/tsserver")

(use-package web-mode
  :straight t)

(require 'web-mode)

(add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))
(add-hook 'web-mode-hook
          (lambda ()
            (when (string-equal "tsx" (file-name-extension buffer-file-name))
              (setup-tide-mode))))

;; enable typescript - tslint checker
(flycheck-add-mode 'typescript-tslint 'web-mode)

FILE PARSING

PDF parsing

Always amusing to tell people this text-editor can open pdfs,

(use-package pdf-tools
  :straight t
  :hook (pdf-tools-enabled . pdf-view-midnight-minor-mode)
  :config
  (setq pdf-view-midnight-colors '( "#a99a89" . "#171614")))
(pdf-tools-install)
(add-hook 'pdf-tools-enabled 'pdf-view-midnight-minor-mode)

Latex

and compile LaTeX documents.

(add-to-list 'load-path "~/.emacs.d/manual/auctex-13.1/") 
(load "auctex.el" nil t t)
(load "preview-latex.el" nil t t)
(setq my/org-latex-scale 2.5)
(setq org-format-latex-options (plist-put org-format-latex-options :scale my/org-latex-scale))
(use-package latex-preview-pane
  :straight t)

Bibtex

I used this aggressivly for a bit but not so much anymore.

(use-package company-bibtex
  :after company
  :straight t
  :config
    (add-to-list 'company-backends 'company-bibtex)
    (setq company-bibtex-bibliography "path-to-home/global.bib")
    (org-add-link-type "ebib" 'ebib))
(add-to-list 'org-cite-global-bibliography "path-to-home/global.bib")

Markdown

This very page is encoded in markdown, how could I not include this.

(use-package markdown-mode
  :straight t
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :init
  (custom-set-variables
   '(markdown-command "/usr/bin/pandoc"))
  (setq markdown-command "multimarkdown"))

PROJECTS

This used to contain more things but they've moved elsewhere.

Projectile

(use-package projectile
  :straight t
  :init (projectile-mode +1))
(define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)

Time Tracking

This is pretty cool, source.

;; 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 org-stuck-projects (quote ("" nil nil "")))

(setq bh/keep-clock-running nil)

;; ;; Remove empty LOGBOOK drawers on clock out
;; (defun bh/remove-empty-drawer-on-clock-out ()
;;   (interactive)
;;   (save-excursion
;;     (beginning-of-line 0)
;;     (org-remove-empty-drawer-at "LOGBOOK" (point))))

;; (add-hook 'org-clock-out-hook 'bh/remove-empty-drawer-on-clock-out 'append)

(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))
      "WORKING")
     ((and (member (org-get-todo-state) (list "WORKING"))
           (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 org-agenda-remove-restriction-lock (&optional noupdate)
  "Remove agenda restriction lock."
  (interactive "P")
  (if (not org-agenda-restrict)
      (message "No agenda restriction to remove.")
    (delete-overlay org-agenda-restriction-lock-overlay)
    (delete-overlay org-speedbar-restriction-lock-overlay)
    (setq org-agenda-overriding-restriction nil)
    (setq org-agenda-restrict nil)
    (put 'org-agenda-files 'org-restrict nil)
    (move-marker org-agenda-restrict-begin nil)
    (move-marker org-agenda-restrict-end nil)
    (setq current-prefix-arg nil)
    (message "Agenda restriction lock removed")
    (or noupdate (org-agenda-maybe-redo))))

(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")

(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)


(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)))

ORG MODE

Org-mode is a beautiful creation. If you need to take any kind of note that may reference other notes, events, todo items, or really anything, it provides an easy way to do that. Additionally, I've built quite the scheduling program around the Org-agenda.

Org Mode

The base package.

(use-package org
  :config
 
  ;; (setq org-clock-sound "~/config/alarm.wav")
  (setq org-directory "org-path")
  (setq org-capture-bookmark nil)
  (setq org-agenda-files (quote ("roam-path/daily")))
  (setq org-hide-emphasis-markers t)
  ;; (setq org-agenda-files (directory-files-recursively "home-path" "\\.org$"))
  (setq org-agenda-prefix-format
	'((agenda . "%i %-12:c%?-12t% s")
	  (todo . "  %-12:c")
	  (tags . " %i %-12:c")
	  (search . " %i %-12:c")))
  (setq org-todo-keywords
	'((sequence "TODO(t)" "WORKING(w)" "REVIEW(r)" "|" "DONE(d)")
	  (sequence "BLOCKED(b@/!)" "WAITING(w@/!)" "|" "CANCELLED(c@/!)")
	  (sequence "[ ](T)" "[-](S)" "|" "[x](D)")))
  (setq org-todo-keyword-faces
	'(("TODO"      :foreground "#bb323b" :weight bold)
	  ("WORKING"   :foreground "#38613a" :weight bold)
	  ("DONE"      :foreground "#309393" :weight bold)
	  ("BLOCKED"   :foreground "#4f81d4" :weight bold)
	  ("WAITING"   :foreground "#d3d3da" :weight bold)
	  ("CANCELLED" :foreground "#454545" :weight bold)
	  ("REVIEW"   :foreground "#d6c45a" :weight bold)))
  )
(add-hook 'org-mode-hook
	  (lambda ()
	    (setq truncate-lines nil)
	    ))

(setq org-use-fast-todo-selection t)
(setq org-startup-folded nil)
(setq org-src-preserve-indentation t)
(setq org-edit-src-content-indentation 0)
(setq org-tags-exclude-from-inheritance '("HEADER"))
(global-set-key (kbd "C-c n c") 'org-capture)
(setq split-width-threshold 79)
(setq org-agenda-sticky t)
(setq org-priority-lowest 69)
(setq org-priority-faces '(
			   (?A . (:foreground "#83373c"))
			   (?B . (:foreground "#2e5c32"))
                           (?C . (:foreground "#2a4c60"))
                           (?D . (:foreground "#556377"))
                           (?E . (:foreground "#7a7a7a"))
			   ))

(setq org-default-notes-file "org-path/refile.org")
; Targets include this file and any file contributing to the agenda - up to 9 levels deep
(setq org-refile-targets (quote ((nil :maxlevel . 9)
                                 ("org-path/todo.org" :maxlevel . 2)
                                 ("org-path/archive.org" :maxlevel . 2)

                                 (org-project-list :maxlevel . 9) )))
;;;; Refile settings
; Exclude DONE state tasks from refile targets
(defun bh/verify-refile-target ()
  "Exclude todo keywords with a done state from refile targets"
  (not (member (nth 2 (org-heading-components)) org-done-keywords)))

(setq org-refile-target-verify-function 'bh/verify-refile-target)

SuperAgenda

Adds some extra capabilities to the agenda.

(setq target-date (org-read-date nil nil "+31"))
(use-package org-super-agenda
  :after org-agenda
   :straight t
  :init
  (setq org-agenda-skip-scheduled-if-done 'past
	org-agenda-skip-deadline-if-done t
	org-agenda-include-deadlines t
	org-agenda-block-separator ?=
	org-agenda-compact-blocks nil
	org-agenda-start-day "-0d" ;; i.e. today
	org-agenda-span 1
	org-agenda-start-on-weekday nil
	org-agenda-use-time-grid t
	org-agenda-todo-ignore-scheduled 'future
	org-agenda-tags-todo-honor-ignore-options t
	org-agenda-fontify-priorities t
        org-deadline-warning-days 31
	org-habit-show-habits t
	org-habit-show-habits-only-for-today t
	org-habit-show-all-today t
	org-habit-show-done-always-green t
	org-habit-today-glyph ?!
	org-habit-completed-glyph ?x
	org-agenda-skip-deadline-prewarning-if-scheduled t
        org-log-into-drawer t)

(setq org-agenda-custom-commands
      '(("d" "Day view"
	 ((agenda "" ((org-agenda-overriding-header "")
		      (org-super-agenda-groups
		       `((:name "Events"
                                 :tag "EVENT"
                                 :order 1)
                         (:name "Chores"
                                 :tag "CHORE"
                                 :order 2)
			 (:name "Habits"
				:habit t
				:order 4)
                (:name "Habits-Reminder"
				:tag "HABITr"
				:order 5)
                         (:name "Goals"
                                :tag "GOAL"
                                :scheduled future
                                :order 3)
                         (:name "Today"	
						 :time-grid t
				:date today
				:scheduled today
				:order 6)
			 (:discard (:anything t)) 
                         ))) 
	 (tags-todo "+TASK-HEADER" ((org-agenda-overriding-header "Project Tasks")
 		       (org-super-agenda-groups
			'((:auto-category t))
				      )))))
	("p" "Project View"
	 ((tags-todo "+TASK-HEADER" ((org-agenda-overriding-header "Project Tasks")
 		       (org-super-agenda-groups
			'((:auto-category t))
				      )))))
        ("r" "Research View"
         ((agenda "" ((org-agenda-override-header "")
		      (org-super-agenda-groups
                      `((:name "Events"	
                                 :tag "EVENT"					      
                                 :order 1)						      
                          (:name "Chores"						      
                                 :tag "CHORE"					      
                                 :order 2)						      
			 (:name "Habits"						      
				:habit t						      
				:order 4)
                         (:name "Goals"
                                :tag "GOAL"
                                :scheduled future
                                :order 3)						      
                         (:name "Today"						      
				:time-grid t						      
				:date today						      
				:scheduled today 					      
				:order 5)
			 (:discard (:anything t))				))))
          (tags-todo "+TASK-HEADER" ((org-agenda-overriding-header "Research Tasks")
                                     (org-super-agenda-groups
                                      `(
                                        (:name "General"
                                           :category "RESEARCH"
                                           :order 1)
                                         (:name "Dynamic Mode Decomposition"
                                            :tag "DMD"
                                            :order 2)
                                         (:name "Federated Learning 1"
                                            :tag "FL1"
                                            :order 3
                                         )
                                         (:name "Federated Learning 2"
                                            :tag "FL2"
                                            :order 3
                                         )

                                        (:discard (:anything t))
                                        
                                        ))))
                       
          ))
        ("t" "Test View"
         ((agenda "" ((org-agenda-override-header "")
		      (org-super-agenda-groups
                      `((:name "Events"
                                 :tag "EVENT" 
                                 :order 1)
			 (:discard (:anything t))				))))
          (tags-todo "+TASK-HEADER" ((org-agenda-overriding-header "Research Tasks")
                                     (org-super-agenda-groups
                                      `((:name "Tasks"
                                         :tag "MICROPHONE"
                                         :order 1)
                                        (:discard (:anything t))
                                        
                                        ))))
                       
          ))

        ))


(org-super-agenda-mode))

Org Roam

This package is one of the main reasons people get into emacs. It adds the linking and backlinking note taking system. Very usefull.

  (use-package org-roam
    :straight t
    :init (setq org-roam-v2-ack t)
    :custom (org-roam-directoy "roam-path")
    :bind (("C-c n l" . org-roam-buffer-toggle)
	   ("C-c n f" . org-roam-node-find)
	   ("C-c n i" . org-roam-node-insert)
	   :map org-mode-map
	     ("C-M-i" . completion-at-point)
	   :map org-roam-dailies-map
	     ("Y" . org-roam-dailies-capture-yesterday)
	     ("T" . org-roam-dailies-capture-today))
    :bind-keymap ("C-c n d" . org-roam-dailies-map)
    :config
    (require 'org-roam-dailies)
    (setq org-roam-directory  "roam-path"))
  (org-roam-db-autosync-mode)
  (org-roam-setup)
    (setq org-return-follows-link  t)
  (setq org-link-frame-setup '((file . find-file)))


(advice-add 'org-roam-db-update-file :around
              (defun +org-roam-db-update-file (fn &rest args)
                  (emacsql-with-transaction (org-roam-db)
                    (apply fn args))))

Org Capture Templates

Some templates for specific note structures.

(setq org-roam-capture-templates
      '(("p" "project" plain "%[~/config/templates/project.org]"
	 :target (file+head "%<%Y%m%d%H%m%S>-project_${slug}.org" 
			    "#+title: Project: ${title}\n#+category: %(upcase \"${title}\")\n#+nickname: ${nickname}\n#+filetags: PROJECT\n")
	 :unnarrowed t)
	
	("n" "note" plain "Sources: %a\n\n%?"
	 :target (file+head "%<%Y%m%d%H%m%S>-${slug}.org" "#+title: ${title}\n")
	 :unnarrowed t)))
(setq org-hide-emphasis-markers t)

(defun org-next-min ()
(let* ((now (decode-time))
       (next (copy-sequence now)))
  (cl-incf (nth 1 next))
   (format-time-string "[%Y-%m-%d %a %H:%M]" (apply #'encode-time next))
  )
)
(defun org-log-one-min ()
  (concat (concat (concat (format-time-string "[%Y-%m-%d %a %H:%M]") "--") (org-next-min)) " => 0:01")
)

(setq org-capture-templates
      '(("p" "Project Task" entry
         (file+headline "org-path/refile.org" "Tasks")
	 "%[~/config/templates/project_task.org]")
	("c" "Chore" entry
	 (file+headline "org-path/refile.org" "Chores")
	 "* TODO %^{Chore} %(org-set-tags \"CHORE\")\n%i SCHEDULED:%^t")
        ("n" "Note" entry 
         (file+headline "org-path/refile.org" "Notes")
         "* %^{Thing to note} %(org-set-tags \"NOTE\")\n%i%u\n- %?")
	("e" "Event" entry
	 (file+headline "org-path/refile.org" "Events")
	 "* %^{Event} %(org-set-tags \"EVENT\")\n:PROPERTIES:\n:LENGTH: %^{Length}\n:END:\nSCHEDULED: %^t")
        ("x" "auto" entry 
         (file+headline "org-path/refile.org" "Tasks")
         "* TODO %(symbol-name myNewProjectTask) %(org-set-tags \"TASK\")\n:PROPERTIES:\n:EFFORT: %^{Effort}\n:END:\n :LOGBOOK:\n CLOCK: %(org-log-one-min)\n :END:\n %i")
	))

(defun my/getfilename(filetitle)
  (concat (format-time-string "%Y%m%d%H%M%S") "-" (replace-regexp-in-string "[^[:alnum:]]" "-" filetitle) ".org")
) 

(defun my/org-roam-capture-task ()
  (interactive)
  ;; Add the project file to the agenda after capture is finished
  ;; (add-hook 'org-capture-after-finalize-hook #'my/org-roam-project-finalize-hook)
  (let* ((myproject (completing-read "Choose a project: " org-project-tag-list))
         (mytitle (read-string "Enter todo item title: "))
         (myfile (my/getfilename (concat myproject ": " mytitle)))
         )

     
    (org-roam-capture- :node (org-roam-node-create)
                       :info `(:myproject ,myproject :mytitle ,mytitle :myfile ,myfile)
                       :templates '(("g" "todo" plain "%?\n* ${mytitle}"
                                     :target (file+head "${myfile}" "#+title: ${myproject}: ${mytitle}\n#+filetags: ${myproject}")
                                     :unnarrowed t
                                     )))
    (save-buffer)
    (let* ((id (org-id-get))
          (mylink (concat "[[id:" id "][" mytitle "]]")))
      (setq myNewProjectTask (intern mylink))
    (org-capture-finalize)  
    ;; (switch-to-buffer (other-buffer))
    (org-capture nil "x")
    )
  )
)

(global-set-key (kbd "C-c n t") #'my/org-roam-capture-task)

Effort

When adding a task for projects I include an estimate of the effort it'll take to complete. This block adds that effort to the clock summary table.

; Set default column view headings: Task Effort Clock_Summary
(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM")
; 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"))))

(defun my-clocktable-write (&rest args)
  "Custom clocktable writer.
Uses the default writer but shifts the first column right."
  (apply #'org-clocktable-write-default args)
  (save-excursion
    (forward-char) ;; move into the first table field
    (org-table-move-column-right)
    ;; (org-table-move-column-right)
    ;; (org-table-move-column-right)
    ;; (org-table-move-column-right)
    ))
(defun org-sum-effort ()
  "Add up all the TALLY properties of headings underneath the current one
The total is written to the TALLY_SUM property of this heading"
  (interactive)
  (org-entry-put (point) "EFFORT"
                 (org-minutes-to-clocksum-string  
                  (let ((total 0.0))
                    (save-excursion
                      (org-map-tree
                       (lambda ()
                         (let ((n (org-entry-get (point) "EFFORT")))
                           (when (stringp n)
                             (setq total (+ total (org-duration-string-to-minutes n))))))))
                   
total)))

)

Org-Roam Get Property by ID

Pretty sure I replaced this with the more global helper get-property but this one used org-id.

  (defun cz/org-roam-get-property-by-id (org-id regexp property)
    (cz/print "get-property-by-id")
    "Find an Org-roam file by ID, retrieve the specified PROPERTY and return it."
    (let* ((file (org-roam-node-file (org-roam-node-from-id org-id))))
      (if file
	(with-temp-buffer
	  (insert-file-contents file)
	  (goto-char (point-min))
	  (search-forward-regexp regexp nil t)
          (search-forward-regexp property nil t)
          (let* ((line (buffer-substring (line-beginning-position) (line-end-position)))
                 (len (length property))
                 (prop (substring line (+ len 3))))
            prop
          
          )
	)
      )
    )
  )

Org-Roam Sum Logbook Clocks

Sums the logbooks to determine how much time has been spent on a todo item.

(defun cz/org-roam-sum-logbook-clocks (org-id regexp)
  (cz/print "org-roam-sum-logbook")
  (let* ((file (org-roam-node-file (org-roam-node-from-id org-id)))
         (mins 0))
         (if file
           (with-temp-buffer
             (insert-file-contents file)
             (goto-char (point-min))
             (search-forward-regexp regexp nil t)
             (search-forward-regexp ":LOGBOOK:")
             (while (not (eobp))
               (let ((line (buffer-substring (line-beginning-position) (line-end-position))))
                 (when (string-match "CLOCK: .* => \\(.*\\)" line)
                   (let ((part (match-string 1 line)))
                     (let* ((clock (string-split part ":"))
                          (hour (string-to-number (nth 0 clock)))
                          (min (string-to-number (nth 1 clock))))
                       (setq mins (+ mins (+ (* hour 60) min )))
                     )
                   )
                 )
                (if (string-match ":END:" line)
                  (goto-char (point-max))
                  (forward-line 1)
                )
               )
             )
           (concat (number-to-string (/ mins 60)) ":" (number-to-string (- mins (* 60 (/ mins 60)))))
         )
    )
  ) 
)

Org Project Files

This function looks through the roam directory for any files of the name [time-created-id]-project_some-name.org.

  (setq org-project-list (list ))
  (setq org-project-list (directory-files-recursively "roam-dir/" "^[0-9]\\{14\\}-project_.*\.org$"))

  (setq org-project-tag-list (list ))
  (setq org-project-map (list ))

  (defun org--set-project-tag-list()
    (let* ((files org-project-list))
      (dolist (file files)
        (let* ((split (string-split file "/")))
          (with-current-buffer (find-file-noselect file)
            (let ((name (nth 1 (nth 0 (org-collect-keywords '("category")))))
                  (tag (nth 1 (nth 0 (org-collect-keywords '("nickname")))))
                  (id (cz/get-property file "ID"))
                  (file (nth 4 split)))
              (progn
                (add-to-list 'org-project-tag-list tag)
                (add-to-list 'org-project-map (cons tag (list (cons "NAME" name)
                                                              (cons "ID" id)
                                                              (cons "FILE" file))))
              )
            )
          )
        )
      )
    )
   )

DAILY AUTO-SCHEDULER

This is my magnum opus of elisp design. It is probably too highly specific for general use but I will try and get this into a public package. You set goals for the week of todo tasks associated with different projects and it changes their scheduled datetimes for a daily todo list. It also adds general daily things such as meals and habits you want to perform daily.

The Gist

My main objective with using Emacs is to organize my projects/work. As such, each project gets a file which hold notes and tasks associated with that project. I want tasks for the project to accomplish a few things. First, they should keep track of what needs to be accomplished, maintaining an estimate of how long something should take and how long I've spent thus far. This is done through the Effort property and the Time-Tracking mentioned in sections above. Second, tasks should provide a space to take notes on for that specific todo item. I.e. it should be an org-roam note. Lastly, it needs to show up in the agenda for the day. This is easily accomplished by scheduling it for the day, however, that's not good enough. The following block of elisp takes a weekly goal file that has been populated with tasks from various projects and schedules them automatically throughout the day. The program additionally adds in daily things like meals as well as single off events. I try and work on some general health habits such as running and stretching so those are included as well. The program handles time conflicts and shifts around the schedule to make things fit. It does this automatically at midnight every night, because who shuts down an emacs session, or when loaded. I also have a problem with working on side projects instead of real work so projects are broken out for after the workday and weekends. It is still a work in progress but I learned most of my elisp working on this

Actual Code

(require 'cl-lib)

(setq daily/today-file (concat org-path "today.org"))
(setq daily/yesterday-file (concat org-path "yesterday.old"))
(setq template-path "~/config/templates/")
(setq daily/today-template (concat template-path "today_template.org"))
(setq config-path "~/config/")
(setq daily/habit-rules (concat config-path "habit_rules.org"))
(setq daily/daily-rules (concat config-path "daily_rules.org"))
(setq daily/todo-file (concat org-path "todo.org"))
(setq daily/hour-split 12)
(setq daily/min-block (/ 60 daily/hour-split))
(setq daily/work-hours '("8:00" "17:00"))
(setq daily/project-hours '("17:00" "21:00"))
(setq daily/work-tags '("FL1" "NG" "RES" "FL1p5" "FL2"))
(setq daily/prio-2-int (list (cons "#A" 1)
                             (cons "#B" 2)
                             (cons "#C" 3)
                             (cons "#D" 4)
                             (cons "#E" 5)))
(setq daily/min-work-block "0:30")
(setq daily/work-block "2:00")
(setq daily/events ())
(setq work-heap (make-heap 'compare))
(setq project-heap (make-heap 'compare))

(setq todo-vector (make-vector (* daily/hour-split 24) nil))

(defun daily/reset-day-vector()
  (setq todo-vector (make-vector (* daily/hour-split 24) nil))
)

(defun daily/time-2-index(time)
  (let* ((time-parts (string-split time ":"))
         (hour (string-to-number (nth 0 time-parts)))
         (min (string-to-number (nth 1 time-parts)))
         (hour-idx (* hour daily/hour-split))
         (min-idx  (/ min (/ 60 daily/hour-split)))
         (idx (+ hour-idx min-idx)))
         idx
  )
)

(defun daily/index-2-time(idx)
  (let* ((hour (/ idx daily/hour-split))
         (min-split (/ 60 daily/hour-split))
         (sub (- idx (* daily/hour-split hour)))
         (min (* min-split sub))
         )
    (if (= min 0)
      (concat (number-to-string hour) ":00")
      (concat (number-to-string hour) ":" (number-to-string min))
    )
  )
)

(defun daily/get-events()
  (with-temp-buffer
    (insert-file-contents daily/todo-file)
    (goto-char (point-min))
    (let ((event-list ())) 
      (while (search-forward-regexp "\\** .* :EVENT:$" nil t)
        (let* ((start (line-beginning-position))
               (end (progn (forward-line 4) (line-end-position)))
               )
          (let ((event-array '()))
          (let ((event-lines (string-split (buffer-substring start end) "\n")))
            (if (string-match "^\\*\\*\\s-*\\(.*?\\)\\s-*:" (nth 0 event-lines))
                (let* ((event-title (match-string 1 (nth 0 event-lines))))
                  (add-to-list 'event-array (cons "title" event-title))
                  )
              (message "EVENT HAS NO TITLE")
              )
            (if (string-match "^:LENGTH: \\(.*\\)" (nth 2 event-lines))
                (let ((event-length (match-string 1 (nth 2 event-lines))))
                  (add-to-list 'event-array (cons "length" event-length))
                  
                  )
              (message "EVENT HAS NO LENGTH")
              )
            (if (string-match "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" (nth 4 event-lines))
                (let ((event-date (match-string 1 (nth 4 event-lines))))
                  (add-to-list 'event-array (cons "date" event-date))
                  )
              (message "EVENT NOT SCHEDULED")
              )
            (if (string-match "<[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} \\w\\{3\\} \\([0-9]\\{2\\}:[0-9]\\{2\\}\\)>" (nth 4 event-lines))
                (let ((event-time (match-string 1 (nth 4 event-lines))))
                  (add-to-list 'event-array (cons "time" event-time))
                  
                  )
              (message "EVENT NO TIME")
              )
            )
            (if (string= (cdr (assoc "date" event-array)) (format-time-string "%Y-%m-%d"))
              (push event-array event-list)
            )
            )
          )
        )
      event-list
      )
    )
  )

(defun daily/add-events()
  (setq daily/events ())
  (let ((event-list (daily/get-events)))
    (dolist (event event-list)
      (let ((time (cdr (assoc '"time" event)))
            (length (cdr (assoc '"length" event)))
            (name (cdr (assoc '"title" event))))
        (let ((idx (daily/time-2-index time))
              (len-idx (daily/time-2-index length)))
          (aset todo-vector idx name)
          (push name daily/events)
          (dotimes (i len-idx)
            (if (null (aref todo-vector (+ i idx)))
                (aset todo-vector (+ i idx) "X")
            )
          )
        )
      ) 
    )
  )
)

(defun daily/is-open(time-idx len-idx)
  (if (cl-some (lambda (x) x) (cl-subseq todo-vector time-idx (+ time-idx len-idx)))
    nil
    t
  )     
)

(defun daily/first-open-range (time-idx len-idx limit &optional min )
  (let ((j (if min -1 1)))
    (catch 'break
      (dotimes (i limit)
        (when (daily/is-open (+ time-idx (* i j)) len-idx)
              (throw 'break i)
        )
      )
    )
  )
)

(defun daily/insert-item(item time-idx len-idx shift-idx)

  (if (daily/is-open time-idx len-idx)
      (progn 
        (aset todo-vector time-idx item)
        (dotimes (i (- len-idx 1))
          (aset todo-vector (+ (+ i 1) time-idx) "X")
          )
        )
    (progn
      (message "%s" "ITEM CONFLICT")
      (let ((later (daily/first-open-range time-idx len-idx shift-idx))
            (earlier (daily/first-open-range time-idx len-idx shift-idx t)))
        
        (if (and later earlier)
            (if (< later earlier)
                (progn 
                  (aset todo-vector (+ time-idx later) item)
                  (dotimes (i (- len-idx 1))
                    (aset todo-vector (+(+ (+ i 1) time-idx) later) "X")
                    )
                  )
              (progn 
                (aset todo-vector (- time-idx earlier) item)
                (dotimes (i (- len-idx 1))
                  (aset todo-vector (-(+ (+ i 1) time-idx) earlier) "X")
                  )
                )
              )
          (if (null later)
              (if (null earlier)
                  (message (concat item ": CANT SHIFT WITHIN BOUNDS"))
                (progn 
                  (aset todo-vector (- time-idx earlier) item)
                  (dotimes (i (- len-idx 1))
                    (aset todo-vector (-(+ (+ i 1) time-idx) earlier) "X")
                    )
                  )
                )
            (progn 
              (aset todo-vector (+ time-idx later) item)
              (dotimes (i (- len-idx 1))
                (aset todo-vector (+(+ (+ i 1) time-idx) later) "X")
                )
              )
            )
          )
        )
      )
    )
  )

(defun daily/add-dailies()
  (let* ((items '("Breakfast" "Lunch" "Dinner" "Wake Up" "Sleep" "Clean Espresso")))
    (dolist (item items)
      (let* ((props (cz/get-all-properties daily/daily-rules item))
             (time (nth 0 (cdr (assoc '"TIME" props))))
             (weekend-time (nth 0 (cdr (assoc '"WEEKEND-TIME" props))))
             (len (nth 0 (cdr (assoc '"LENGTH" props))))
             (max-shift (nth 0 (cdr (assoc '"MAX-SHIFT" props))))
             (only (cdr (assoc '"ONLY" props)))
             (time-idx (daily/time-2-index time))
             (len-idx (daily/time-2-index len))
             (shift-idx (daily/time-2-index max-shift)))
        
         (if (member (format-time-string "%a") '("Sat" "Sun"))
           (when weekend-time
             (progn
               (setq time-idx (daily/time-2-index weekend-time))
             )
           )
         )

         (if only
           (when (member (format-time-string "%a") only)
             (daily/insert-item item time-idx len-idx shift-idx)
           )
           (daily/insert-item item time-idx len-idx shift-idx)
         )
         

       )
    )
  )
)

(defun daily/fill-the-night()
  (dotimes (i (length todo-vector))
    (when (string= (aref todo-vector i) "Wake Up")
      (dotimes (j i)
        (aset todo-vector j "X")
      )
    )
    (when (string= (aref todo-vector i) "Sleep")
      (dotimes (j (- (- (length todo-vector) i) 1))
        (aset todo-vector (+ (+ j i) 1) "X")
      )
    )
  )
)

(defun daily/get-open-after(item)
  (let ((pos (position item todo-vector :test 'string=)))
    (catch 'break
      (dotimes (i (- (length todo-vector) pos))
        (when (null (aref todo-vector (+ i pos)))
          (throw 'break (+ i pos))
        )
      )
    )
  ) 
)

(defun daily/add-habits()
  (let* ((items '("CON" "DEX" "STR" "WIS"))
         (mwf '("Mon" "Wed" "Fri")))
    (dolist (item items)
      (let* ((props (cz/get-all-properties daily/habit-rules item))
             (name (nth 0 (cdr (assoc '"NAME" props))))
             (link (nth 0 (cdr (assoc '"LINK" props))))
             (time (nth 0 (cdr (assoc '"TIME" props))))
             (len (nth 0 (cdr (assoc '"LENGTH" props))))
             (max-shift (nth 0 (cdr (assoc '"MAX-SHIFT" props))))
             (excluded (nth 0 (cdr (assoc '"EXCLUDED-WHEN" props))))
             (follows (nth 0 (cdr (assoc '"FOLLOWS-EVENT" props))))
             (follows-flag (member follows daily/events))
             (precedes (nth 0 (cdr (assoc '"PRECEDES-EVENT" props))))
             (precedes-flag (member precedes daily/events))
            )
             (if follows-flag
               (setq len (nth 0 (cdr (assoc '"FOLLOWS-LEN" props))))
               (if precedes-flag
                 (setq len (nth 0 (cdr (assoc '"PRECEDES-LEN" props))))
                 (when (null len)
                   (if (member (format-time-string "%a") mwf)
                     (setq len (nth 0 (cdr (assoc '"MWF-LENGTH" props))))
                     (setq len (nth 0 (cdr (assoc '"TT-LENGTH" props))))
                   )
                 )
               )
             )
             (let ((time-idx (daily/time-2-index time))
                  (len-idx (daily/time-2-index len))
                  (shift-idx (daily/time-2-index max-shift))
                  (linked-name (concat "[[" link "][" name "]]")))
               (if follows-flag
                 (setq time-idx (daily/get-open-after (nth 0 follows-flag)))
                 (when precedes-flag
                   (progn
                     (setq time-idx (- (position (nth 0 precedes-flag) todo-vector :test 'string=) len-idx))
                   )
                 )
               )
               (daily/insert-item linked-name time-idx len-idx shift-idx)
             )
      )
    )
  )
)

(defun extract-bracketed-substrings (str)
  "Extract all substrings within square brackets in STR."
  (let ((matches))
    (while (string-match "\\[\\([^][]+\\(?:][^][]+\\)*\\)\\]" str)
      (let ((match (match-string-no-properties 0 str)))
        (while (string-match "\\([^][]+\\)" match)
          (push (concat "[" (match-string-no-properties 0 match) "]") matches)
          (setq match (substring match (match-end 0)))))
      (setq str (substring str (match-end 0))))
    (butlast matches)
  )
)

(defun daily/read-bullet-points (file-path)
  "Read a list of bullet points from a file and return a list of strings."
  (with-temp-buffer
    (insert-file-contents file-path)
    (goto-char (point-min))
    (let ((bullet-points ()))
      (while (not (eobp))
        (when (looking-at "\\** \\[.\\].*$")
          (push (match-string 0) bullet-points)
        )
        (forward-line 1)
      )
      (reverse bullet-points)
    )
  )
)

(defun daily/get-tasks()
  ;; (message "Get weekly goals")
  (let* ((regexp (concat "^[0-9]\\{14\\}-week_" (format-time-string "%V_%Y") "_goals.org$"))
         (file (nth 0 (directory-files roam-path t regexp))))
    (if file
        (progn
          (with-temp-buffer
            (insert-file-contents file)
            (goto-char (point-min))
            (let ((goals-list))
              (while (not (eobp))
                (let ((line (buffer-substring (line-beginning-position) (line-end-position))))
                  (when (string-match "^** \\[\\(.\\)\\] \\[\\(..\\)\\] \\[\\[\\(.*\\)\\]\\[\\(.*\\): \\(.*\\)\\]\\]" line)
                    (let*((done (match-string 1 line))
                          (prio (cdr (assoc (match-string 2 line) daily/prio-2-int)))
                          (id (match-string 3 line))
                          (tag (match-string 4 line))
                          (name (match-string 5 line))
                          (parent-id (nth 0 (cdr (assoc '"ID" (cdr (assoc tag org-project-map))))))
                          (effort (cz/org-roam-get-property-by-id parent-id name "EFFORT"))
                          (clocksum (cz/org-roam-sum-logbook-clocks parent-id name))
                          (effort-blocks (daily/time-2-index effort))
                          (clocksum-blocks (daily/time-2-index clocksum)))

                      (when (not(string= done "x"))
                        (let ((prop-alist (list (cons "name" name)
                                                (cons "id" id)
                                                (cons "priority" prio)
                                                (cons "count" 0)
                                                (cons "tag" tag)
                                                (cons "effort" (- effort-blocks clocksum-blocks))
                                                (cons "parent" parent-id))
                                          ))
                          (push prop-alist goals-list)
                          )
                        )
                      )
                    )
                  (forward-line 1)
                  )
                )
              goals-list
              )
            )
          )
      )
    )
  )

(defun daily/get-empty-blocks()
  (let ((empty-blocks ())
        (start 0)
        (end 0)
        (in-block nil))
    (dotimes (i (length todo-vector))
      (let ((item (aref todo-vector i)))
        (if in-block
          (when item
            (progn 
              (setq end i) 
              (setq in-block nil)
              (push (list start end) empty-blocks)
            )
          )
          (when (null item)
            (progn (setq start i) (setq in-block t))
          )
        )
      ) 
    )
    (nreverse empty-blocks)
  )
)

(defun compare-time (a b)
  (let ((a_len (- (nth 1 a) (nth 0 a)))
        (b_len (- (nth 1 b) (nth 0 b))))
    (if (< (nth 0 a) (nth 0 b))
      t
      nil
    )

  )
)

(defun daily/filter-empty-blocks(empty-blocks)
  (let ((work-time-heap (make-heap 'compare-time))
        (project-time-heap (make-heap 'compare-time))
        (start-time (daily/time-2-index (nth 0 daily/work-hours)))
        (end-time (daily/time-2-index (nth 1 daily/work-hours))))
    (dolist (time-block empty-blocks)
        (let* ((start (nth 0 time-block))
               (end (nth 1 time-block))
               (len (- end start)))
          (when (> len daily/min-block)
            (if (< start start-time)
              (when (> end start-time)
                (heap-add work-time-heap (list start-time end))
              )
              (if (< end end-time)
                (heap-add work-time-heap (list start end))
                (if (> start end-time)
                  (heap-add project-time-heap (list start end))
                  (progn 
                    (when (> (- end-time start) daily/min-block)
                      (heap-add work-time-heap (list start end-time))
                    )
                    (when (> (- end end-time) daily/min-block)
                      (heap-add project-time-heap (list end-time end))
                    )
                  )
                )
              )
            )
          )
        )
    )
    (list work-time-heap project-time-heap)
  )
)

(defun compare (a b)
  (let ((a_prio (cdr (assoc '"priority" a)))
        (b_prio (cdr (assoc '"priority" b)))
        (a_count (cdr (assoc '"count" a)))
        (b_count (cdr (assoc '"count" b)))
        (a_effort (cdr (assoc '"effort" a)))
        (b_effort (cdr (assoc '"effort" b))))

    (if (< a_prio b_prio)
      t
      (if (< b_prio a_prio)
        nil
        (if (< a_count b_count)
          t
          (if (< b_count a_count)
            nil
            (if (< a_effort b_effort)
              nil
               (if (< b_effort a_effort)
                 t
                 t
               )
            )
          )
        )
      )
    ) 
  )
)

(defun daily/add-tasks()
  (let* ((todos (daily/get-tasks)))
    (dolist (item todos)
      (if (member (cdr (assoc '"tag" item)) daily/work-tags)
        (heap-add work-heap item)
        (heap-add project-heap item)
      )
    ) 
  ) 

  (let* ((time-blocks (daily/filter-empty-blocks (daily/get-empty-blocks)))
         (work-time-heap (nth 0 time-blocks))
         (project-time-heap (nth 1 time-blocks)))
     (while (and (not(heap-empty work-time-heap)) (not (heap-empty work-heap)) )
       (let* ((time-block (heap-delete-root work-time-heap))
              (task (heap-delete-root work-heap))
              (len-time (- (nth 1 time-block) (nth 0 time-block)))
              (len-task (cdr (assoc '"effort" task)))
              (name (cdr (assoc '"name" task)))
              (count (cdr (assoc '"count" task))))
         (aset todo-vector (nth 0 time-block) task)
         (if (<= len-task len-time)
           (progn
             (when (not (= len-task len-time))
               (let* ((end (nth 1 time-block))
                      (start (+ (nth 0 time-block) len-task))
                      (len (- end start))
                      (min-block (daily/time-2-index daily/min-work-block)))
                 (if (>= len min-block)
                   (heap-add work-time-heap (list (+ (nth 0 time-block) len-task) (nth 1 time-block)))
                 )
               )
             )
           )
           (progn
             (setf (cdr (assoc '"count" task)) (+ 1 (cdr (assoc '"count" task))))
             (setf (cdr (assoc '"effort" task)) (- (cdr (assoc '"effort" task)) len-time ))
             (heap-add work-heap task)  
           )
         )
       )
     )
    (when (not (heap-empty project-heap))
      (let* ((project (heap-delete-root project-heap))
             (name (cdr (assoc '"name" project))))
        (while (not(heap-empty project-time-heap))
          (let* ((project-time (heap-delete-root project-time-heap)))
            (aset todo-vector (nth 0 project-time) project)
          )    
        )
      )
    )
  )
)

(defun daily/vector-2-schedule()
  (with-temp-buffer daily/today-file
    (goto-char (point-min))
    (let ((last_edited (nth 0 (cz/get-property daily/today-file "lastEdited")))
          (today_date  (format-time-string "%Y-%m-%d"))
          (started-list (list)))
          (message "%s" last_edited)
         (if (string= last_edited (concat today_date "ha"))
             (message "already edited")
             (progn
               (message "Copying contents to yesterday.old")
               (copy-file daily/today-file daily/yesterday-file t)
               (insert-file-contents-literally daily/today-template)

               (goto-char (point-max))
               (dotimes (i (length todo-vector))
                 (let ((item (aref todo-vector i))
                       (time (daily/index-2-time i)))
                   (when item
                     (if (stringp item)
                       (when (not (string= "X" item))
                         (insert (concat "** " item "\n"))
                         (insert (concat "SCHEDULED:<${date} ${day} " time ">\n"))
      
                       )
                       (let* ((parent-id (cdr (assoc '"parent" item)))
                             (task-name (cdr (assoc '"name" item)))
                             (date (format-time-string "%Y-%m-%d"))
                             (day (format-time-string "%a"))
                             (new-date (concat "<" date " " day " " time ">"))
                             (count (cdr (assoc '"count" item))))
                              (let ((file (org-roam-node-file (org-roam-node-from-id parent-id))))
                                (when (not (member task-name started-list))
                                  (cz/remove-property file "SCHEDULED" task-name)
                                  (push task-name started-list)
                                )
                                (cz/set-property file "SCHEDULED" new-date task-name 0)
                             )
                           ;; (daily/replace-date parent-id task-name new-date)
                       )
                     )
                   )
                 ) 
               )

               (replace-string "${date}" today_date nil (point-min) (point-max))
               (replace-string "${day}" (format-time-string "%a") nil (point-min) (point-max))
               (write-region (point-min) (point-max) daily/today-file nil)
             )
         )
    )
  )
)

(defun daily/todo()
;; (toggle-debug-on-error)
  (interactive)
  (cz/print "reset day")
  (daily/reset-day-vector)
  (cz/print "add-events")
  (daily/add-events)
  (cz/print "add-dailies")
  (daily/add-dailies)
  (cz/print "fill night")
  (daily/fill-the-night)
  (cz/print "add-habits")
  (when (member (format-time-string "%a") '("Mon" "Tue" "Wed" "Thu" "Fri"))
    (daily/add-habits)
    (cz/print "add tasks")
    (daily/add-tasks)
  )
  (cz/print "schedule")
  (daily/vector-2-schedule)
)

(defun make-note-done()
  (let* ((line (buffer-substring (line-beginning-position) (line-end-position))))
     (when (string-match "** \\(TODO\\|WORKING\\|DONE\\) \\[..\\] \\[\\[id:\\(.*\\)\\]\\[\\(.*\\)\\]\\]" line)
       (let* ((state (match-string 1 line))
              (id (match-string 2 line))
              (title (match-string 3 line)))
        (when (string= state "DONE")
          (let* ((file (org-roam-node-file (org-roam-node-from-id (string-to-unibyte id))))
                 (original-buffer (current-buffer))
                 (split (string-split file "/"))
                 (buffer (nth (- (length split) 1) split)))
            
            (if (get-buffer buffer)
              (progn 
                (set-buffer buffer)
                (edit-title)
               )
              (with-temp-buffer
                (insert-file-contents file)
                (edit-title)
               )
            )
            (set-buffer original-buffer)
            (org-roam-db-sync)
          )
        )
       )
      )
    )
)

(defun edit-title()
   (goto-char (point-min))
   (when (search-forward-regexp "#\\+title: \\(.*\\): \\(.*\\)")
     (let ((tag (match-string 1))
           (name (match-string 2))
           (end (point)))
       (forward-line -1)
       (delete-region (point) end)
       (insert (concat ":END:\n#+title: DONE-" tag ": " name))
       (write-file file)
       (save-buffer)
     )
   )
)


(run-at-time "23:99" t 'daily/todo)
(add-hook 'org-after-todo-state-change-hook 'make-note-done)

SHADOW FILES

Actually one of the coolest little tools. I could find very little on how to use this so I'll make another post about how I have it set up.

(require 'shadowfile)
    
(setq shadow-debug nil)
(add-hook 'after-save-hook 'shadow-add-to-todo) 

MISC

Ripgrep

Replacement for grep and ag.

 (use-package rg
      :straight t
      :init (rg-enable-default-bindings))

Compilation Window

Blocks some annoying warnings from popping up after boot.

(setq warning-minimum-level :error)

Visual Bell

In some update they got sound working with WSL2 and this stops the window from beeping a million times when I do a bad thing.

(setq visible-bell t)

LOAD LATER

Some things need to be loaded later when using server/daemon setup

(defun fix-agenda()
  (interactive)
  ;; (setq org-agenda-files (quote ("roam-path/daily")))
  (setq org-agenda-files (quote ("org-path")))
  (add-to-list 'org-agenda-files '"roam-path/20220104200144-tracking_strength.org")
  (add-to-list 'org-agenda-files '"roam-path/20220106160156-tracking_dexterity.org")
  (add-to-list 'org-agenda-files '"roam-path/20220104200117-tracking_constitution.org")
  (add-to-list 'org-agenda-files '"roam-path/20220106090133-tracking_wisdom.org")
  (add-to-list 'org-agenda-files '"roam-path/20220827210800-tracking_intelligence.org")
  (add-to-list 'org-agenda-files '"roam-path/20220827210835-tracking_charisma.org")
  (add-to-list 'org-agenda-files '"roam-path/20211118174406-0103_tracking.org")
  

  (setq goal-agenda-files (directory-files-recursively "roam-path" "\\.*goals.org$"))
 

  (setq org-agenda-files (append org-agenda-files org-project-list))
  (setq org-agenda-files (append org-agenda-files goal-agenda-files))

  (org--set-project-tag-list)
   ;; must be run after ^
  (daily/todo)
)

(defun load-for-client ()
  
  (load-theme 'doom-cambox t)
  (doom-modeline-mode 1)
  (page-break-lines-mode)
  (fix-agenda)
  (setq truncate-lines nil)
  (setq org-capture-bookmark nil)
  
)


(if (daemonp)
    (add-hook 'after-make-frame-functions
	      (lambda (frame)
		(setq doom-modeline-icon t)
		(with-selected-frame frame
		  (load-for-client))))
  (load-for-client))