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
- ox-pandoc - https://github.com/kawabata/ox-pandoc
- https://www.masteringemacs.org/
- https://sachachua.com/blog/
- org-insert-todo-heading - maybe useful on mobile?
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.
(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
(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
- org mode: digital garden is also generated using org mode. My Emacs Configuration has a section
; 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"))))