;; -*- lexical-binding: t; -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; windows performance tweaks (when (eq system-type 'windows-nt) ; https://lists.gnu.org/archive/html/bug-gnu-emacs/2012-10/msg00274.html ; https://gioorgi.com/2013/solving-emacs-freeze-andor-slowdown-on-windows7/ (setq w32-get-true-file-attributes nil) ; https://www.reddit.com/r/emacs/comments/c9ef5i/comment/esx5ndr/ (setq inhibit-compacting-font-caches t) ; https://www.reddit.com/r/emacs/comments/c9ef5i/comment/esx5snw/ (when (boundp 'w32-pipe-read-delay) (setq w32-pipe-read-delay 0)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; misc performance tweaks ;; Make gc pauses faster by decreasing the threshold. (setq gc-cons-threshold (* 2 1000 1000)) ;; disable warnings from popping up, they are still logged (setq warning-suppress-types '((comp))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; os specific config (when (eq system-type 'windows-nt) ; Fix resolution of ~ to match other client paths (setenv "HOME" "C:/Users/mcros/Nextcloud") (setq default-directory "C:/Users/mcros/Nextcloud") ; add executables to path ahead of them being used by extensions / emacs stuff (add-to-list 'exec-path "C:/Users/mcros/OneDrive/Programs/PortableApps/sqlite3") (add-to-list 'exec-path "C:/msys64/usr/bin/unzip.exe") (setenv "PATH" (concat "C:\\msys64\\mingw64\\bin;" (getenv "PATH"))) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Packages related (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) ; this goes in chemacs2 init -- DO NOT UNCOMMENT (package-initialize) ; this goes in chemacs2 init -- DO NOT UNCOMMENT (add-to-list 'package-selected-packages '(which-key all-the-icons revert-buffer-all scratch persistent-scratchrainbow-mode rainbow-delimiters focus zoom popwin dired-single diredfl doominhibitinhibit-modeline helpful helm helm-org dired-rainbow dired-rainbow-listing dired-single dash s origami modus-themes use-package) ) (add-to-list 'package-selected-packages '(xclip) ) (require 'use-package) ; ensure elisp plugins are compiled (require 'dash) (require 'f) (defun was-compiled-p (path) "Does the directory at PATH contain any .elc files?" (--any-p (f-ext? it "elc") (f-files path))) (defun ensure-packages-compiled () "If any packages installed with package.el aren't compiled yet, compile them." (--each (f-directories package-user-dir) (unless (was-compiled-p it) (byte-recompile-directory it 0)))) (ensure-packages-compiled) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dont wrap lines (set-default 'truncate-lines t) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; highlight current line global -- Do NOT use ;(global-hl-line-mode +1) ; this overrides std hl-line-mode buffer config ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; hl-line-mode based on major mode hook (on by default, off for listed major modes) ; to get major mode: (message "%s" major-mode) ; to get minor modes: (describe-mode) (add-hook 'minibuffer-setup-hook #'(lambda () (hl-line-mode 0))) (add-hook 'after-change-major-mode-hook #'(lambda () (hl-line-mode (if (member major-mode '(org-agenda-mode)) 0 1)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; backups (defconst my/backup-dir (expand-file-name "backups/" user-emacs-directory)) (unless (file-exists-p my/backup-dir) (make-directory my/backup-dir)) (setq backup-directory-alist `((".*" . ,my/backup-dir))) (setq auto-save-list-file-prefix my/backup-dir) (setq auto-save-file-name-transforms `(("\\(?:[^/]*/\\)*\\(.*\\)" ,(concat my/backup-dir "\\1") t))) (setq backup-by-copying t) ; safest form of backup file creation ; Config backups so we have *more*, not less (setq delete-old-versions t kept-new-versions 1 kept-old-versions 3 version-control t ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; add all-the-icons so status icons (normally handled by a font patched w/ nerd font) (require 'all-the-icons) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; which-key (require 'which-key) ;; Allow C-h to trigger which-key before it is done automatically ;(setq which-key-show-early-on-C-h t) ;; make sure which-key doesn't show normally but refreshes quickly after it is ;; triggered. ;(setq which-key-idle-delay 10000) ;(setq which-key-idle-secondary-delay 0.05) (which-key-mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dired adjustments (with-eval-after-load 'dired (require 'dired-x) ;; Set dired-x global variables here. For example: ;; (setq dired-x-hands-off-my-keys nil) ) (require 'dired-single) (setq dired-single-use-magic-buffer t) (setq dired-single-magic-buffer-name "dired - main") ; open dirs in same dired window + open files in main window ; facilitates static file browser sidebar (defun my/dired-open() (interactive) (setq file (dired-get-file-for-visit)) (if (equal (file-directory-p file) t) (progn (dired-single-buffer) ) (progn (dired-find-file-other-window) ) ) ) (defun my/dired-init () "Bunch of stuff to run for dired, either immediately or when it's loaded." ;; (define-key dired-mode-map [remap dired-find-file] 'my/dired-open) (define-key dired-mode-map [remap dired-mouse-find-file-other-window] 'my/dired-open) (define-key dired-mode-map [remap dired-up-directory] 'dired-single-up-directory)) ;; if dired's already loaded, then the keymap will be bound (if (boundp 'dired-mode-map) ;; we're good to go; just add our bindings (my/dired-init) ;; it's not loaded yet, so add our bindings to the load-hook (add-hook 'dired-load-hook 'my/dired-init)) ; icons / colors / line tunes (add-hook 'dired-mode-hook (lambda () (interactive) (all-the-icons-dired-mode 1) (hl-line-mode 1))) (use-package diredfl :commands diredfl-global-mode :init (diredfl-global-mode)) ; hide dired details by default, use `(` to toggle (add-hook 'dired-mode-hook 'dired-hide-details-mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; coding general (in case its ever on) (add-hook 'prog-mode-hook #'rainbow-delimiters-mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; origami config (use-package origami :demand :config (define-prefix-command 'origami-mode-map) (define-key ctl-x-map (kbd "z") 'origami-mode-map) (global-origami-mode) :bind (:map origami-mode-map ("o" . origami-open-node) ("O" . origami-open-node-recursively) ("c" . origami-close-node) ("C" . origami-close-node-recursively) ("a" . origami-toggle-node) ("t" . origami-recursively-toggle-node) ("R" . origami-open-all-nodes) ("M" . origami-close-all-nodes) ("v" . origami-show-only-node) ("k" . origami-previous-fold) ("j" . origami-forward-fold) ("x" . origami-reset))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Setup global keys (cua-mode 1) (windmove-default-keybindings 'meta) (global-set-key (kbd "") 'keyboard-escape-quit) (global-set-key (kbd "M-g") 'keyboard-quit) (global-set-key (kbd "M-m") 'menu-bar-open) (global-set-key (kbd "C-x C-z") nil) (global-set-key (kbd "C-g") 'goto-line) (global-set-key (kbd "C-c u") 'toggle-truncate-lines) (global-set-key (kbd "C-o") 'helm-find-files) (global-set-key (kbd "C-a") 'mark-whole-buffer) (global-set-key (kbd "C-S-a") 'my/smart-beginning-of-line) (global-set-key (kbd "M-a") 'my/smart-beginning-of-line) (global-set-key (kbd "C-S-e") 'my/smart-end-of-line) (global-set-key (kbd "M-e") 'my/smart-end-of-line) (global-set-key (kbd "C-c v") 'previous-buffer) (global-set-key (kbd "C-c w") (lambda () (interactive) (whitespace-mode) (whitespace-newline-mode) ) ) ; tmux keybind compatibility (setq windmove-wrap-around t) ; tmux does wraparound for window movements, enable for emacs (define-key global-map (kbd "C-b") (make-sparse-keymap)) ; prevent ardux typos w/ window sizing (global-set-key (kbd "C-") nil) (global-set-key (kbd "C-") nil) (global-set-key (kbd "C-") nil) (global-set-key (kbd "C-") nil) ; main tmux key binds ; *remember* C-b b in tmux sends a single C-b to the underlying tty (global-set-key (kbd "C-b c") (lambda () (interactive) (let ((frame (make-frame)) ; yes, this is silly ; need a name for the buffer and emacs always re-uses buffers based on name ; to keep w/ tmux, use an arbitrary, 'unique' name of the new buffer (buffer (get-buffer-create (format-time-string "%Y-%m-%d %H:%M:%S" (current-time))))) (select-frame-set-input-focus frame) (display-buffer buffer) (switch-to-buffer buffer) (delete-other-windows) ))) (global-set-key (kbd "C-b &") 'delete-frame) (global-set-key (kbd "C-b \"") 'split-window-below) (global-set-key (kbd "C-b %") 'split-window-right) (global-set-key (kbd "C-b x") 'delete-window) (global-set-key (kbd "C-b ") 'windmove-up) (global-set-key (kbd "C-b ") 'windmove-down) (global-set-key (kbd "C-b ") 'windmove-left) (global-set-key (kbd "C-b ") 'windmove-right) (global-set-key (kbd "C-b !") 'make-frame) (global-set-key (kbd "C-b C-") 'enlarge-window) (global-set-key (kbd "C-b C-") 'shrink-window) (global-set-key (kbd "C-b C-") 'enlarge-window-horizontally) (global-set-key (kbd "C-b C-") 'shrink-window-horizontally) (global-set-key (kbd "C-b :") 'helm-M-x) (global-set-key (kbd "C-b SPC") 'zoom-mode) ; use 'balance-windows function if not using zoom.el or golden-ratio ;; Lookup the current symbol at point. C-c C-d is a common keybinding ;; for this in lisp modes. (global-set-key (kbd "M-h") 'describe-bindings) (global-set-key (kbd "C-c C-d") #'helpful-at-point) (bind-key* "M-%" 'query-replace-region-or-from-top) (global-set-key (kbd "C-h") 'query-replace-region-or-from-top) ; more vscode/gui styled find/replace (global-set-key (kbd "C-s") 'isearch-forward) (global-set-key (kbd "C-f") 'isearch-forward) ; C-h find/replace (global-set-key (kbd "C-h") 'query-replace) (global-set-key (kbd "C-H") 'query-replace-regexp) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Set some global config (setq inhibit-splash-screen t) ; why do you need to tell me things i know by default (setq inhibit-startup-screen t) (setq inhibit-startup-echo-area-message t) (setq-default tab-width 4) ; sanity! (electric-indent-mode 0) ; return should NOT fuck with the indentation of the previous line ;; make typing delete/overwrites selected text (delete-selection-mode 1) (setq frame-inhibit-implied-resize t) (setq pixel-scroll-precision-mode t) ;; UTF-8 as default encoding (set-default-coding-systems 'utf-8) (set-language-environment "UTF-8") (set-default-coding-systems 'utf-8-unix) ; dont pop-up tooltips, show in echo area instead (setq tooltip-mode nil) (setq tooltip-use-echo-area t) ; auto revert when files/dired changes (setq global-auto-revert-non-file-buffers t) (global-auto-revert-mode 1) ; Clipboard integration (use-package xclip :config (xclip-mode 1)) ;; use y or n instead of yes or no (fset 'yes-or-no-p 'y-or-n-p) ; org mode syntax highlight code blocks (setq org-src-fontify-natively t) ; org mode - multiple agenda buffers (setq org-agenda-sticky t) ; org show file name in refile, also allows refiling to the file as a top level headline (setq org-refile-use-outline-path 'file) (setq org-outline-path-complete-in-steps nil) (setq org-refile-allow-creating-parent-nodes 'confirm) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; set font for emoji -- DOES NOT WORK ON MOBILE (when (eq system-type 'windows-nt) (set-fontset-font t '(#x1f300 . #x1fad0) (cond ((member "Noto Color Emoji" (font-family-list)) "Noto Color Emoji") ((member "Noto Emoji" (font-family-list)) "Noto Emoji") ((member "Segoe UI Emoji" (font-family-list)) "Segoe UI Emoji") ((member "Symbola" (font-family-list)) "Symbola") ((member "Apple Color Emoji" (font-family-list)) "Apple Color Emoji")) ;; Apple Color Emoji should be before Symbola, but Richard Stallman skum disabled it. ;; GNU Emacs Removes Color Emoji Support on the Mac ;; http://ergoemacs.org/misc/emacs_macos_emoji.html ;; ) (custom-theme-set-faces 'user '(default ((t ( :family "MonoLisa Variable" :height 100)))) '(variable-pitch ((t (:family "Atkinson Hyperlegible" :height 120)))) '(fixed-pitch ((t ( :family "MonoLisa Variable" :height 120)))) ) ; (custom-theme-set-faces ; 'user ; '(org-agenda ((t (:inherit fixed-pitch)))) ; '(org-block ((t (:inherit fixed-pitch)))) ; '(org-code ((t (:inherit (shadow fixed-pitch))))) ; '(org-document-info ((t (:inherit fixed-pitch)))) ; '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch))))) ; '(org-indent ((t (:inherit (org-hide fixed-pitch))))) ; '(org-link ((t (:inherit (shadow fixed-pitch))))) ; '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch))))) ; '(org-property-value ((t (:inherit fixed-pitch))) t) ; '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch))))) ; '(org-table ((t (:inherit fixed-pitch)))) ; '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8)))) ; '(org-verbatim ((t (:inherit (shadow fixed-pitch))))) ;) ;(add-hook 'org-mode-hook 'variable-pitch-mode) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; line numbers ; find major mode : M-x eval-expression [enter] major-mode (require 'display-line-numbers) (defcustom display-line-numbers-exempt-modes '(org-mode org-agenda-mode) "Major modes on which to disable the linum mode, exempts them from global requirement" :group 'display-line-numbers :type 'list :version "green") (defun display-line-numbers--turn-on () "turn on line numbers but excempting certain majore modes defined in `display-line-numbers-exempt-modes'" (if (and (not (member major-mode display-line-numbers-exempt-modes)) (not (minibufferp))) (display-line-numbers-mode))) (global-display-line-numbers-mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; whitespace (progn ;; Make whitespace-mode with very basic background coloring for whitespaces. ;; http://xahlee.info/emacs/emacs/whitespace-mode.html (setq whitespace-style (quote (face spaces tabs newline space-mark tab-mark newline-mark ))) ;; Make whitespace-mode and whitespace-newline-mode use “¶” for end of line char and “▷” for tab. (setq whitespace-display-mappings ;; all numbers are unicode codepoint in decimal. e.g. (insert-char 182 1) '( (space-mark 32 [183] [46]) ; SPACE 32 「 」, 183 MIDDLE DOT 「·」, 46 FULL STOP 「.」 (newline-mark 10 [8629 10]) ; LINE FEED, (tab-mark 9 [8594 9] [92 9]) ; tab ))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; modus-themes setup and enable (require-theme 'modus-themes) (setq modus-themes-region '(no-extend) modus-themes-fringes 'subtle modus-themes-scale-headings t ) (load-theme 'modus-vivendi :no-confirm) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; focus on text (turn on as desired/needed ; off by default (require 'focus) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; helm (setq completion-styles '(flex)) (require 'helm-color) (require 'helm-bookmark) (helm-autoresize-mode 1) (setq helm-display-header-line nil helm-autoresize-max-height 30 helm-autoresize-min-height 30) (setq helm-split-window-in-side-p t ; open helm buffer inside current window, not occupy whole other window helm-ff-file-name-history-use-recentf t helm-echo-input-in-header-line t) (global-set-key (kbd "C-x C-d") 'helm-browse-project) (global-set-key (kbd "C-x b") 'list-buffers) (global-set-key (kbd "C-x f") 'focus-mode) (global-set-key (kbd "C-x c") 'focus-read-only-mode) (define-key global-map [remap find-file] 'helm-find-files) (define-key global-map [remap occur] 'helm-occur) (define-key global-map [remap list-buffers] 'helm-buffers-list) (define-key global-map [remap dabbrev-expand] 'helm-dabbrev) (define-key global-map [remap execute-extended-command] 'helm-M-x) (define-key global-map [remap apropos-command] 'helm-apropos) (define-key global-map [remap bookmark-jump] 'helm-filtered-bookmarks) (unless (boundp 'completion-in-region-function) (define-key lisp-interaction-mode-map [remap completion-at-point] 'helm-lisp-completion-at-point) (define-key emacs-lisp-mode-map [remap completion-at-point] 'helm-lisp-completion-at-point)) (add-hook 'kill-emacs-hook #'(lambda () (and (file-exists-p "$CONF_FILE") (delete-file "$CONF_FILE")))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; doom-modeline (require 'doom-modeline) (setq all-the-icons-color-icons nil) (setq find-file-visit-truename t) (doom-modeline-mode 1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; flyspell (require 'flyspell) (setenv "LANG" "en_US") (setq ispell-program-name "hunspell") (setq ispell-dictionary "en_US") (use-package flyspell :demand t :config (use-package flyspell-correct-helm) (defun flyspell-most-modes() (add-hook 'text-mode-hook (lambda () (flyspell-mode 1) )) (add-hook 'prog-mode-hook (lambda () (flyspell-mode 1) )) (dolist (hook '(change-log-mode-hook log-edit-mode-hook org-agenda)) (add-hook hook (lambda () (flyspell-mode -1))))) (flyspell-most-modes) ) (defun flyspell-on-for-buffer-type () "Enable Flyspell appropriately for the major mode of the current buffer. Uses `flyspell-prog-mode' for modes derived from `prog-mode', so only strings and comments get checked. All other buffers get `flyspell-mode' to check all text. If flyspell is already enabled, does nothing." (interactive) (if (not (symbol-value flyspell-mode)) ; if not already on (progn (if (derived-mode-p 'prog-mode) (progn (message "Flyspell on (code)") (flyspell-prog-mode)) ;; else (progn (message "Flyspell on (text)") (flyspell-mode 1))) ))) (defun flyspell-toggle () "Turn Flyspell on if it is off, or off if it is on. When turning on, it uses `flyspell-on-for-buffer-type' so code-vs-text is handled appropriately." (interactive) (if (symbol-value flyspell-mode) (progn ; flyspell is on, t it off (message "Flyspell off") (flyspell-mode -1)) ; else - flyspell is off, turn it on (flyspell-on-for-buffer-type))) ; flyspell keyboard shortcuts (global-set-key (kbd "C-x y") 'flyspell-toggle) (global-set-key (kbd "C-x w") 'flyspell-correct-wrapper) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; zoom (replaces golden-ratio) (require 'zoom) ; sizing ;(setq zoom-size '(0.618 . 0.618)) (defun my-zoom-size-callback () (cond ((equal major-mode 'dired-mode) '(0.20 . 0.20)) ((equal major-mode 'eww-mode) '(0.75 . 0.75)) ((equal major-mode 'eshell-mode) '(0.75 . 0.75)) ((string-equal (window-dedicated-p) "side") '(0.15 . 0.15)) (t '(0.618 . 0.618)) )) ; tuning (custom-set-variables '(zoom-size 'my-zoom-size-callback) '(temp-buffer-resize-mode t) ; '(zoom-ignored-major-modes '(dired-mode)) ; '(zoom-ignore-predicates '((lambda () ; (if (string-equal (window-dedicated-p) "side") 'Y' nil) ; ))) ) (global-set-key (kbd "C-x +") 'zoom) (zoom-mode t) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; popup windows (use-package popwin :bind ( :map popwin:keymap ("h" . popwin:close-popup-window) ("r" . my/workspace-frame-scratch) ("R" . my/popwin-scratch) ("q" . my/popwin-quick-ref)) :config (global-set-key (kbd "C-w") popwin:keymap) (push "_quick_reference.org" popwin:special-display-config)) (popwin-mode 1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; scratch buffer ; set mode options -- borrowed from : https://emacsredux.com/blog/2014/07/25/configure-the-scratch-buffers-mode/ ; fundamental-mode / text-mode / org-mode (setq initial-major-mode 'text-mode) (setq initial-scratch-message "original scratch buffer") ; persistent-scratch (melpa) (persistent-scratch-autosave-mode 0) (global-set-key (kbd "C-c r S") 'persistent-scratch-save) (global-set-key (kbd "C-c r r") 'persistent-scratch-restore) (global-set-key (kbd "C-c r w") 'persistent-scratch-save-to-file) (global-set-key (kbd "C-c r l") 'persistent-scratch-restore-from-file) ; scratch buffers frame - use 'scratch and prompts to manage as needed (defun my/workspace-frame-scratch() (interactive) (let ((frame (make-frame))) (select-frame-set-input-focus frame) (delete-other-windows) (scratch 'text-mode) ) ) ; scratch buffers frame, prompt for initial mode - use 'scratch and prompts to manage as needed (defun my/workspace-frame-scratch-prompt() (interactive) (let ((frame (make-frame))) (select-frame-set-input-focus frame) (delete-other-windows) (scratch (let ((current-prefix-arg t)) (scratch--buffer-querymode))) ) ) ; scratch.el (multi-scratch / melpa) (global-set-key (kbd "C-c r s") #'my/workspace-frame-scratch) (global-set-key (kbd "C-c r p") #'my/workspace-frame-scratch-prompt) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; generic functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; open org agenda in top side window ; BUGGED AND UNUSED, agenda does NOT work properly in side windows (defun my/org-agenda-on-top () (defvar parameters '(window-parameters . ((no-other-window . t) (no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) (interactive) (display-buffer-in-side-window (get-buffer "*Org Agenda*") `((side . top) (slot . 0) (window-width . fit-window-to-buffer) (preserve-size . (t . nil)) , parameters)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; open text-mode scratch in right side window (defun my/scratch-text-on-right () (defvar parameters '(window-parameters . ((no-other-window . t) (no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) "Display `default-directory' in side window on right, hiding details." (interactive) (let ((buffer (scratch 'text-mode))) (display-buffer-in-side-window buffer `((side . right) (slot . -1) (window-width . fit-window-to-buffer) , parameters) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; open text-mode scratch in left side window (defun my/scratch-text-on-left () (defvar parameters '(window-parameters . ((no-other-window . t) (no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) "Display `default-directory' in side window on left, hiding details." (interactive) (let ((buffer (scratch 'text-mode))) (display-buffer-in-side-window buffer `((side . left) (slot . -1) (window-width . fit-window-to-buffer) , parameters) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; open text-mode scratch in right side window (defun my/scratch-prompt-on-right () (defvar parameters '(window-parameters . ((no-other-window . t) (no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) "Display `default-directory' in side window on left, hiding details." (interactive) (let ((buffer (scratch (let ((current-prefix-arg t)) (scratch--buffer-querymode))))) (display-buffer-in-side-window buffer `((side . right) (slot . -1) (window-width . fit-window-to-buffer) , parameters) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; open text-mode scratch in left side window (defun my/scratch-prompt-on-left () (defvar parameters '(window-parameters . ((no-other-window . t) (no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) "Display `default-directory' in side window on left, hiding details." (interactive) (let ((buffer (scratch (let ((current-prefix-arg t)) (scratch--buffer-querymode))))) (display-buffer-in-side-window buffer `((side . left) (slot . -1) (window-width . fit-window-to-buffer) , parameters) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; text-mode scratch buffer as a pop up window (defun my/popwin-scratch () (interactive) (popwin:popup-buffer (scratch 'text-mode))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; quick reference notes as a pop up window (defun my/popwin-quick-ref () (interactive) (when (eq system-type 'windows-nt) (popwin:find-file "~/org/_quick_reference.org")) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; modern home/end via C-a and C-e (defun my/smart-beginning-of-line () "Move point to `beginning-of-line'. If repeat command it cycle position between `back-to-indentation' and `beginning-of-line'." (interactive "^") (if (eq last-command 'smart-beginning-of-line) (if (= (line-beginning-position) (point)) (back-to-indentation) (beginning-of-line)) (back-to-indentation))) (defun my/smart-end-of-line () "Move point to `end-of-line'. If repeat command it cycle position between last non-whitespace and `end-of-line'." (interactive "^") (if (and (eq last-command 'my/smart-end-of-line) (= (line-end-position) (point))) (skip-syntax-backward " " (line-beginning-position)) (end-of-line))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; function to kill all non special, non active buffers (defun my/kill-other-buffers () "Kill all buffers but the current one. Don't mess with special buffers. Kill dired buffers" (interactive) ; dired buffer cleanup (mapc (lambda (buffer) (when (eq 'dired-mode (buffer-local-value 'major-mode buffer)) (kill-buffer buffer))) (buffer-list)) ; non special buffer cleanup (dolist (buffer (buffer-list)) (unless (or (eql buffer (current-buffer)) (not (buffer-file-name buffer))) (kill-buffer buffer)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; find/replace all (interactive) (defun my/query-replace-region-or-from-top () "If marked, query-replace for the region, else for the whole buffer (start from the top)" (interactive) (progn (let ((orig-point (point))) (if (use-region-p) (call-interactively 'query-replace) (save-excursion (goto-char (point-min)) (call-interactively 'query-replace))) (message "Back to old point.") (goto-char orig-point)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dock current buffer in given side panel (defun my/dock-current-buffer-on-side (selected_side) (defvar parameters '(window-parameters . ((no-delete-other-windows . t)))) (setq fit-window-to-buffer-horizontally t) (setq window-resize-pixelwise t) "Display `default-directory' in side window on left, hiding details." (interactive) (let ((buffer (current-buffer))) (display-buffer-in-side-window buffer `((side . ,selected_side) (slot . -1) (window-width . fit-window-to-buffer) , parameters) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dock current buffer on right (defun my/dock-current-buffer-on-right () (interactive) (my/dock-current-buffer-on-side 'right) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dock current buffer on right (defun my/dock-current-buffer-on-left () (interactive) (my/dock-current-buffer-on-side 'left) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dock current buffer on right (defun my/dock-current-buffer-on-top () (interactive) (my/dock-current-buffer-on-side 'top) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dock current buffer on right (defun my/dock-current-buffer-on-bottom () (interactive) (my/dock-current-buffer-on-side 'bottom) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; perform days math on a date string (defun my/date-string-days-math (date-string days-to-subtract) (format-time-string "%Y-%m-%d" (encode-time (decoded-time-add (parse-time-string date-string) (make-decoded-time :day days-to-subtract)) ) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; subtract 7 days from a date string (defun my/date-string-minus-seven-days (date-string) (my/date-string-days-math date-string -7) ) ; subtract 3 days from a date string (defun my/date-string-minus-three-days (date-string) (my/date-string-days-math date-string -3) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; various workspaces used day to day ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; create new frame if not inside termux (defun my/possibly-create-frame () (interactive) (let ((frame (make-frame))) (select-frame-set-input-focus frame)) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-mode workspace (defun my/workspace-org () (interactive) (my/possibly-create-frame) ; start with fresh frame (delete-other-windows) (my/kill-other-buffers) (ignore-errors (kill-buffer "*scratch*")) ; Open main files used as 'gateway' to everything else (find-file "~/org/_habits.org") (find-file "~/org/_todo.org") (find-file "~/org/_slipbox.org") (find-file "~/org/_index.org") ; top window for habits/scheduled (org-agenda nil "h") (goto-char 0) ; main todo/slipbox (split-window-below) (next-multiframe-window) (org-agenda nil "t") (goto-char 0) (next-multiframe-window) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-mode workspace for general kb work that doesnt use the agenda (defun my/workspace-org-index () (interactive) (my/possibly-create-frame) ; start with fresh frame (delete-other-windows) ; setup scratch on right (my/scratch-text-on-right) (goto-char 0) ; Open main files used as 'gateway' to everything else (find-file "~/org/_index.org") (goto-char 0) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; disable toolbar in gui (tool-bar-mode -1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; additional packages (add-to-list 'package-selected-packages '(org-tidy org-auto-expand ox-pandoc org-super-agenda org-alert burnt-toast alert) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Load misc extensions (require 'org) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-alert - windows only (when (eq system-type 'windows-nt) (use-package org-alert) (use-package burnt-toast :config (require 'burnt-toast-alert) (setq alert-default-style 'burnt-toast) ) (setq org-alert-interval 300 org-alert-notify-cutoff 5 org-alert-notify-after-event-cutoff 5) (org-alert-enable) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Update/add auto file handling (add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; hide property drawers / minimize them (use-package org-tidy :ensure t :config (setq org-tidy-top-property-style 'keep) (setq org-tidy-properties-style 'inline) (setq org-tidy-properties-inline-symbol "↕") (setq org-tidy-property-drawer-flag t) ;org-tidy-property-drawer-property-whitelist (setq org-tidy-property-drawer-property-blacklist '("TIDY_DISABLE" "TIDY_OFF")) (setq org-tidy-general-drawer-flag t) ;org-tidy-general-drawer-name-whitelist ;org-tidy-general-drawer-name-blacklist :hook (org-mode . org-tidy-mode)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; keyboard shortcuts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Setup global keys (global-set-key (kbd "C-c a") 'org-agenda) (global-set-key (kbd "C-c c") 'org-capture) (global-set-key (kbd "C-c m x") 'org-cut-subtree) (global-set-key (kbd "C-c m r") 'org-archive-subtree) (global-set-key (kbd "C-c m s") 'org-agenda-schedule) (global-set-key (kbd "C-c m d") 'org-agenda-deadline) (global-set-key (kbd "C-w t t") 'org-tidy-untidy-buffer) (global-set-key (kbd "C-w t h") 'org-tidy-toggle) (global-set-key (kbd "C-c d n") (lambda () (interactive) (find-file "~/org/_index.org")) ) (global-set-key (kbd "C-c d t") (lambda () (interactive) (find-file "~/org/_todo.org")) ) (global-set-key (kbd "C-c d s") (lambda () (interactive) (find-file "~/org/_slipbox.org")) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Set some orgmode config (defun my/org_timestamp () (interactive) (org-insert-time-stamp (current-time) (current-time) "t") ) (org-defkey org-mode-map (kbd "C-c !") #'my/org_timestamp) (setq org-startup-folded t) (setq org-return-follows-link t) (setq org-startup-folded t) (setq org-support-shift-select t) (setq org-log-into-drawer t) (setq org-log-done t) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; automatic ids for everything ; From https://web.archive.org/web/20220512120917/https://stackoverflow.com/questions/13340616/assign-ids-to-every-entry-in-org-mode ; From https://stackoverflow.com/questions/13340616/assign-ids-to-every-entry-in-org-mode ; This hunk of lisp automatically adds IDs to every header in an org file when you hit save. it usese org-id which can generated IDs of various types including timestamps. I’m using the default UUIDs (defun stackoverflow/org-add-ids-to-headlines-in-file () "cargo cult from stackoverflow to add ids" (interactive) (org-map-entries 'org-id-get-create) ) (add-hook 'org-mode-hook (lambda () (add-hook 'before-save-hook 'stackoverflow/org-add-ids-to-headlines-in-file nil 'local))) (add-hook 'org-capture-prepare-finalize-hook 'org-id-get-create) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; keep 'ARCHIVE' property up to date for entire buffer so any archives are filed in 'todays' archive ; assumes you want archive dates to be the date of the archive operation, not the last time they were 'touched' (or created) (defun my/org-set-archive-prop-for-todo () (interactive) (save-excursion (goto-char (point-max)) (while (outline-previous-heading) (org-set-property "ARCHIVE" (concat "~/Attic/org-mode/%s/" (format-time-string "%Y" (current-time)) "/" (format-time-string "%m" (current-time)) ".archive.org::datetree/" )) )) ) (add-hook 'org-mode-hook (lambda () (add-hook 'before-save-hook 'my/org-set-archive-prop-for-todo nil 'local))) (add-hook 'org-capture-prepare-finalize-hook 'my/org-set-archive-prop-for-todo) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-mode todo tuning ; the ! tells org to log state changes for todo entries (setq org-todo-keywords '((sequence "TODO(t!)" "READY(r!)" "WIP(w!)" "|" "SKIPPED(k!)" "COMPLETE(c!)" "CANCELED(x!)")) ) (setq org-todo-keyword-faces '(("TODO" . "turquoise") ("READY" . "yellow") ("WIP" . "magenta") ("COMPLETE" . "green") ("CANCELED" . "orange")) ) ; log to drawer of entry (setq org-log-into-drawer t) (setq org-treat-insert-todo-heading-as-state-change t) ; set completion stats to auto-calc for checkboxes and nested todo's (setq org-provide-todo-statistics t) (setq org-checkbox-hierarchical-statistics nil) (setq org-hierarchical-todo-statistics nil) ; From https://christiantietze.de/posts/2021/02/emacs-org-todo-doing-done-checkbox-cycling/ ; Ensure the uppermost todo, in a nested tree, has an appropriate status based on all sub-items in the tree ; Basically: - can only be marked 'complete' if all sub-items are in a completed state ; - auto-marked as 'todo' if no sub-items are complete ; - auto-marked as 'wip' if some sub-items are complete ; - auto-marked as 'complete' if all sub-items are complete (defun org-todo-if-needed (state) "Change header state to STATE unless the current item is in STATE already." (unless (string-equal (org-get-todo-state) state) (org-todo state))) (defun ct/org-summary-todo-cookie (n-done n-not-done) "Switch header state to DONE when all subentries are DONE, to TODO when none are DONE, and to DOING otherwise" (let (org-log-done org-log-states) ; turn off logging (org-todo-if-needed (cond ((= n-done 0) "TODO") ((= n-not-done 0) "COMPLETE") (t "WIP"))))) (add-hook 'org-after-todo-statistics-hook #'ct/org-summary-todo-cookie) (defun ct/org-summary-checkbox-cookie () "Switch header state to DONE when all checkboxes are ticked, to TODO when none are ticked, and to DOING otherwise" (let (beg end) (unless (not (org-get-todo-state)) (save-excursion (org-back-to-heading t) (setq beg (point)) (end-of-line) (setq end (point)) (goto-char beg) ;; Regex group 1: %-based cookie ;; Regex group 2 and 3: x/y cookie (if (re-search-forward "\\[\\([0-9]*%\\)\\]\\|\\[\\([0-9]*\\)/\\([0-9]*\\)\\]" end t) (if (match-end 1) ;; [xx%] cookie support (cond ((equal (match-string 1) "100%") (org-todo-if-needed "COMPLETE")) ((equal (match-string 1) "0%") (org-todo-if-needed "TODO")) (t (org-todo-if-needed "WIP"))) ;; [x/y] cookie support (if (> (match-end 2) (match-beginning 2)) ; = if not empty (cond ((equal (match-string 2) (match-string 3)) (org-todo-if-needed "COMPLETE")) ((or (equal (string-trim (match-string 2)) "") (equal (match-string 2) "0")) (org-todo-if-needed "TODO")) (t (org-todo-if-needed "WIP"))) (org-todo-if-needed "WIP")))))))) (add-hook 'org-checkbox-statistics-hook #'ct/org-summary-checkbox-cookie) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; files (setq org-default-notes-file "~/org/_index.org") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; agendas ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; helper for building agenda file lists (defun my/load-org-agenda-files-recursively (dir) "Find all directories in DIR." (unless (file-directory-p dir) (error "Not a directory `%s'" dir)) (unless (equal (directory-files dir nil org-agenda-file-regexp t) nil) (add-to-list 'org-agenda-files dir) ) (dolist (file (directory-files dir nil nil t)) (unless (member file '("." "..")) (let ((file (concat dir file "/"))) (when (file-directory-p file) (load-org-agenda-files-recursively file) ) ) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; set org habit status for yesterday and schedule for today. this is to facilitate adding "skipped" log entries for better reporting and tracking of habits ; this assumes youre operating on daily habits ; https://emacs.stackexchange.com/questions/9433/how-to-make-org-prompt-for-a-timestamp-when-changing-state-of-a-todo/63809 ; https://stackoverflow.com/questions/25437069/how-can-i-mark-org-habits-as-done-in-the-past (defun my/org-habit-with-date (&optional arg) (interactive "P") (setq my-activation-time (current-time)) (cl-letf* ((org-read-date-prefer-future nil) (org-popup-calendar-for-date-prompt nil) (my-current-time (org-read-date t t nil "when:" nil "-1d" nil)) ((symbol-function 'current-time) #'(lambda () my-current-time)) ((symbol-function 'org-today) #'(lambda () (time-to-days my-current-time))) ((symbol-function 'org-current-effective-time) #'(lambda () my-current-time)) (super-org-entry-put (symbol-function 'org-entry-put)) ((symbol-function 'org-entry-put) #'(lambda (pom property value) (print property) (if (lambda (or (equal property "LAST_REPEAT") (equal property "SCHEDULED"))) (let ((my-value (format-time-string (org-time-stamp-format t t) my-current-time))) (funcall super-org-entry-put pom property my-value)) (funcall super-org-entry-put pom property value) )))) (if (eq major-mode 'org-agenda-mode) (org-agenda-todo arg) (org-todo arg)) ) (if (eq major-mode 'org-agenda-mode) (org-agenda-schedule arg my-activation-time) (org-schedule arg my-activation-time) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-agenda tuning (view stuff only) ;; Remove completed deadline tasks from the agenda view (setq org-agenda-skip-deadline-if-done t) ;; Remove completed scheduled tasks from the agenda view (setq org-agenda-skip-scheduled-if-done t) ;; Remove completed items from search results (setq org-agenda-skip-timestamp-if-done t) ;; Dont show deadline pre-warning if already scheduled (scheduled means the time when you /start work/ on a todo ; this is very different than norms ;; see https://orgmode.org/manual/Deadlines-and-Scheduling.html (setq org-agenda-skip-deadline-prewarning-if-scheduled t) ;; dont tend to care about tag inheritance outside of search (setq org-agenda-use-tag-inheritance nil) ;; add new option to update habit for yesterday and reschedule it as active today (add-hook 'org-agenda-mode-hook (lambda () (local-set-key (kbd "y") 'my/org-habit-with-date))) (setq org-agenda-use-time-grid nil) (setq org-agenda-window-setup "current-window") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Set some org agendas/dashboards (setq org-agenda-sorting-strategy '( (agenda habit-down scheduled-up deadline-up scheduled-up timestamp-up time-up priority-down category-keep) (todo habit-up time-down scheduled-down deadline-down todo-state-down priority-down alpha-up) (tags priority-down category-keep) (search category-keep) ) ) (setq org-agenda-file-regexp "\\`[^.].*\\.org\\'") (setq org-agenda-files (list "~/org/_index.org" "~/org/_todo.org" "~/org/_slipbox.org" )) ; use *all* org agenda files as refile targets (setq org-refile-targets '((nil :maxlevel . 3) (org-agenda-files :maxlevel . 3))) (require 'org-super-agenda) (org-super-agenda-mode) (setq org-super-agenda-groups '( (:name "Health" :and (:habit t :tag "health")) (:name "House" :and (:habit t :tag "house")) (:name "Meds" :and (:habit t :tag "meds")) (:name "Health" :and (:tag "health" :tag "homework")) (:name "Misc" :tag "homework") (:name "Health" :and (:todo ("WIP" "READY") :tag "health")) (:order 200 :auto-parent "t") (:name "Slipbox" :and (:todo ("WIP" "READY") :tag "slipbox")) (:name "Personal" :and (:todo ("WIP" "READY") :tag "personal")) (:name "To Do" :todo ("WIP" "READY")) (:name "Slipbox" :tag "slipbox") (:name "Health" :tag "health") (:name "Personal" :tag "personal") (:name "To Do" :priority>= "C") (:name "Low Priority" :priority< "C") )) (setq org-agenda-custom-commands '( ("r" "Personal (Combined)" ( (agenda "" ( (org-agenda-overriding-header "Scheduled") (org-agenda-remove-tags t) (org-deadline-warning-days 14) (org-agenda-entry-types '(:deadline :scheduled)) )) (agenda "" ( (org-agenda-overriding-header "Habits") (org-agenda-files (list "~/org/_habits.org" "~/org/_meds.org")) (org-agenda-remove-tags t) )) (tags-todo "+homework" ( (org-agenda-overriding-header "Homework") (org-agenda-remove-tags t) )) (tags-todo "+TODO=\"WIP\"&-homework" ( (org-agenda-overriding-header "WIP") (org-agenda-remove-tags t) )) (tags-todo "+TODO=\"READY\"&-homework" ( (org-agenda-overriding-header "Ready") (org-agenda-remove-tags t) )) (tags-todo "-TODO=\"WIP\"&-TODO=\"READY\"&-homework" ( (org-agenda-overriding-header "") (org-agenda-remove-tags t) )) ) ) ("h" "Personal (Habits/Scheduled)" ( (agenda "" ( (org-agenda-overriding-header "Scheduled") (org-agenda-remove-tags t) (org-deadline-warning-days 14) (org-agenda-entry-types '(:deadline :scheduled)) )) (agenda "" ( (org-agenda-overriding-header "Habits") (org-agenda-files (list "~/org/_habits.org" "~/org/_meds.org")) (org-agenda-remove-tags t) )) ) ) ("t" "Personal (Tasks)" ( (tags-todo "+homework" ( (org-agenda-overriding-header "Homework") (org-agenda-remove-tags t) )) (tags-todo "+TODO=\"WIP\"&-homework" ( (org-agenda-overriding-header "WIP") (org-agenda-remove-tags t) )) (tags-todo "+TODO=\"READY\"&-homework" ( (org-agenda-overriding-header "Ready") (org-agenda-remove-tags t) )) (tags-todo "-TODO=\"WIP\"&-TODO=\"READY\"&-homework" ( (org-agenda-overriding-header "") (org-agenda-remove-tags t) )) ) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; capture templates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; inspiration for conditional capture template : https://storax.github.io/blog/2016/05/02/org-capture-tricks/ ; helper functions (defvar my-capture-prmt-history nil "History of prompt answers for org capture.") (defun my/prmt (prompt variable) "PROMPT for string, save it to VARIABLE and insert it." (make-local-variable variable) (set variable (read-string (concat prompt ": ") nil my-capture-prmt-history))) (defun my/inc (what text &rest fmtvars) "Ask user to include WHAT. If user agrees return TEXT." (when (y-or-n-p (concat "Include " what "?")) (apply 'format text fmtvars))) (defun my/inc_date (what prefix) "Ask user to include a date. If user agrees prompt for date." (when (y-or-n-p (concat "Include " what "?")) (concat prefix (org-time-stamp nil)))) ; orgmode capture templates (setq org-capture-templates '( ("d" "TODO (Main)" entry (file "~/org/_todo.org") (file "~/org/_org-capture-templates/template_todo.org") :prepend t :immediate-finish "f" :jump-to-captured "t" ) ("s" "TODO (Slipbox)" entry (file "~/org/_slipbox.org") (file "~/org/_org-capture-templates/template_slipbox.org") :prepend t :immediate-finish "f" :jump-to-captured "t" ) ("h" "Health") ("hc" "Couples Appointment noteworthy developments / [time] in review" entry (file "~/org/_slipbox.org") (file "~/org/_org-capture-templates/template_couples_apt_developments.org") :immediate-finish "f" :jump-to-captured "t" ) ("hm" "Mike Appointment noteworthy developments / [time] in review" entry (file "~/org/_slipbox.org") (file "~/org/_org-capture-templates/template_apt_developments.org") :immediate-finish "f" :jump-to-captured "t" ) ("m" "Music import (beets)" entry (file "~/org/_slipbox.org") (file "~/org/_org-capture-templates/template_beets.org") :prepend t :immediate-finish "f" :jump-to-captured "t" ) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; org-habit (add-to-list 'org-modules 'org-habit) (setq org-habit-preceding-days 14) (setq org-habit-following-days 7) (setq org-agenda-span 1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; helm specific org adjustments (load "helm-org.el") (require 'helm-org) (helm-mode 1) (add-to-list 'helm-completing-read-handlers-alist '(org-capture . helm-org-completing-read-tags)) (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags . helm-org-completing-read-tags)) (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags-command . helm-org-completing-read-tags)) ; fix helm multi-tag select (needs custom seprator. default is comma ;(setq helm-crm-default-separator ":" (define-advice helm--completion-in-region (:around (helm-fun origfun start end collection &optional predicate) temporary-helm-crm-separator-for-tags) (setq tcrmds helm-crm-default-separator) ;; If the last command was any of these values, we're looking at tags most likely (when (or (member last-command '(org-capture org-ctrl-c-ctrl-c org-set-tags org-set-tags-command)) ;;This is a workaround for completions when you've already started typing. (and (eq this-command 'crm-complete) (eq major-mode 'org-mode)) ;; This is probably the only thing we really need, but it doesn't handle custom "Tags" prompts (and (active-minibuffer-window) (eq "Tags: " (minibuffer-prompt)))) (setq helm-crm-default-separator ":")) ;; Call the original Helm Completion function with all the original arguments (funcall helm-fun origfun start end collection predicate) (setq helm-crm-default-separator tcrmds)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; obsidian link handler from https://forum.obsidian.md/t/emacs-on-macos-org-mode-link-handler-for-obsidian-links/19230 ;; 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))) (async-shell-command (concat "/c/Users/mcros/AppData/Local/Programs/Obsidian/Obsidian.exe \"obsidian://" message-id "\"")))) ;; on obsdian://aoeu link, this will call handler with //aoeu (org-link-set-parameters "obsidian" :follow #'org-obsidian-link-open) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ui tuning ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; misc desktop specific overrides ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; set more reasonable org habit line width (setq org-habit-graph-column 75) ; set ui to standard org-workspace at launch (add-hook 'after-init-hook (lambda () (my/workspace-org) )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; pomidor ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (use-package pomidor :bind (("C-w P" . pomidor)) :config (setq pomidor-update-interval 30 pomidor-save-session-file "~/org/pomidor-sessions.json" pomidor-alert nil pomidor-sound-tick nil pomidor-sound-tack nil pomidor-sound-overwork nil pomidor-sound-break-over nil) :hook (pomidor-mode . (lambda () (display-line-numbers-mode -1) ; Emacs 26.1+ (setq left-fringe-width 0 right-fringe-width 0) (setq left-margin-width 2 right-margin-width 0) ;; force fringe update (set-window-buffer nil (current-buffer))))) ; setup 6 pomodoros for 8 hr work day ; 7 hours of work, with 1 hr of long breaks ; each pomodor is: ; warm up of 15 mins ; work of 45 mins ; total work period: 60 mins ; 10 min break ; total interval period: 70 mins ; long break is 30 mins ; every 3rd pomodoro give 30 minute break ; actual values (setq pomidor-seconds (* 60 60)) ; total work period (setq pomidor-warmup-seconds (* 15 60)) ; warmup time (setq pomidor-break-seconds (* 10 60)) (setq pomidor-breaks-before-long 3) (setq pomidor-long-break-seconds (* 30 60)) ; setup emoji separators to enhance visual state indicators (defun my-pomidor-separator-hook () (let* ((state (pomidor--current-state)) (total (pomidor--total-duration state)) (elapsed (round (time-to-seconds total)))) (cond ; watch out for the order of this conditional ; there are overlapping states and this order is meaninful ((or (pomidor-overwork-p) (pomidor-break-over-p)) (setq pomidor-header-separator " ⚠️ ")) ((pomidor-should-long-break-p) (setq pomidor-header-separator " 🪁 ")) ((pomidor--break state) (setq pomidor-header-separator " 🚶 ")) ((<= elapsed pomidor-warmup-seconds) (setq pomidor-header-separator " 🌡️↑ ")) (t (setq pomidor-header-separator " 🏢 ")) ) ) ) ; add separator logic as std pomidor update hook to ensure the emoji separators get displayed ; this will be delayed up to the pomidor-update-interval for display (add-hook 'pomidor-update-hook #'my-pomidor-separator-hook) ; trigger the emoji separator for common operations so it 'triggers' faster than via the std pomidor update hook ; this should show the emoji separators 'faster' than relying on just the update hook (advice-add #'pomidor-reset :after #'my-pomidor-separator-hook) (advice-add #'pomidor-stop :after #'my-pomidor-separator-hook) (advice-add #'pomidor-break :after #'my-pomidor-separator-hook) ; 'hold' visual indicator (defun my-pomidor-hold-separator () (interactive) (setq pomidor-header-separator " 💤 ") (pomidor--update) ) (advice-add #'pomidor-hold :before #'my-pomidor-hold-separator) ; 'unhold' -- reset visual indicator (defun my-pomidor-unhold-separator () (interactive) (my-pomidor-separator-hook) (pomidor--update) ) (advice-add #'pomidor-unhold :before #'my-pomidor-unhold-separator) ; position cursor @ top of pomidor buffer after rendering (defun my-pomidor-beginning-of-buffer (buffer states) (interactive) (with-current-buffer (get-buffer pomidor-buffer-name) (goto-char (point-min)) ) ) (advice-add #'pomidor--render :after #'my-pomidor-beginning-of-buffer) ; safely run pomidor in a buffer programatically ; doesnt mung or reset state (defun my-pomidor-run-safe () ; make sure pomidor is running + active w/o resetting the current state (when (or (not (boundp 'pomidor-buffer-name)) (not (get-buffer pomidor-buffer-name))) (pomidor) (previous-buffer) ; pomidor switches buffers -- go back to orig buffer ) ) ; frame for pomidor, just the main timer text visible ; centered at bottom of the screen (defun my-pomidor-frame () (interactive) (select-frame (make-frame '( (name . "Pomidor") (menu-bar-lines 0) (tool-bar-lines 0) (width . (text-pixels . 818)) (height . (text-pixels . 144)) ))) ; single monitor / main monitor positing (let* ((dw (display-pixel-width)) (dh (display-pixel-height)) (f (selected-frame)) (fw (frame-pixel-width f)) (fh (frame-pixel-height f)) ; handle multi-monitor goofy (main panel is /left/ of first panel) ; assumes a 1440p multi-monitor setup with monitor 1 'to the right of' monitor 2 ; remove the (- [stuff] 2506) to undo this workaround (x (- (- (/ dw 2) (/ fw 2)) 2560)) (y (- (- dh fh ) 125))) (set-frame-position f x y) ) ; ensure pomidor is running & do /not/ reset state (my-pomidor-run-safe) ; switch to pomidor buffer (switch-to-buffer pomidor-buffer-name) ) (global-set-key (kbd "C-M-p") 'my-pomidor-frame) ; pop win pomidor setup ; main way to invoke ; ensure pomidor is running before trying to open the buffer ; use global buffer name for pomidor ; make the popwin window 'sticky' ; position sticky popwin @ top (push '(pomidor-mode :position top :stick t) popwin:special-display-config) (global-set-key (kbd "C-w p") (lambda () (interactive) (my-pomidor-run-safe) ; open pomidor in popwin (popwin:display-buffer (get-buffer pomidor-buffer-name)) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; origami + super agenda config (defvar ap/org-super-agenda-auto-hide-groups '(".*Low Priority.*" "^.*?[^R]+.*?(desc)$")) (defun ap/org-super-agenda-origami-fold-default () "Fold certain groups by default in Org Super Agenda buffer." (--each ap/org-super-agenda-auto-hide-groups (goto-char (point-min)) (cl-loop while (re-search-forward it nil t) do (if (not (or (string-match "TODO" (thing-at-point 'line)) (string-match "READY" (thing-at-point 'line)) (string-match "WIP" (thing-at-point 'line)) )) (origami-close-node (current-buffer) (point)) ) ) ) ) (add-hook 'org-agenda-mode-hook #'origami-mode) (add-hook 'org-agenda-finalize-hook #'ap/org-super-agenda-origami-fold-default) (define-key org-super-agenda-header-map (kbd "") #'origami-toggle-node) (define-key org-agenda-mode-map (kbd "") #'origami-toggle-node) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(default ((t (:family "MonoLisa Variable" :height 100)))) '(fixed-pitch ((t (:family "MonoLisa Variable" :height 120)))) '(variable-pitch ((t (:family "Atkinson Hyperlegible" :height 120))))) (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(custom-safe-themes '("dde643b0efb339c0de5645a2bc2e8b4176976d5298065b8e6ca45bc4ddf188b7" "bfc0b9c3de0382e452a878a1fb4726e1302bf9da20e69d6ec1cd1d5d82f61e3d" default)) '(package-selected-packages '(pomidor (org-tidy org-auto-expand ox-pandoc org-super-agenda org-alert burnt-toast alert) (xclip) (which-key all-the-icons revert-buffer-all scratch persistent-scratch rainbow-mode rainbow-delimiters focus zoom popwin dired-single diredfl doominhibitinhibit-modeline helpful helm helm-org dired-rainbow dired-rainbow-listing dired-single dash s origami modus-themes use-package))) '(safe-local-variable-values '((org-duration-format . h:mm) (org-duration-format quote h:mm) (eval setq org-html-postamble nil) (eval setq org-export-html-postamble-format nil) (eval add-hook 'after-save-hook 'org-pandoc-export-to-latex-pdf t t) (eval add-hook 'after-save-hook 'org-pandoc-export-to-docx t t) (eval add-hook 'after-save-hook 'org-ascii-export-to-ascii t t) (eval add-hook 'after-save-hook 'org-md-export-to-markdown t t) (eval add-hook 'after-save-hook 'org-org-export-to-org t t) (eval add-hook 'after-save-hook 'org-html-export-to-html t t) (eval add-hook 'after-save-hook 'org-gfm-export-to-markdown t t))) '(temp-buffer-resize-mode t) '(zoom-ignored-major-modes '(dired-mode)) '(zoom-size 'my-zoom-size-callback))