This note contains my entire emacs configuration. For those who are curious, this is an exmaple of literate programming. This note itself is written in org mode format.

Org mode supports a feature called babel which allow org documents to contain executable code.

One time setup

This step is only needed one time. Execute the following block once.

echo "(org-babel-load-file \"${PWD}/configuration.org\")" > ~/.emacs
mkdir -p ~/.emacs.d
wget https://raw.githubusercontent.com/nobiot/md-roam/refs/heads/main/md-roam.el -P ~/.emacs.d/

Things to Look into

Emacs Debugging

Turn this on (set to t) if there's a need to debug emacs.

(setq debug-on-error nil)

User Information

(setq user-full-name "Harshad Shirwadkar")
(setq user-mail-address "harshadshirwadkar@gmail.com")

Define Platform

(setq hs/platform 'nil)

(cond
 ((file-exists-p "~/.emacs-on-android") (setq hs/platform 'hs/platform/android))
 ((file-exists-p "~/.emacs-on-work-linux") (setq hs/platform 'hs/platform/linux/work))
 ((eq system-type 'darwin) (setq hs/platform 'hs/platform/macos))
 (t (setq hs/platform 'hs/platform/linux)))

Emacs custom load file

I don't like it when ~/.emacs gets cluttered with custom stuff. Let's put that in a dedicated file.

(setq custom-file "~/.emacs.d/custom.el")
(load-file custom-file)

Automatically install use-package

First things first. Let's install use-package, which serves as our main package installing tool.

use-package

(require 'package)

(package-initialize)
(setq package-archives
      '(
	("org" . "https://orgmode.org/elpa/")
	("gnu" . "https://elpa.gnu.org/packages/")
	("melpa" . "https://melpa.org/packages/")
	("gnu-devel" . "https://elpa.gnu.org/devel/")
	))

(setq package-check-signature 'nil)

(unless (package-installed-p 'use-package)
  (unless package-archive-contents
    (package-refresh-contents))
  (package-install 'use-package))

quelpa

(use-package quelpa :ensure t)

Language Specific Settings

C

Indentation

(defun hs/c-indent/config-indent-80andNoTrail()
  (setq whitespace-line-column 80) ;; limit line length
  (setq whitespace-style '(face lines-tail))
  (add-hook 'prog-mode-hook 'whitespace-mode)
  (setq show-trailing-whitespace t)
  )

(defun hs/c-indent/config-indent-linux()
  (setq c-default-style "linux")
  ;; Use TABs of length of 8
  (setq indent-tabs-mode 1
	tab-width 8
	c-basic-offset 8)
  )

(add-hook 'c-mode-hook 'hs/c-indent/config-indent-linux)
(add-hook 'c-mode-common-hook 'hs/c-indent/config-indent-80andNoTrail)

;; For CamelCase Editing
(add-hook 'c-mode-common-hook
          (lambda () (subword-mode 1)))

Cscope

(use-package xcscope :ensure t)
(cscope-setup)

(global-set-key (kbd "\C-c s s") 'cscope-find-this-symbol)
(global-set-key (kbd "\C-c s d") 'cscope-find-global-definition)
(global-set-key (kbd "\C-c s g") 'cscope-find-global-definition)
(global-set-key (kbd "\C-c s G") 'cscope-find-global-definition-no-prompting)
(global-set-key (kbd "\C-c s c") 'cscope-find-functions-calling-this-function)
(global-set-key (kbd "\C-c s C") 'cscope-find-called-functions)
(global-set-key (kbd "\C-c s t") 'cscope-find-this-text-string)
(global-set-key (kbd "\C-c s e") 'cscope-find-egrep-pattern)
(global-set-key (kbd "\C-c s f") 'cscope-find-this-file)
(global-set-key (kbd "\C-c s i") 'cscope-find-files-including-file)

(global-set-key (kbd "\C-c s b") 'cscope-display-buffer)
(global-set-key (kbd "\C-c s B") 'cscope-display-buffer-toggle)
(global-set-key (kbd "\C-c s n") 'cscope-next-symbol)
(global-set-key (kbd "\C-c s N") 'cscope-next-file)
(global-set-key (kbd "\C-c s p") 'cscope-prev-symbol)
(global-set-key (kbd "\C-c s P") 'cscope-prev-file)
(global-set-key (kbd "\C-c s u") 'cscope-pop-mark)

(global-set-key (kbd "\C-c s a") 'cscope-set-initial-directory)
(global-set-key (kbd "\C-c s A") 'cscope-unset-initial-directory)

(global-set-key (kbd "\C-c s L") 'cscope-create-list-of-files-to-index)
(global-set-key (kbd "\C-c s I") 'cscope-index-files)
(global-set-key (kbd "\C-c s E") 'cscope-edit-list-of-files-to-index)
(global-set-key (kbd "\C-c s W") 'cscope-tell-user-about-directory)
(global-set-key (kbd "\C-c s S") 'cscope-tell-user-about-directory)
(global-set-key (kbd "\C-c s T") 'cscope-tell-user-about-directory)
(global-set-key (kbd "\C-c s D") 'cscope-dired-directory)

UI

(if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))

(when window-system
  (setq frame-title-format '(buffer-file-name "%f" ("%b")))
  (tooltip-mode -1)
  (mouse-wheel-mode t)
  (blink-cursor-mode -1))

(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
(ansi-color-for-comint-mode-on)
(delete-selection-mode 1)

(setq visible-bell t
      echo-keystrokes 0.1
      font-lock-maximum-decoration t
      inhibit-startup-message t
      transient-mark-mode t
      color-theme-is-global t
      shift-select-mode nil
      mouse-yank-at-point t
      require-final-newline t
      truncate-partial-width-windows nil
      uniquify-buffer-name-style 'forward
      ediff-window-setup-function 'ediff-setup-windows-plain
      dotfiles-dir (file-name-directory
                    (or (buffer-file-name) load-file-name))
      oddmuse-directory (concat dotfiles-dir "oddmuse")
      xterm-mouse-mode t
      save-place-file (concat dotfiles-dir "places"))

(add-to-list 'safe-local-variable-values '(lexical-binding . t))
(add-to-list 'safe-local-variable-values '(whitespace-line-column . 80))

(set-face-background 'vertical-border "white")
(set-face-foreground 'vertical-border "white")

;; Disable status and header lines for cleaner appearance
(setq-default header-line-format nil)
					; (setq-default mode-line-format nil)

					; (doom-modeline-mode 1)

Mode line

(setq-default mode-line-format (list "%e"
        mode-line-front-space mode-line-mule-info mode-line-client
        mode-line-modified mode-line-remote
        mode-line-frame-identification mode-line-buffer-identification
        " " mode-line-position  " " 
        mode-line-misc-info mode-line-end-spaces ))

Winner Mode

This mode allows me to undo the window configuration.

    (when (fboundp 'winner-mode)
      (winner-mode 1))

Scrolling

(setq mouse-wheel-scroll-amount '(1 ((shift) . 1))) ;; one line at a time
(setq mouse-wheel-progressive-speed t) ;; don't accelerate scrolling
(setq mouse-wheel-follow-mouse t) ;; scroll window under mouse~
(setq scroll-conservatively 100)

Line and Column Numbers

Enable Line Numbers and Column Numbers. This is enabled by three modes:

  • Line number mode: shows line numbers above mini-buffer
  • Column number mode: shows column numbers above mini-buffer
  • Linum mode: shows line numbers on the left hand side of the buffer
(line-number-mode 1)
(column-number-mode 1)
(autoload 'linum-mode "linum" "toggle line numbers on/off" t)
(if (display-graphic-p)
  (setq linum-format " %d")
  (setq linum-format "%4d | ")
  )

Olivetti Mode

This mode nicely adds gray colored fringes, making org mode look like google docs. Currently only enabled on Linux machines. On others it doesn't yet make a lot of sense. Only problem with this mode is that, large tables look terrible. But that might be an acceptable compromise. Just don't use this mode on notes where you have large tables.

(when (or (eq hs/platform 'hs/platform/linux)
	  (eq hs/platform 'hs/platform/linux/work))
  (use-package olivetti  :ensure t
    :custom
    (olivetti-body-width 97))

  (setq olivetti-style 'fancy
	olivetti-margin-width 8))

Themes

I generally prefer loading dark themes. But there are times when light theme makes sense. So, instead of changing config everytime, just check for file existence while loading theme. Terminal is always dark though.

Folowing logic loads theme based on presence of file ~/.emacs-light. If present, it's time to use light mode, otherwise dark. At some point I should look into integrating with system's light / dark status.

(use-package doom-themes :ensure t)
(use-package flucui-themes :ensure t)

(set-face-attribute 'default nil :font "Hack-12")

(defun hs/load-dark-theme ()
  "Load dark theme"
  (interactive)
  ;; (load-theme 'doom-plain-dark t)
  ;;(load-theme 'doom-homage-black t)
  ;; (load-theme 'doom-molokai t)
  (load-theme 'doom-city-lights)
  (set-face-attribute 'default nil :font "Hack-12")
  (custom-theme-set-faces
   'user
   '(org-level-1 ((t (:foreground "lightgreen" :bold t))))
   '(org-level-2 ((t (:foreground "#1abc9c" :bold t))))
   '(org-level-3 ((t (:foreground "lightgreen" :bold t))))
   '(org-scheduled-previously ((t (:foreground "lightgreen"))))
   '(org-document-title ((t (:height 200))))
   '(org-document-info ((t (:foreground "#1976D2"))))
   '(org-document-info-keyword ((t (:foreground "lightgray"))))
   '(org-drawer ((t (:foreground "lightgray"))))
   '(org-table ((t (:foreground "lightgray"))))
   '(org-block-begin-line
     ((t (:foreground "gray" :background "black"))))
   '(org-block-end-line
     ((t (:foreground "gray" :background "black"))))
   '(org-block
     ((t (:foreground "white" :background "black"))))
   '(org-link ((t (:foreground "#3daee9" :bold t))))
   '(link ((t (:foreground "#3daee9" :bold t))))
   '(default ((t (:foreground "#fcfcfc"))))
   )
  (set-face-background 'olivetti-fringe "gray27")
  )


; Theme that sets RFC style custimizations. Here is an exmaple:
; https://datatracker.ietf.org/doc/html/rfc7530#section-1.4.3.1
(defun hs/apply-light-customizations-rfc ()
  "Apply light theme customizations"
  (interactive)
  (set-face-attribute 'default nil :font "Hack-12" :foreground "black")
  (custom-theme-set-faces
   'user
   '(org-level-1 ((t (:foreground "black" :bold t))))
   '(org-level-2 ((t (:foreground "black" :bold t))))
   '(org-level-3 ((t (:foreground "black" :bold t))))
   '(org-level-4 ((t (:foreground "black" :bold t))))
   '(org-level-5 ((t (:foreground "black" :bold t))))
   '(org-document-title ((t (:foreground "black" :bold t :height 200))))
   '(org-document-info ((t (:foreground "dark gray"))))
   '(org-document-info-keyword ((t (:foreground "dark gray"))))
   '(org-drawer ((t (:foreground "lightgray"))))
   '(org-link ((t (:foreground "dodger blue" :bold nil))))
   '(link ((t (:foreground "dodger blue" :bold nil))))
   '(org-block-begin-line
     ((t (:foreground "dark gray" :height 75 :italic nil))))
   '(org-block-end-line
     ((t (:foreground "dark gray" :height 75 :italic nil))))
   '(org-table ((t (:foreground "black"))))
   '(org-block
     ((t (:foreground "black" :background "lemonchiffon2"))))
   '(org-meta-line
     ((t (:foreground "dodger blue"))))
   '(org-tag
     ((t (:foreground "dodger blue" :background nil :box nil :bold t))))

   )
  (setq org-startup-numerated t)
  (set-face-background 'olivetti-fringe "gray92")
)  

(defun hs/apply-light-customizations ()
  "Apply light theme customizations"
  (interactive)
  (set-face-attribute 'default nil :font "Hack-12" :foreground "black")
  (custom-theme-set-faces
   'user
   '(org-level-1 ((t (:foreground "seagreen" :bold t))))
   '(org-level-2 ((t (:foreground "darkgreen"))))
   '(org-level-3 ((t (:foreground "#4E7367"))))
   '(org-document-title ((t (:foreground "forest green" :bold t :height 200))))
   '(org-document-info ((t (:foreground "#527469"))))
   '(org-document-info-keyword ((t (:foreground "lightblue"))))
   '(org-drawer ((t (:foreground "lightgray"))))
   '(org-link ((t (:foreground "#527469" :bold nil))))
   '(link ((t (:foreground "#527469" :bold nil))))
   '(org-block-begin-line
     ((t (:foreground "dark gray" :background "gray93"))))
   '(org-table ((t (:foreground "dark olive green"))))
   '(org-block
     ((t (:foreground "black" :background "gray93"))))
   '(org-block-end-line
     ((t (:foreground "dark gray" :background "gray93"))))
   '(org-meta-line nil :height 0.8 :slant 'normal)
   )
  (set-face-background 'olivetti-fringe "gray92")

)  

(defun hs/load-light-theme ()
  "Load light theme"
  (interactive)
  (load-theme 'doom-homage-white t)
  (hs/apply-light-customizations-rfc)
  (add-hook 'org-mode-hook 'olivetti-mode)
    )

(if (display-graphic-p)
    (if (file-exists-p "~/.emacs-light")
	(hs/load-light-theme)
      (hs/load-dark-theme)
      ))

Following code is an experimental code that tries to automatically lookup system theme using dbus. I haven't been able to get it to work yet.

  ; (when (and IS-LINUX ;; this is doom specific
  ;           (featurep! :ui dbus)) ;; so is this
   ;; I should use a better name than `a`
  (defun theme--handle-dbus-event (a setting values)
      "Handler for FreeDesktop theme changes."
      (when (string= setting "ColorScheme")
	(let ((scheme (car values)))
	  (cond
	   ((string-match-p "Dark" scheme)
	    (hs/load-dark-theme)) ;; my custom function that sets a dark theme
	   ((string-match-p "Light" scheme)
	    (hs/load-light-theme)) ;; 1000 internet points to whoever guesses what this does
	   (t (message "I don't know how to handle scheme: %s" scheme))))))

    (require 'dbus)

    ;; since this is all FreeDesktop stuff, this *might* work on GNOME without changes

    (dbus-register-signal :session
			  "org.freedesktop.portal"
			  "/org/freedesktop/portal/desktop"
			  "org.freedesktop.impl.portal.Settings"
			  "SettingChanged"
			  #'theme--handle-dbus-event)
  (use-package org-bullets :ensure t)
  (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))

Tab Bar Mode

Some handy short-cuts from the manual.

C-x t 2 new tab
C-x t 0 close current tab
C-x t b Visit buffer in new tab
C-x t t Next command open in new tab
(tab-bar-mode 1)                           ;; enable tab bar
(setq tab-bar-show t)                      ;; hide bar if <= 1 tabs open
;(setq tab-bar-close-button-show t)       ;; hide tab close / X button
(setq tab-bar-new-tab-choice t);; buffer to show in new tabs
(setq tab-bar-tab-hints t)                 ;; show tab numbers
;(setq tab-bar-format '(tab-bar-format-tabs tab-bar-separator))
                                            ;; elements to include in bar
(global-set-key (kbd "s-1") (lambda () (interactive) (tab-bar-select-tab 1)))
(global-set-key (kbd "s-2") (lambda () (interactive) (tab-bar-select-tab 2)))
(global-set-key (kbd "s-3") (lambda () (interactive) (tab-bar-select-tab 3)))
(global-set-key (kbd "s-4") (lambda () (interactive) (tab-bar-select-tab 4)))
(global-set-key (kbd "s-5") (lambda () (interactive) (tab-bar-select-tab 5)))
(global-set-key (kbd "s-6") (lambda () (interactive) (tab-bar-select-tab 6)))
(global-set-key (kbd "s-7") (lambda () (interactive) (tab-bar-select-tab 7)))
(global-set-key (kbd "s-8") (lambda () (interactive) (tab-bar-select-tab 8)))
(global-set-key (kbd "s-9") (lambda () (interactive) (tab-bar-select-tab 9)))
(global-set-key (kbd "s-0") (lambda () (interactive) (tab-bar-select-tab 10)))
(global-set-key (kbd "C-t") (lambda () (interactive) (tab-bar-new-tab)))
(global-set-key (kbd "C-<tab>") (lambda () (interactive) (tab-bar-switch-to-next-tab)))

Some helpful Modes

Hippie Expand Mode

HippieExpand looks at the word before point and tries to expand it in various ways including expanding from a fixed list (like `‘expand-abbrev’’), expanding from matching text found in a buffer (like `‘dabbrev-expand’’) or expanding in ways defined by your own functions. Which of these it tries and in what order is controlled by a configurable list of functions.

(delete 'try-expand-line hippie-expand-try-functions-list)
(delete 'try-expand-list hippie-expand-try-functions-list)

Ido Mode

The ido.el package by KimStorm lets you interactively do things with buffers and files. As an example, while searching for a file with C-x C-f, ido can helpfully suggest the files whose paths are closest to your current string, allowing you to find your files more quickly.

;; ido-mode is like magic pixie dust!
(ido-mode t)
(setq ido-enable-prefix nil
        ido-enable-flex-matching t
        ido-create-new-buffer 'always
        ido-use-filename-at-point 'guess
        ido-max-prospects 10)
(setq ido-max-directory-size 100000)
(ido-mode (quote both))
; Use the current window when visiting files and buffers with ido
(setq ido-default-file-method 'selected-window)
(setq ido-default-buffer-method 'selected-window)

Ido Vertical Mode

(use-package ido-vertical-mode
  :ensure t
  :commands
  (ido-vertical-mode)
  :config
  (ido-vertical-define-keys 'C-n-C-p-up-and-down))

Diff Mode

;; Default to unified diffs
(setq diff-switches "-u")

(eval-after-load 'diff-mode
  '(progn
     (set-face-foreground 'diff-added "green4")
     (set-face-foreground 'diff-removed "red3")))

Tramp Mode

(setq tramp-default-method "ssh")

Auto revert mode

(global-auto-revert-mode t)

File Extension to Modes Mapping

(add-to-list 'auto-mode-alist '("COMMIT_EDITMSG$" . diff-mode))
(add-to-list 'auto-mode-alist '("\\.css$" . css-mode))
(add-to-list 'auto-mode-alist '("\\.ya?ml$" . yaml-mode))
(add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.js\\(on\\)?$" . js2-mode))
(add-to-list 'auto-mode-alist '("\\.xml$" . nxml-mode))
(add-to-list 'auto-mode-alist '("\\.\\(org\\|org_archive\\|txt\\)$" . org-mode))

Platform Specific Stuff

(when (eq hs/platform 'hs/platform/macos)
  (setq system-name (car (split-string system-name "\\."))))

(when (eq hs/platform 'hs/platform/android)
  (setq browse-url-browser-function 'browse-url-xdg-open)
  (global-visual-line-mode t)
  (setq org-agenda-tags-column -20))

(when (eq hs/platform 'hs/platform/linux/work)
  (org-babel-load-file "~/repo/org-work/google-config.org"))

(when (or (eq hs/platform 'hs/platform/linux)
	  (eq hs/platform 'hs/platform/linux/work))
  (set-fringe-style 0)
  (scroll-bar-mode)
  (menu-bar-mode)
  (setq org-agenda-tags-column 'auto))

Org Mode Config

Library

(defun hs/org-schedule-today ()
  "Schedule the current task to today."
  (interactive)
  (org-agenda-schedule 'nil (current-time)))

General Config

(require 'org)
(require 'org-mouse)
(require 'org-protocol)

(setq org-modules (quote (
                          org-id
                          org-habit
                          org-inlinetask
                          )))
(setq h-root-path "~/h")
(defun h-path (path)
  (concat h-root-path "/" path)
  )
(setq org-default-notes-file "inbox.org")


(setq org-export-with-broken-links t)
(setq org-use-fast-todo-selection t)

(setq org-treat-S-cursor-todo-selection-as-state-change nil)
(setq org-fontify-done-headline t)

(setq org-hide-emphasis-markers t)
(setq org-pretty-entities t)
(setq org-pretty-entities-include-sub-superscripts nil)
(setq org-hide-leading-stars nil)
					; 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 (("STYLE_ALL" . "habit"))))
(setq org-enforce-todo-dependencies t)
(setq org-startup-indented nil)
(setq org-cycle-separator-lines 1)
(setq org-blank-before-new-entry (quote ((heading)
                                         (plain-list-item . auto))))
(setq org-insert-heading-respect-content nil)
(setq org-special-ctrl-a/e t)
(setq org-special-ctrl-k t)
(setq org-yank-adjusted-subtrees t)
(setq org-id-method (quote uuid))
(setq org-deadline-warning-days 30)
(setq org-agenda-skip-scheduled-if-done t)

					; Use the current window for C-c ' source editing
(setq org-src-window-setup 'current-window)

					; Targets complete directly with IDO
(setq org-outline-path-complete-in-steps nil)

;;(setq org-completion-use-ido t)
					; Use the current window for indirect buffer display
(setq org-indirect-buffer-display 'current-window)
(setq org-hide-block-startup t)
(setq org-startup-indented t)
(global-set-key (kbd "s-b") (lambda () (interactive) (org-emphasize ?*)))
(global-set-key (kbd "s-i") (lambda () (interactive) (org-emphasize ?/)))
(global-set-key (kbd "s-u") (lambda () (interactive) (org-emphasize ?_)))
(setq org-tags-match-list-sublevels t)
(setq org-agenda-persistent-filter t)
(setq org-agenda-skip-additional-timestamps-same-entry t)
(setq org-table-use-standard-references (quote from))
(setq org-tags-column 0)

					; Overwrite the current window with the agenda
(setq org-agenda-window-setup 'current-window)
(setq org-clone-delete-id t)
(setq org-cycle-include-plain-lists t)
(setq org-startup-folded nil)

(setq org-catch-invisible-edits 'error)

Return follows links!

(setq org-return-follows-link t)

Org source code blocks configuration. Don't preserve indentation while editing source code blocks, use 0 indentation for source content and fontify natively.

(setq org-src-preserve-indentation nil)
(setq org-edit-src-content-indentation 0)
(setq org-src-fontify-natively t)

Enable inline images by default.

(setq org-startup-with-inline-images t)

UTF 8 Configuration.

(setq org-export-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
(set-charset-priority 'unicode)
(setq default-process-coding-system '(utf-8-unix . utf-8-unix))

Stuck projets are projects that have todos with tag project and at least one active todo.

;;(setq org-stuck-projects '("+project/-DONE" (*) () nil))

Use speed commands to quickly invoke important functions when at start of headline.

(setq org-use-speed-commands t)
(setq org-speed-commands '(
                           ("i" . org-clock-in)
                           ("o" . org-clock-out)
                           ("p" . hs/pomodoro)
                           ("r" . org-refile)
                           ("s" . org-save-all-org-buffers)
			   ("t" . org-todo)
                           ("n" . org-narrow-to-subtree)
                           ("w" . widen)
                           ("x" . hs/org-schedule-today)
                           ("z" . org-add-note)
			   ))

Enabled download mode for dired mode.

(add-hook 'dired-mode-hook 'org-download-enable)

No need to ask for confirmation during babel evaluation.

(setq org-confirm-babel-evaluate nil)

(setq org-plantuml-exec-mode "plantuml")

(setq org-plantuml-jar-path "~/bin/plantuml-gplv2-1.2024.3.jar")

(org-babel-do-load-languages 'org-babel-load-languages
			     '((shell . t)
			       
			       	(python . t)
							 (plantuml . t)
							 (gnuplot . t)
							 (dot . t)
							 ))

Use python3 for babel evaluation.

(setq org-babel-python-command "python3")

Org Download

(use-package org-download
  :ensure t
  :after org
  :custom
  (org-download-image-dir "~/base/notes/data")
  (org-image-actual-width 500)
  :config
  (require 'org-download)
  )

Org crypt

(require 'org-crypt)
(org-crypt-use-before-save-magic)
(setq org-crypt-key "harshadshirwadkar@gmail.com")
;; GPG key to use for encryption
;; Either the Key ID or set to nil to use symmetric encryption.

(setq auto-save-default nil)

Short Links

Emacs Special Links

(defun org-generic-shortlinks-open (url)
  "Open generic shortlinks"
  (browse-url (concat "http://" url)))

(defun org-tel-open (url)
  "Open generic shortlinks"
  (browse-url (concat "tehttp://" url)))

(defun org-harshad-shortlinks-open (url)
  "Open the google link"
  (browse-url (concat "http://go.harshad.me/" url)))

(defun org-gdoc-click (url)
  "Perform a specific action"
  (browse-url (concat "http://docs.google.com/document/d/" url)))

(defun org-emacs-click (url)
  "Perform a specific action"
  (if (string= url "agenda") (org-agenda))
  (if (string= url "capture") (org-capture))
  (if (string= url "roam-random") (org-roam-node-random))
  (if (string= url "toggle-sidebar") (dired-sidebar-toggle-sidebar))
  (if (string= url "run") (org-babel-execute-buffer))
)

(org-add-link-type "l" 'org-generic-shortlinks-open 'my/link-export)
(org-add-link-type "h" 'org-harshad-shortlinks-open)
(org-add-link-type "tel" 'org-tel-open)
(org-add-link-type "gdoc" 'org-gdoc-click)
(org-add-link-type "em" 'org-emacs-click)

TODO Obsidian Links

This works only on MacOS.

;; obsidan link handling for obsidian:// links
(defun org-obsidian-link-open (slash-message-id)
    "Handler for org-link-set-parameters that opens a obsidian:// link in obsidian"
    ;; remove any / at the start of slash-message-id to create real note-id
    (let ((message-id
           (replace-regexp-in-string (rx bos (* "/"))
                                     ""
                                     slash-message-id)))
      (do-applescript
       (concat "tell application \"Obsidian\" to open location \"obsidian://"
               message-id
               "\" activate"))))
;; on obsdian://aoeu link, this will call handler with //aoeu
(org-link-set-parameters "obsidian" :follow #'org-obsidian-link-open)

Logging

Logging of entries. On marking entries as done, also record the state change by mmodifying org-log-note-headings variable to reflect the state change. This allows such state changes to be tracked in weekly review.

(setq org-log-done (quote note))
(setq org-log-into-drawer t)
(setq org-log-state-notes-insert-after-drawers nil)

Tags excluded from inheritance

(setq
 org-tags-exclude-from-inheritance
 '("travel"
   "project"
   "crypt"
   "work_agenda"
   "personal_agenda"
   "area"
   "doc"))

"TODO" Keywords

(setq org-todo-keywords
      (quote (
	      (sequence
	       "TODO(t)"
	       "HOLD(h)"
	       "BLOCKED(b)"
	       "NEXT(n)"
	       "WORKING(w)"
	       "SOMEDAY(s)"
	       "GROUP(g)"
	       ; READING
	       "TO-READ(r)"
	       "PROJ(p)"
	       "|"

	       "DONE(d)"
	       "FINISHED-READING(f)"
	       "CANCELLED(c)"
	       "OBSOLETE(o)"
	       "GAVE-UP-READING(x)"
	       ))))
(setq org-todo-keyword-faces (quote (("TODO" :foreground "" :weight
      bold) ("WORKING" :foreground "cyan" :weight bold) ("BLOCKED"
      :foreground "pink" :weight bold) ("HOLD" :foreground "gray"
      :weight bold) ("NEXT" :foreground "blue" :weight bold) ("DONE"
      :foreground "forest green" :weight bold) ("DONE" :foreground
      "yellow" :weight bold) ("CANCELLED" :foreground "gray" :weight
      bold) )))

Refile Settings

Targets include this file and any file contributing to the agenda - up to 2 levels deep.

(setq org-refile-targets (quote ((nil :maxlevel . 2) (org-agenda-files
                                 :maxlevel . 2))))

Use full outline paths for refile.

(setq org-refile-use-outline-path 'file)

;; Allow refile to create parent tasks with confirmation

(setq org-refile-allow-creating-parent-nodes (quote confirm))

Roam

I use org roam to organize my notes. All the entries under ~/h are included - including the ones with "ARCHIVE" tag.

(use-package org-roam
  :ensure t
  :custom
  (org-roam-v2-ack t)
  (org-roam-directory (file-truename "~/base/"))
  (org-roam-graph-executable "/usr/local/bin/dot")
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
	 ("C-c n m" . org-roam-node-random)
	 ("C-c n s" . org-roam-db-sync)
	 ("C-c n o" . org-id-get-create)
	 ; Not using dailies as of now
	 ("C-c n t" . org-roam-dailies-goto-today)
	 ("C-c n d" . org-roam-dailies-goto-date)
	 )
  :config
  ; (setq org-roam-node-display-template (concat "${title:*} ${tags:*}"))
  (org-roam-db-autosync-mode)
)
(use-package helm :ensure t :config (helm-mode))
; Skip entries with ARCHIVE tag set.
; (setq org-roam-db-node-include-function
;      (lambda ()
;        (not (member "ARCHIVE" (org-get-tags)))))

On android, there isn't enough space to see tags in org roam node find functions. So skip tags when on android.

(when (eq hs/platform 'hs/platform/android)
  (setq org-roam-node-display-template (concat "${title:*}")))

https://www.orgroam.com/manual.html#Configuring-what-is-displayed-in-the-buffer

Markdown

(setq org-roam-file-extensions '("org" "md"))

Dailies

Update as of [2023-09-14 Thu] - Well using dailies makes it really hard to search for past notes. I get a feeling that I have written something at some time, but scrolling by days is too painful compared to scrolling by weeks. Going back to weeklies.

Update as of [2023-08-17 Thu] - Trying to use dailies. I think a clear file for each day maybe worth it.

Update as of [2023-06-25 Sun] - I don't use dailies.

I use dailies as weekly notes. For each week, I have a weekly note representing the tasks that are part of the week, some thoughts etc. That is the first note that opens up as soon as you launch emacs.

(setq org-roam-dailies-directory "~/base/daily/")

Quick Insert Nodes

As opposed to org-roam-node-insert, this function allows you to insert a "work" node immediately without you having to break your writing. Just use a different key for this! (C-c n w)

(defun hs/org-roam-node-insert-work ()
  (interactive)
 (org-roam-node-insert
   (lambda (node)
     (member "work" (org-roam-node-tags node)))
   :templates
   '(("w" "work" plain "%?"
      :if-new (file+head "~/base/notes/notes/${slug}.org"
                         "#+title: ${title}\n#+timestamp: %T\n#+filetags: work\n")
      :unnarrowed t :immediate-finish t))))

(global-set-key (kbd "C-c n w") 'hs/org-roam-node-insert-work)

Find Nodes by Tag

(defun hs/org-roam-find-by-tag ()
  (interactive)
  (setq tag
	(completing-read "tag: " '(
				   ("area" 1)
				   ("project" 2)
				   ("command" 3)
				   )
		   nil t ""))
  (org-roam-node-find nil nil
		      (lambda (node) (member tag (org-roam-node-list node)))
   )
  )

(global-set-key (kbd "C-c n C-f") 'hs/org-roam-find-by-tag)

Roam buffer

(add-to-list 'display-buffer-alist
             '("\\*org-roam\\*"
               (display-buffer-in-side-window)
               (side . right)
               (slot . 0)
               (window-width . 0.33)
               (window-parameters . ((no-other-window . t)
                                     (no-delete-other-windows . t)))))
(setq org-roam-mode-section-functions
      '((org-roam-backlinks-section :unique t)
	(org-roam-reflinks-section)
	(org-roam-unlinked-references-section)
	)
      )

Roam UI

Configure Roam UI. It is dependent on websocket and simple-httpd so load them first.

(use-package websocket :ensure t)
(use-package simple-httpd :ensure t)
(use-package org-roam-ui
  :bind
  (("C-c n u" . 'org-roam-ui-open)
   ("C-c n z" . 'org-roam-ui-node-zoom)
   ("C-c n n" . 'org-roam-ui-node-local))
  :ensure t)

Capture Templates

Function to return journaling prompts. This function returns a prompt from base/public/templates/journaling-prompts randomly.

Library

Defines following functions:

hs/get-journaling-prompt: Returns a single line from journaling prompts file. However, this is not needed anymore with org-ai.

hs/org-capture-at-point: Adds captured template to the current position. This is bound to C-c 0.

(defun hs/get-journaling-prompt ()
  "Returns a single line from journaling prompts."
  (save-window-excursion
    (find-file "~/base/public/templates/journaling-prompts")
    (goto-char (point-max))
    (let* ((number-of-prompts (- (line-number-at-pos) 10)))
      (goto-line (+ 10 (random number-of-prompts)))
      (s-chomp (thing-at-point 'line t)))))

(defun hs/org-capture-at-point ()
  "Insert an org capture template at point."
  (interactive)
  (org-capture 0))

(global-set-key (kbd "C-c 0") #'hs/org-capture-at-point)

  (defun transform-square-brackets-to-round-ones(string-to-transform)
    "Transforms [ into ( and ] into ), other chars left unchanged."
    (concat 
     (mapcar #'(lambda (c) (if (equal c ?[) ?\( (if (equal c ?]) ?\) c))) string-to-transform)))

(require 'org-protocol)

Org mode capture templates

Capture templates are defined in base/templates.

(require 'org-capture)

(setq org-capture-templates nil)


;; Section 1: Common capture patterns available on all platforms
(add-to-list 'org-capture-templates
	     '("j" "(j)ournal") t)
(add-to-list 'org-capture-templates
	     '("j5" "(5) Whys!" entry
	       (file "~/base/personal/org/journal.org")
	       (file "~/base/public/templates/5-whys.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("je" "(e)ntry" entry
	       (file "~/base/personal/org/journal.org")
	       (file "~/base/public/templates/journal-entry.capture") :prepend t))
(add-to-list 'org-capture-templates
	     '("jr" "(r)andom prompt" entry
	       (file "~/base/personal/org/journal.org")
	       (file "~/base/public/templates/journal-prompt.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("jt" "(t)thought Record" entry
	       (file "~/base/personal/org/journal.org")
	       (file "~/base/personal/templates/journal-thought-record.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("t" "(t)odo" entry (file "~/base/personal/org/inbox.org")
	       (file "~/base/public/templates/todo.capture")) t)
(add-to-list 'org-capture-templates
	     '("d" "(d)ecision" entry
	       (file "~/base/personal/org/inbox.org")
	       (file "~/base/public/templates/data-driven-decision.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("p" "(p)roject") t)
(add-to-list 'org-capture-templates
	     '("pp" "(p)ersonal project" entry
	       (file "~/base/personal/org/tasks.org")
	       (file "~/base/public/templates/project.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("pw" "(w)ork project" entry
	       (file "~/base/work/org/tasks.org")
	       (file "~/base/public/templates/project.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("f" "(f)eedback" entry
	       (file "~/base/personal/org/inbox.org")
	       (file "~/base/public/templates/feedback.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	     '("w" "(w)eekly notes") t)
(add-to-list 'org-capture-templates
	     '("wp" "weekly (p)lan"
	      entry (file "~/base/personal/org/weekly.org")
	      (file "~/base/public/templates/weekly-kickstart.capture") :prepend t) t)
(add-to-list 'org-capture-templates
	'("wr" "(r)etrospective entry"
	  entry (file "~/base/personal/org/weekly.org" :prepend t)
	  (file "~/base/public/templates/weekly-retrospective.capture")))

;; Section 2: Capture patterns not available on android
(when (not (eq hs/platform 'hs/platform/android))
  (add-to-list 'org-capture-templates
	       '("b" "(b)ookmark" entry (file "~/base/personal/org/inbox.org")
		 (file "~/base/public/templates/protocol.capture")) t)
  (add-to-list 'org-capture-templates
	       '("L" "add note to currently c(l)ocked Task"
		 entry (clock)
		 (file "~/base/public/templates/current-clocked.capture") :prepend t) t)
  (add-to-list 'org-capture-templates
	       '("c" "Command") t)
  (add-to-list 'org-capture-templates
	       '("cc" "regular (c)ommand" entry
		 (file "~/base/personal/org/inbox.org")
		 (file "~/base/public/templates/command.capture")) t)
  (add-to-list 'org-capture-templates
	       '("cd" "(d)ynamic command" entry
		 (file "~/base/personal/org/inbox.org")
		 (file "~/base/public/templates/dynamic-command.capture")) t)
  (add-to-list 'org-capture-templates
	       '("r" "(r)esource" entry
		 (file "~/base/personal/org/inbox.org")
		 (file "~/base/public/templates/resource.capture")) t)
  (add-to-list 'org-capture-templates
	       '("m" "(m)eeting" entry
		 (file "~/base/personal/org/inbox.org")
		 (file "~/base/public/templates/meeting.capture")) t)
  )

Org roam capture templates

Update on [2024-04-01 Mon] - okay back to org-roam. I think emacs wins after all. Starting new team today, and want to go back to things that have worked well in the past.

; This is commented. The goal is to use the default template. (setq org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :target (file+head "%<%Y-%m>.org" "#+title: Notes for %<%b %Y>\n"))))

(setq org-roam-capture-templates
      '(
	("w" "Work")
	("wd" "work doc" plain (file "~/base/work/templates/doc.capture")
	 :if-new (file "~/base/work/org/docs/${slug}.org")
	 :unnarrowed t)
	("wn" "work note" plain (file "~/base/public/templates/roam-note.capture")
	 :if-new (file "~/base/work/notes/${slug}.org")
	 :unnarrowed t)
	("wm" "work markdown note" plain (file "~/base/work/templates/markdown.capture")
	 :if-new (file "~/base/work/notes/${slug}.org")
	 :unnarrowed t)
	("wq" "work question" plain (file "~/base/public/templates/roam-question.capture")
	 :if-new (file "~/base/work/notes/questions/${slug}.org")
	 :unnarrowed t)
	
	("c" "Chords" plain (file "~/base/public/templates/chords.capture")
	 :if-new (file "~/base/sites/chords/posts/${slug}.org")
	 :unnarrowed t)
	("n" "Personal note" plain (file "~/base/public/templates/roam-note.capture")
	 :if-new (file "~/base/personal/notes/current/${slug}.org")
	 :unnarrowed t)
	("x" "Public note" plain (file "~/base/public/templates/roam-public-note.capture")
	 :if-new (file "~/base/public/notes/${slug}.org")
	 :unnarrowed t)
	("?" "question" plain (file "~/base/public/templates/roam-question.capture")
	 :if-new (file "~/base/personal/notes/current/${slug}.org")
	 :unnarrowed t)
	))

Instead of using org roam capture template for chords, use https://emacs.stackexchange.com/questions/40749/using-user-prompted-file-name-for-org-capture-in-template. This avoids having ID field initialized. You can add a custom ":noexport" tagged header below for org roam discoverability.

Custom ID Setter

(defun hs/org-roam-set-id ()
  (interactive)
  (message
   (cond
    ((string= (buffer-name) "weekly.org")
     (org-set-property "ID" (format-time-string "week-%Y-%U")
		       ))
    ((string= (buffer-name) "meeting.org")
     (org-set-property "ID" (format-time-string "meeting-%<%s>")
		       ))
    (t (org-id-get-create))
    ))
)

Agenda Configuration

Following table describes how files are put into org-agenda-files:

Personal Focus Work Focus
Base Directory ~/base/org/personal ~/base/work/org
Files with roam tag agenda agenda
(defun hs/roam-get-agenda-files ()
  "Return a list of note files containing work_agenda tag." ;
  (seq-uniq
   (seq-map
    #'car
    (org-roam-db-query
     [:select [nodes:file]
      :from tags
      :left-join nodes
      :on (= tags:node-id nodes:id)
      :where (like tag (quote "%\"agenda\"%"))]))))
(setq org-agenda-files (directory-files-recursively "~/base/personal/org" "\.org$"))
(setq org-directory "~/base/personal/org/")

(defun hs/org-focus-work() "Set focus to work"
       (interactive)
       (setq org-agenda-files (directory-files-recursively "~/base/work/org" "\.org$"))
       (setq org-directory "~/base/work/org")
       (add-to-list 'org-agenda-files "~/base/personal/org/habits.org")
       (setq org-agenda-files (append org-agenda-files (hs/roam-get-agenda-files)))
       (setq org-refile-targets (quote ((nil :maxlevel . 2) (org-agenda-files
							     :maxlevel . 2))))

       )

(defun hs/org-focus-personal() "Set focus to personal"
       (interactive)
       (setq org-directory "~/base/personal/org/")
       (setq org-agenda-files (directory-files-recursively "~/base/personal/org" "\.org$"))
       (setq org-agenda-files (append org-agenda-files (hs/roam-get-agenda-files)))
       (setq org-refile-targets (quote ((nil :maxlevel . 2) (org-agenda-files
							     :maxlevel . 2))))

       )

(defun hs/org-focus-clear() "Clear Focus"
       (interactive)
       (setq org-directory "~/base/personal/org")
       (setq org-agenda-files (directory-files-recursively "~/base/personal/org/" "\.org$"))
       (setq org-agenda-files
	     (append org-agenda-files
		     (directory-files-recursively "~/base/work/org/" "\.org$")))
       (setq org-agenda-files (append org-agenda-files (hs/roam-get-agenda-files)))
       (setq org-refile-targets (quote ((nil :maxlevel . 2) (org-agenda-files
							     :maxlevel . 2))))

       )


(global-set-key (kbd "\C-c f w") 'hs/org-focus-work)
(global-set-key (kbd "\C-c f p") 'hs/org-focus-personal)
(global-set-key (kbd "\C-c f c") 'hs/org-focus-clear)

(hs/org-focus-clear)

THIS CONFIG SHOULD NOT BE ENABLED BY DEFAULT.

This is a config that should only be enabled to see if there are any task related items present in nodes/ directory. If such items are present, either move them to inbox.org or move them to appropriate project under notes/projects.

(directory-files-recursively "~/base/work/org" "\.org$" nil nil t)
(setq org-agenda-sorting-strategy '(time-up))

(setq org-agenda-tags-column 'auto)

(setq org-agenda-start-with-log-mode t)

(add-hook 'org-agenda-mode-hook
          '(lambda ()
	    (hl-line-mode 1)
	    (abbrev-mode 1)
	    )
          'append)

Default agenda span is a day.

(setq org-agenda-span 'day)

Show habits in agenda view.

(setq org-habit-show-habits t)

Show habits on column 70.

(if (eq hs/platform 'hs/platform/android)
    (setq org-habit-graph-column 20)
  (setq org-habit-graph-column 70)
  )

Show habit consistency graphs.

(when (not (eq hs/platform 'hs/platform/android))
  (setq org-habit-show-all-today t))
(defun hs/org-get-prefix ()
  (let (
	(tags (org-get-tags (point)))
	(birthday (member "birthday" (org-get-tags (point))))
	(home (member "home" (org-get-tags (point))))
	(scheduled (org-get-scheduled-time (point)))
	(deadline (org-get-deadline-time (point)))
	)
    (if (member "urgent" tags) (setq prefix "🚨")
      (if (member "pinned" tags) (setq prefix "📌")
	(if scheduled (setq prefix "🕑")
	  (if deadline (setq prefix "🛑")
	    (setq prefix "  ")))))


    (if scheduled
	(concat prefix (format-time-string " %m/%d:" scheduled))
      (concat prefix (format-time-string " %m/%d:" deadline))
      )

    )
  )

(defun hs/org-get-resource-prefix ()
  (let (
	(tags (org-get-tags (point)))
	)
    (if (member "doc" tags)
	(setq prefix " 📄  ")
      (if (member "code" tags)
	  (setq prefix " 💽 ")
	(setq prefix "  ")
	))

    )
  )

(if (eq hs/platform 'hs/platform/android)
    (setq org-agenda-prefix-format
	  '(
	    (agenda . "")
	    (todo . "")
	    ))
    (setq org-agenda-prefix-format
	  '(
	    (agenda . "%-4.8c\t.. %-6e%-12t %-10(hs/org-get-prefix)   ")
	    (todo . "%-4.8c\t.. %-6e%-12t %-10(hs/org-get-prefix)   ")
	    ))
    )

Super Agenda

(use-package org-super-agenda :ensure t :config (org-super-agenda-mode))

(setq org-agenda-overriding-header "[[em:agenda][Home]] | [[file:~/base/personal/org/weekly.org::Weekly Notes][Weekly]] | [[file:~/base/personal/org/diary][Diary]]\n\n * Agenda *\n")
(setq org-super-agenda-groups '(
				(:discard (:todo "DONE"))
 				(:name "* Habits *\n" :habit t)
 				(:name "* Books *\n" :and (:tag "book"))
				(:name "* Urgent *\n" :and (:tag "urgent"))
 				(:name "* Agenda for Today *\n" :scheduled today :date today)
 				(:name "* Pins *\n" :and (:tag "pinned"))
 				(:name "* Overdue Items *\n" :scheduled past :deadline past)
				(:name "** Appointments ** \n" :and (:tag "appointment"))
				(:name "* Deadlines *\n" :deadline future)
 				(:name "* PROJECTS *\n" :and (:tag "project"))
				
 				)
      )

(setq org-agenda-custom-commands
      '(
	("t" "Tasks Dashboard"
	 (
	  (alltodo ""
		   (
		    (org-agenda-overriding-header "[[em:agenda][Home]] | [[file:~/base/personal/org/weekly.org::Weekly Notes][Weekly]] | [[file:~/base/personal/org/diary][Diary]]\n\n * Tasks *")
		    (org-agenda-prefix-format '((todo . "%-4.8c\t.. %-6e%-12t %-10(hs/org-get-prefix)   ")))
		    (org-super-agenda-groups
		     '(
		       (:name "** Habits **\n" :habit t)
 		       (:name "* Books *\n" :and (:tag "book"))

		       (:name "** Urgent **\n"
			      :and (:tag "urgent")
			      )
		       (:name "** Pinned **\n"
			      :and (:tag "pinned")
			      )
		       (:name "** Overdue ** \n"
			      :scheduled past
			      :deadline past
			      )
		       (:name "** Appointments ** \n"
			      :and (:tag "appointment")
			      )
		       (:name "** Active **\n"
			      :todo "WORKING"
			      :face (:foreground "peru"))
		       (:name "** Today! **\n"
			      :scheduled today
			      :deadline today)
		       (:name "** Upcoming! **\n"
			      :and (:scheduled future
					       :not (:tag "birthday"))
			      :and (:deadline future
					      :not (:tag "birthday"))
			      )
		       (:name "** Next **\n"
			      :todo "NEXT")
		       (:name "** Blocked **\n"
			      :todo "BLOCKED")
		       (:name "** Someday **\n"
			      :todo "SOMEDAY"
			      :order 100)
		       (:name "** Backlog **\n"
			      :and (:scheduled nil :deadline nil)
			      :order 90)
		       (:discard (:anything t))
		       ))
		    )
		   )
	  )
	 )
	("i" "Inbox"
	 (
	  (tags "+inbox" (
			  (org-agenda-overriding-header " * Inbox *")
			  )
		)
	  )
	 )
	("r" "Resources"
	 (
	  (tags "+doc|+code"
		(
		 (org-agenda-overriding-header "[[em:agenda][Home]] | [[file:~/base/personal/org/weekly.org][Weekly]] | [[file:~/base/personal/org/diary][Diary]]\n\n * Resources *")
		 (org-agenda-prefix-format '((tags . "%-5(hs/org-get-resource-prefix)")))
		 (org-super-agenda-groups
		  '(
		    (:name "📎 Pinned" :and (:tag "pinned"))
		    (:name "🔎 Needs Review" :and (:tag "review"))
		    (:name "✏️ Draft" :tag "draft")
		    (:name "💠 Rest" :anything)
		    ))
		 )
		)
	  )
	 )
	
	)
      )

Clocking

;; Separate drawers for clocking and logs
(setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))
(setq org-clock-into-drawer t)
(setq org-clock-out-remove-zero-time-clocks t)
(setq org-clock-out-when-done t)

Special Tags

; Tags with fast selection keys
(setq org-tag-alist (quote (
			    ("urgent" . ?u)
			    ("agenda" . ?a)
			    ("work" . ?w)
			    ("pinned" . ?p)
			    ("project" . ?r)
			    ("ARCHIVE" . ?x)
			    )))

; Allow setting single tags without the menu
(setq org-fast-tag-selection-single-key (quote expert))

Xeft!

(use-package deadgrep :ensure t)

(global-set-key (kbd "C-c d") #'deadgrep)
(use-package xeft :ensure t)
(setq xeft-directory "~/base/")
(setq xeft-recursive t)
(global-set-key (kbd "C-c x") #'xeft)

Prettify

(add-hook 'org-mode-hook (lambda ()
  "Beautify Org Checkbox Symbol"
  (push '("[ ]" .  "☐") prettify-symbols-alist)
  (push '("[X]" . "☑" ) prettify-symbols-alist)
  (push '("[-]" . "❍" ) prettify-symbols-alist)
  (prettify-symbols-mode)))

(defface org-checkbox-done-text
  '((t (:foreground "#71696A" :strike-through t)))
  "Face for the text part of a checked org-mode checkbox.")

(font-lock-add-keywords
 'org-mode
 `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)"
    1 'org-checkbox-done-text prepend))
 'append)

Highlight Text

Going forward, just use org-remark for highlights.

(add-to-list 'org-emphasis-alist
             '("/" (:background "yellow" :foreground "black")
               ))

Structured Templates

org-tempo converts <q to quote block once you hit tab.

(use-package org-tempo
  :ensure nil
  :after org
  :config
  (let ((templates '(("sh"  . "src sh")
                     ("el"  . "src emacs-lisp")
                     ("py" . "src python :results output")
                     ("ai" . "ai :max-tokens 256 :noweb yes")
		     )))
    (dolist (template templates)
      (push template org-structure-template-alist))))

Keybindings

(global-set-key (kbd "C-c c") 'org-capture)
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c t") 'hs/org-schedule-today)
(global-set-key (kbd "C-c i") 'org-clock-in)
(global-set-key (kbd "C-c o") 'org-clock-out)

Pomodoro

This is a simple library function that starts a pomodoro timer and automatically clocks in the current task. So the expected usage of this function is to put cursor on the current task and start a pomodoro timer.

(require 'notifications)

(defun hs/org-clock-out-and-notify ()
  (interactive)
  (org-clock-out)
  (notifications-notify
   :title "🍅 Pomodoro Timer Finished"
   :body "Pomodoro timer finished, please ensure.")
  )


(defun hs/pomodoro (mins)
  (interactive (list (read-number "🍅 Pomodoro Focus Time (mins): " 25)))
  (if (string= (buffer-name) "*Org Agenda*")
      (org-agenda-clock-in)
    (org-clock-in))
  (org-timer-set-timer (concat
			(number-to-string (/ mins 60))
			":"
			(number-to-string (% mins 60))
			":00"
			)
		       )
  (run-at-time  (concat (number-to-string mins) "min") nil 'hs/org-clock-out-and-notify)
  )

(global-set-key (kbd "C-c p") 'hs/pomodoro)

Reveal

(use-package ox-reveal :ensure t
  :custom
  (org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js")
  (org-reveal-mathjax t))

(use-package htmlize :ensure t)

org-tree-slide

(use-package org-tree-slide :ensure t)
(global-set-key (kbd "<f8>") 'org-tree-slide-mode)
(global-set-key (kbd "S-<f8>") 'org-tree-slide-skip-done-toggle)

Archiving

Change the location every year.

(setq org-archive-location "~/base/archives/2024/archive.org::* %s")

Diary

(setq diary-file "~/base/personal/org/diary")
(setq org-agenda-include-diary t)

Mobile

I don't use it anymore. So I'll leave it commented.

(require 'org-mobile) (setq org-mobile-directory "tmp/orgmobile") (setq org-mobile-capture-file "Capture.org") (setq org-mobile-inbox-for-pull "~/base/notes/org/inbox.org") (setq org-mobile-files (org-agenda-files)) (setq org-mobile-agendas '("z"))

Org-Remark

Update as of [2024-04-04 Thu]: Trying to use it again for doc comments and leaving TODOs inside docs.

Update as of ??: not using anymore

Allows you to leave annotations on plain text org files.

(use-package org-remark :ensure t
  :custom
  (org-remark-notes-file-name "~/base/work/org/remark.org")
  (org-remark-global-tracking-mode +1)

  :config
  (org-remark-create "commenter"
                     '(:underline "Green" :background "DarkSeaGreen1" :foreground "black")
                     '(CATEGORY "comment"))
  (define-key org-remark-mode-map (kbd "s-o") #'org-remark-open)
  (define-key org-remark-mode-map (kbd "s-<mouse-1>") #'org-remark-open)
  (define-key org-remark-mode-map (kbd "s-<mouse-3>") #'org-remark-mark)
  (define-key org-remark-mode-map (kbd "s-]") #'org-remark-view-next)
  (define-key org-remark-mode-map (kbd "s-[") #'org-remark-view-prev)
  (define-key org-remark-mode-map (kbd "s-d") #'org-remark-remove)
  (define-key org-remark-mode-map (kbd "s-r") #'org-remark-toggle)
  (define-key org-remark-mode-map (kbd "s-n") #'org-remark-mark-commenter)
  )

Gnuplot mode

(use-package gnuplot-mode :ensure t)

Org AI

For whatever reason, "string-equal-ignore-case" doesn't seem to work in my setup. This function is referenced through org-ai's "org-ai.el" file. I have simply just replaced it with "string=" for now. I then deleted all the ".elc" files from that folder and restarted emacs. Things work fine now, but something to watch out next time you update the config / update emacs or whatever.

(use-package org-ai
  :ensure t
  :commands (org-ai-mode
             org-ai-global-mode)
  :init
  (add-hook 'org-mode-hook #'org-ai-mode) ; enable org-ai in org-mode
  (org-ai-global-mode) ; installs global keybindings on C-c M-a
  :config
  )

(setq org-ai-image-directory "~/base/data/ai-images")

Set API token.

(org-babel-load-file "~/base/personal/notes/current/openai_api_token.org")

Transclusion

(use-package org-transclusion :ensure t)
(set-face-attribute
 'org-transclusion-fringe nil
 :foreground "gray75"
 :background "gray75")

Org Special Copy

This command does one of the following:

  • Copies from table cell if we are inside a table
  • Copies from source block if we are inside a block
(defun hs/org-special-copy()
  (interactive)
  (if (org-at-table-p)
      (kill-new
       (string-trim
        (substring-no-properties(org-table-get-field))))
    (kill-new (plist-get (cadr (org-element-at-point)) :value)))
  (message "Copied.")
  )
  

(global-set-key (kbd "<mouse-2>") 'hs/org-special-copy)
(global-set-key (kbd "C-c b") 'hs/org-special-copy)

Export Backends

(setq org-export-backends '(ascii html icalendar latex md odt))

Dired Sidebar Mode

(use-package dired-sidebar
  :bind (("C-x C-n" . dired-sidebar-toggle-sidebar))
  :ensure t
  :commands (dired-sidebar-toggle-sidebar)
  :init
  (add-hook 'dired-sidebar-mode-hook
            (lambda ()
              (unless (file-remote-p default-directory)
                (auto-revert-mode))))
  :config
  (push 'toggle-window-split dired-sidebar-toggle-hidden-commands)
  (push 'rotate-windows dired-sidebar-toggle-hidden-commands)

  (setq dired-sidebar-subtree-line-prefix "__")
  (setq dired-sidebar-theme 'vscode)
  (setq dired-sidebar-use-term-integration t)
  (setq dired-sidebar-use-custom-font nil))

(use-package vscode-icon
  :ensure t
  :commands (vscode-icon-for-file))

Keybindings

Misc Bindings

;; HS minor mode
(global-set-key (kbd "C-c h s") 'hs-minor-mode)
(global-set-key (kbd "C-c -") 'hs-hide-block)
(global-set-key (kbd "C-c h -") 'hs-hide-all)
(global-set-key (kbd "C-c =") 'hs-show-block)
(global-set-key (kbd "C-c h =") 'hs-show-all)

;; White-space mode
(global-set-key (kbd "C-c W") 'whitespace-mode)

;; Comment lines
(global-set-key (kbd "C-c / /") 'comment-or-uncomment-region)
(global-set-key (kbd "C-c / *") 'comment-region)
(global-set-key (kbd "C-c * /") 'uncomment-region)

;; ibuffer
(global-set-key (kbd "C-x C-b") 'ibuffer)

Simplified Bindings for Org Mode

Uncomment this block if you want to use simpler keybindings for Org mode.

(setq shift-select-mode t)
(setq org-support-shift-select t)

(global-set-key (kbd "<f1>") 'ibuffer)
(global-set-key (kbd "<f2>") 'delete-other-windows)
(global-set-key (kbd "M-t") 'org-ctrl-c-ctrl-c)
(global-set-key (kbd "M-s") 'org-schedule)
(global-set-key (kbd "M-d") 'org-deadline)
(global-set-key (kbd "M-a") 'org-agenda)
(global-set-key (kbd "M-c") 'org-capture)
(global-set-key (kbd "<f11>") 'org-clock-in)
(global-set-key (kbd "<f12>") 'org-clock-out)

Custom Faces

I like my headings to be larger than normal text

(custom-set-faces
  '(org-level-1 ((t (:inherit outline-1 :height 1.8))))
  '(org-level-2 ((t (:inherit outline-2 :height 1.4))))
  '(org-level-3 ((t (:inherit outline-3 :height 1.2))))
  '(org-level-4 ((t (:inherit outline-4 :height 1.1))))
  '(org-level-5 ((t (:inherit outline-5 :height 1.0))))
  '(org-table ((t (:extend t :foreground "black" :background "gray"))))
  '(org-document-title ((t (:height 250))))

)

Calendar

(setq calendar-latitude 47.79)
(setq calendar-longitude -122.18)
(setq calendar-location-name "Bothell, WA")

(defun solar-sunrise-string (date &optional nolocation)
  "String of *local* time of sunrise and daylight on Gregorian DATE."
  (let ((l (solar-sunrise-sunset date)))
    (format
     "%s (%s hours daylight)"
     (if (car l)
	 (concat "Sunrise " (apply 'solar-time-string (car l)))
       "no sunrise")
     (nth 2 l)
     )))

(defun diary-sunrise ()
  "Local time of sunrise as a diary entry.
  Accurate to a few seconds."
  (or (and calendar-latitude calendar-longitude calendar-time-zone)
      (solar-setup))
  (solar-sunrise-string date))


(defun solar-sunset-string (date &optional nolocation)
  "String of *local* time of sunset and daylight on Gregorian DATE."
  (let ((l (solar-sunrise-sunset date)))
    (format
     "%s"
     (if (cadr l)
	 (concat "Sunset  " (apply 'solar-time-string (cadr l)))
       "no sunset")
     )))

(defun diary-sunset ()
  "Local time of sunset as a diary entry.
  Accurate to a few seconds."
  (or (and calendar-latitude calendar-longitude calendar-time-zone)
      (solar-setup))
  (solar-sunset-string date))

Misc Configuration

;; make emacs use the clipboard
(setq x-select-enable-clipboard t)
(setq make-backup-files nil)
(put 'set-goal-column 'disabled nil)

;; Transparently open compressed files
(auto-compression-mode t)

;; Enable syntax highlighting for older Emacsen that have it off
(global-font-lock-mode t)

;; Save a list of recent files visited.
;; (recentf-mode 1)

;; Highlight matching parentheses when the point is on them.
(show-paren-mode 1)

;; (set-default 'indicate-empty-lines t)
(set-default 'imenu-auto-rescan t)

(add-hook 'text-mode-hook 'turn-on-auto-fill)

(defalias 'yes-or-no-p 'y-or-n-p)
(random t) ;; Seed the random-number generator

(set-face-attribute 'default nil :height 120)

(setq-default fill-column 92)

Server Starting

(Reference)

(require 'server)
(or (server-running-p)
    (server-start))

Post Config

;(find-file "~/base/personal/org/dashboard.org")
(setq warning-minimum-level 'error)

ARCHIVE

Configuration that is not used anymore

Obsidian Mode

This mode is not used as of [2024-04-16 Tue].

(use-package obsidian
  :ensure t
  :demand t
  :config
  (obsidian-specify-path "~/base/notes/notes")
  (global-obsidian-mode t)
  :custom
  ;; This directory will be used for `obsidian-capture' if set.
  (obsidian-inbox-directory "Inbox")
  (obsidian-daily-notes-directory "Journal")
  :bind (:map obsidian-mode-map
  ;; Trying to keep these consistent with org-roam mode.
  ("C-c m o" . obsidian-follow-link-at-point)
  ;; Jump to backlinks
  ("C-c m b" . obsidian-backlink-jump)
  ;; If you prefer you can use obsidian-insert-link
  ("C-c m l" . obsidian-insert-wikilink)))

(global-set-key (kbd "C-c m f") 'obsidian-search)
(global-set-key (kbd "C-c m c") 'obsidian-capture)

Links to this note