commit 8cd61c31dbe519c77e04ca02afac0e29c0bcc30a
parent 3a3acbd4b5296e279397a97eda211d124fddc723
Author: Alex Balgavy <alex@balgavy.eu>
Date: Sun, 21 Aug 2022 21:56:11 +0200
emacs: reorganize configuration
Diffstat:
M | emacs/config.org | | | 3690 | ++++++++++++++++++++++++++++++++++++------------------------------------------- |
1 file changed, 1669 insertions(+), 2021 deletions(-)
diff --git a/emacs/config.org b/emacs/config.org
@@ -2,74 +2,29 @@
On macOS, I use Homebrew to install Emacs from daviderestivo/emacs-head/emacs-head@28.
I install with ~--HEAD --with-dbus --with-cocoa --with-xwidgets --with-native-comp~.
-* Keep track of custom key bindings
-I want an easy way to see which global key bindings I have.
-Some packages provide things like this, but I want it to be simple and always available, so I'll roll my own.
-These functions allow a way to store personal global key bindings and view them as a list.
-
-I'll use a hash table to map a key (in ~kbd~ format, the inverse of this is ~key-description~) to its function (and its previous function):
-
-#+begin_src emacs-lisp
- (defvar za/personal-keybinds (make-hash-table :test 'equal) "A hash table for my personal keybindings.")
-#+end_src
-
-A function to bind keys:
-
-#+begin_src emacs-lisp
- (defun za/global-set-key (keys fun)
- "Bind KEYS to FUN. This will be saved to the za/personal-keybinds hash table."
- (let* (;; Store the previous function KEYS were bound to
- (lookup-result (lookup-key (current-global-map) keys))
- (previous-fun (cond ((not lookup-result) nil)
- ((numberp lookup-result) nil)
- ((equal lookup-result fun) nil)
- (t lookup-result)))
- ;; Try to see if it already has a custom binding
- (existing-entry (gethash keys za/personal-keybinds)))
-
- ;; If KEYS already have a custom binding, fail, unless we're just repeating the same binding
- (if (and existing-entry
- (not (equal (car existing-entry) fun)))
- (user-error (format "Keys are already bound to %s" (car existing-entry))))
-
- (puthash keys (list fun previous-fun) za/personal-keybinds)
- (global-set-key keys fun)))
-#+end_src
-
-And one to remove bindings:
+* Why I choose use-package
+- provides bind-key by default
+- is mostly just macros that wrap the needed stuff from package.el. can check that with ~macroexpand~.
+- adds a bunch of performance improvements
+* quelpa
+use-package downloads from ELPA/MELPA, sometimes I need to get stuff that's not in those repos.
+Quelpa lets you install from local or remote source (like git). 'Quelle', like source, get it?
+With quelpa-use-package, I can use the keyword ~:quelpa~ to install via quelpa.
#+begin_src emacs-lisp
- (defun za/global-unset-key (keys)
- "Remove a custom key binding from za/personal-keybinds and unbind the keys."
- (remhash keys za/personal-keybinds)
- (global-unset-key keys))
+ (use-package quelpa)
+ (use-package quelpa-use-package)
#+end_src
-Then a way to retrieve keybindings:
-
-#+begin_src emacs-lisp
- (defun za/get-personal-keybinds ()
- "Return the contents of za/personal-keybinds as a string."
- (let ((result nil)
- (format-entry (lambda (k v)
- (let ((get-func (lambda (e) (nth 0 e)))
- (get-prev-func (lambda (e) (nth 1 e))))
- (push
- (concat (format "%s\t\t%s" (key-description k) (funcall get-func v))
- (if (funcall get-prev-func v)
- (format "\t\t(previously %s)" (funcall get-prev-func v))))
- result)))))
- (maphash format-entry za/personal-keybinds)
- (mapconcat 'identity (sort result 'string<) "\n")))
-#+end_src
-(za/global-unset-key (kbd "C-x C-s"))
-And a way to display all of the keybindings:
+* exec-path-from-shell (macOS)
+In macOS, the path is not set correctly (i.e. as it is in the terminal) in the GUI app. This fixes it.
#+begin_src emacs-lisp
- (defun za/list-personal-keybinds ()
- (interactive)
- (with-output-to-temp-buffer "*Personal Keybindings*"
- (princ (za/get-personal-keybinds))))
+ (when (string-equal system-type "darwin")
+ (use-package exec-path-from-shell
+ :config
+ (add-to-list 'exec-path-from-shell-variables "NOTMUCH_CONFIG")
+ (exec-path-from-shell-initialize)))
#+end_src
* Emacs file locations
@@ -112,77 +67,33 @@ The second one actually loads them.
(unless (fboundp 'system-move-file-to-trash)
(setq trash-directory "~/.Trash"))
#+end_src
-
-* Theme
-Icons required for some parts of the doom theme:
-
-#+begin_src emacs-lisp
- (use-package all-the-icons)
-#+end_src
-
-Load Doom Emacs themes:
-
-#+begin_src emacs-lisp
- (use-package doom-themes
- :config
- ;; Global settings (defaults)
- (setq doom-themes-enable-bold t ; if nil, bold is universally disabled
- doom-themes-enable-italic t) ; if nil, italics is universally disabled
-
- ;; Enable flashing mode-line on errors
- (doom-themes-visual-bell-config)
-
- ;; Corrects (and improves) org-mode's native fontification.
- (doom-themes-org-config))
-#+end_src
-
-Define the themes I want:
+* Daemon
+I want to have a way to kill the Emacs daemon.
+So, define a function that kills the frame, and with a prefix kills emacs.
#+begin_src emacs-lisp
- (defconst za/dark-theme-name 'doom-one "A symbol representing the name of the dark theme I use.")
- (defconst za/light-theme-name 'jokull "A symbol representing the name of the light theme I use.")
- ;; I used to use doom-acario-light before writing my own theme
-
- (defun za/dark-theme ()
- "Switch to dark theme"
- (interactive)
- (mapc #'disable-theme custom-enabled-themes)
- (load-theme za/dark-theme-name t)
- (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
-
- (defun za/light-theme ()
- "Switch to light theme"
- (interactive)
- (mapc #'disable-theme custom-enabled-themes)
- (load-theme za/light-theme-name t)
- (remove-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
+ (defun za/emacsclient-c-x-c-c (&optional arg)
+ "If running in emacsclient, make C-x C-c exit frame, and C-u C-x C-c exit Emacs."
+ (interactive "P") ; prefix arg in raw form
+ (if arg
+ (save-buffers-kill-emacs)
+ (save-buffers-kill-terminal)))
#+end_src
-Change theme depending on the current system theme.
-The way I check for dark mode is defined in 'dark-mode-p'; currently I use the presence of the ~/.config/dark-theme file to indicate when dark theme is set.
-I quote the call to ~file-exists-p~ because I want to evaluate it on-demand, not immediately.
-A function ending in '-p' is a predicate, i.e. returns true or false.
-If calling a function that's in a variable, you have to use 'funcall'.
-To evaluate a quoted form, use 'eval'.
+Then, if I'm in an emacsclient, I want to bind C-x C-c to that function (if not, I just want the default keybinding):
#+begin_src emacs-lisp
- (defun za/auto-select-theme (&rest _)
- "Automatically select dark/light theme based on presence of ~/.config/dark-theme"
- (let ((dark-mode-p '(file-exists-p "~/.config/dark-theme")))
- (if (eval dark-mode-p)
- (za/dark-theme)
- (za/light-theme))))
-
- (za/auto-select-theme)
+ ;; If not running in emacsclient, use the default bindings
+ (if (daemonp)
+ (bind-key "C-x C-c" #'za/emacsclient-c-x-c-c))
#+end_src
-* Font
-I want Menlo, size 14:
+Furthermore, I want to set the theme correctly whenever I connect with 'emacsclient':
#+begin_src emacs-lisp
- (add-to-list 'default-frame-alist '(font . "Menlo-14"))
+ (if (daemonp)
+ (add-hook 'after-make-frame-functions #'za/auto-select-theme))
#+end_src
-
* Garbage collection
Garbage-collect on focus-out, Emacs /should/ feel snappier.
@@ -190,2462 +101,2199 @@ Garbage-collect on focus-out, Emacs /should/ feel snappier.
(add-hook 'focus-out-hook #'garbage-collect)
#+end_src
-* Packages
-** quelpa
-Quelpa lets you install from local or remote source (like git).
-With quelpa-use-package, I can use the keyword ~:quelpa~ to install via quelpa.
+* Sound support
+On macOS, you can use afplay:
#+begin_src emacs-lisp
- (use-package quelpa)
- (use-package quelpa-use-package)
+ (defun za/play-sound-file-macos (file &optional volume device)
+ "Play sound using `afplay` on macOS"
+ (unless (file-readable-p file)
+ (user-error "File %s not readable." file))
+
+ ;; the `apply` is required here because I need to build a list of arguments
+ (apply 'start-process `("afplay" nil
+ "afplay"
+ ,@(if volume (list "-v" (int-to-string volume)))
+ ,file)))
#+end_src
-** exec-path-from-shell (macOS)
-In macOS, the path is not set correctly (i.e. as it is in the terminal) in the GUI app. This fixes it.
+Then redefine the play-sound-file function where needed:
#+begin_src emacs-lisp
- (when (string-equal system-type "darwin")
- (use-package exec-path-from-shell
- :config
- (add-to-list 'exec-path-from-shell-variables "NOTMUCH_CONFIG")
- (exec-path-from-shell-initialize)))
+ (cond ((and (not (fboundp 'play-sound-internal))
+ (eq system-type 'darwin))
+ (advice-add 'play-sound-file :override #'za/play-sound-file-macos)))
#+end_src
+* DISABLED Fix non-dbus macOS notification
+macOS version might not be compiled with dbus support; in that case you can use e.g. terminal-notifier.
+If you use the ~sender~ option, notifications don't show
+unless the app is in the background. [[https://github.com/julienXX/terminal-notifier/issues/68][See this Github issue.]]
-** emacs-which-key
-Minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.
-
-#+BEGIN_SRC emacs-lisp
- (use-package which-key
- :config
- (which-key-mode))
+#+begin_src emacs-lisp :tangle no
+ ;; on mac without dbus:
+ (org-show-notification-handler
+ (lambda (str) (start-process "terminal-notifier" nil (executable-find "terminal-notifier")
+ "-title" "Timer done"
+ "-message" str
+ "-group" "org.gnu.Emacs"
+ "-ignoreDnD"
+ "-activate" "org.gnu.Emacs")))
#+end_src
-** counsel + ivy + swiper + prescient
-Better incremental completion and selection narrowing.
-And a bunch more.
-Generally makes for nicer interactivity, like ido mode on steroids.
-Switched to this from Helm, it's more lightweight.
-
-The components:
-- ivy: generic completion mechanism
-- counsel: collection of common Emacs commands enhanced using ivy
-- swiper: search enhanced using ivy
-- prescient: scoring system for M-x
+* Editing
+** Overwrite selection on typing
+Normally, when I select something and start typing, Emacs clears the selection, i.e. it deselects and inserts text after the cursor.
+I want to replace the selection.
#+begin_src emacs-lisp
- (use-package counsel :demand
- :config
- (setq ivy-use-virtual-buffers t ; extend searching to bookmarks and
- ivy-height 20 ; set height of the ivy window
- ivy-count-format "(%d/%d) " ; count format, from the ivy help page
- ivy-display-style 'fancy
- ivy-format-function 'ivy-format-function-line)
- (ivy-mode)
- (counsel-mode)
-
- (defun edit-script ()
- "Edit a file in ~/.scripts/"
- (interactive)
- (let ((input (ivy--input)))
- (ivy-quit-and-run (counsel-file-jump nil "~/.scripts/"))))
- (defun edit-config ()
- "Edit a file in ~/.dotfiles/"
- (interactive)
- (let ((input (ivy--input)))
- (ivy-quit-and-run (counsel-file-jump nil "~/.dotfiles/")))))
-
- (use-package prescient :config (prescient-persist-mode))
- (use-package ivy-prescient
- :after counsel
- :config
- (setq ivy-prescient-retain-classic-highlighting t)
- (ivy-prescient-mode))
+ (delete-selection-mode t)
#+end_src
-Set the key bindings:
+** Strip trailing whitespace
+You can show trailing whitespace by setting show-trailing-whitespace to 't'.
+But I want to automatically strip trailing whitespace.
+Luckily there's already a function for that, I just need to call it in a hook:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-s") #'swiper-isearch)
- (za/global-set-key (kbd "C-r") #'swiper-isearch-backward)
- (za/global-set-key (kbd "M-x") #'counsel-M-x)
- (za/global-set-key (kbd "C-x C-f") #'counsel-find-file)
- (za/global-set-key (kbd "M-y") #'counsel-yank-pop)
- (za/global-set-key (kbd "C-x b") #'ivy-switch-buffer)
- (za/global-set-key (kbd "C-c v") #'ivy-push-view)
- (za/global-set-key (kbd "C-c V") #'ivy-pop-view)
- (za/global-set-key (kbd "C-c c") #'counsel-compile)
- (za/global-set-key (kbd "M-s g") #'counsel-ag)
- (za/global-set-key (kbd "M-s f") #'counsel-fzf)
- (za/global-set-key (kbd "C-c b") #'counsel-bookmark)
- (za/global-set-key (kbd "C-c p") #'counsel-recentf)
- (za/global-set-key (kbd "C-c o") #'counsel-outline)
- (za/global-set-key (kbd "C-h f") #'counsel-describe-function)
- (za/global-set-key (kbd "C-h v") #'counsel-describe-variable)
- (za/global-set-key (kbd "C-h o") #'counsel-describe-symbol)
+ (add-hook 'before-save-hook #'delete-trailing-whitespace)
#+end_src
+** Formatting & indentation
-I like having ivy in a popup.
-Problem: posframe does not work if emacs is too old and on macos.
-See here: https://github.com/tumashu/posframe/issues/30
-On Mac, ~brew install --HEAD emacs~ doesn't work either.
-Solution: ~brew tap daviderestivo/emacs-head && brew install emacs-head@28 --with-cocoa~
+Show a tab as 8 spaces:
#+begin_src emacs-lisp
- (if (and ( version< emacs-version "28") (equal system-type 'darwin))
- (message "ivy-posframe won't work properly, run `brew install daviderestivo/emacs-head/emacs-head@28 --with-cocoa`")
- (use-package ivy-posframe
- :config
- (setq ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-center)))
- (setq ivy-posframe-parameters
- '((left-fringe . 8)
- (right-fringe . 8)))
- (setq ivy-posframe-border-width 3)
- (custom-set-faces '(ivy-posframe-border ((t (:inherit mode-line-inactive)))))
- (ivy-posframe-mode 1)))
+ (setq-default tab-width 8)
#+end_src
-Have to set this to let me select exactly what I'm typing as a candidate:
+Never insert tabs with indentation by default:
#+begin_src emacs-lisp
- (setq ivy-use-selectable-prompt t)
+ (setq-default indent-tabs-mode nil)
#+end_src
-Also, accidentally pressing shift-space deletes input, because by default, shift-space is bound to ~ivy-restrict-to-matches~ in the ivy minibuffer. I don't want that, I want it bound to shift-tab:
+Allow switching between the two easily:
#+begin_src emacs-lisp
- (define-key ivy-minibuffer-map (kbd "S-SPC") (lambda () (interactive) (insert ?\s)))
- (define-key ivy-minibuffer-map (kbd "<backtab>") #'ivy-restrict-to-matches)
+ (defun indent-tabs ()
+ (interactive)
+ (setq indent-tabs-mode t))
+ (defun indent-spaces ()
+ (interactive)
+ (setq indent-tabs-mode nil))
#+end_src
-Custom Ivy actions:
+Indentation for various modes:
#+begin_src emacs-lisp
- (ivy-add-actions
- 'counsel-dired
- '(("f" (lambda (dir) (counsel-fzf nil dir)) "Fzf in directory")
- ("g" (lambda (dir) (counsel-ag nil dir)) "Ag in directory")))
- (ivy-add-actions
- 'dired
- '(("f" (lambda (dir) (ivy-exit-with-action (counsel-fzf nil dir))) "Fzf in directory")
- ("g" (lambda (dir) (ivy-exit-with-action (counsel-ag nil dir))) "Ag in directory")))
+ (setq-default sh-basic-offset 2
+ c-basic-offset 4)
#+end_src
-** company
-Good completion.
-#+begin_src emacs-lisp
- (use-package company
- :hook (ledger-mode . company-mode))
-#+end_src
-** wgrep
-#+begin_src emacs-lisp
- (use-package wgrep)
-#+end_src
-** avy
-This lets me jump to any position in Emacs rather quickly, sometimes it's useful.
-~avy-goto-char-timer~ lets me type a part of the text before avy kicks in.
+** Wrapping
+A function to toggle wrapping:
#+begin_src emacs-lisp
- (use-package avy
- :bind
- (("C-:" . 'avy-goto-char-timer)))
-#+end_src
+ (defvar-local za/wrapping nil "Wrapping changes per buffer.")
-** Org
-*** Installation
-Install Org and require additional components that I use.
+ (defun za/toggle-wrap (&optional enable)
+ "Toggle line wrapping settings. With ENABLE a positive number, enable wrapping. If ENABLE is negative or zero, disable wrapping."
+ (interactive "P") ; prefix arg in raw form
-#+begin_src emacs-lisp
- (use-package org
- :config
- (require 'org-tempo)
- (require 'org-habit)
- (require 'org-agenda)
- (require 'org-id))
-#+end_src
+ ;; If an argument is provided, prefix or otherwise
+ (if enable
+ (let ((enable (cond ((numberp enable)
+ enable)
+ ((booleanp enable)
+ (if enable 1 0))
+ ((or (listp enable) (string= "-" enable))
+ (prefix-numeric-value enable)))))
+ ;; If zero or negative, we want to disable wrapping, so pretend it's currently enabled.
+ ;; And vice versa.
+ (cond ((<= enable 0) (setq za/wrapping t))
+ ((> enable 0) (setq za/wrapping nil)))))
-*** Key bindings
-Global:
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c a") #'org-agenda)
- (za/global-set-key (kbd "C-c n") #'org-capture)
- (za/global-set-key (kbd "C-c l") #'org-store-link)
-#+end_src
-Org mode:
+ (let ((disable-wrapping (lambda ()
+ (visual-line-mode -1)
+ (toggle-truncate-lines t)))
+ (enable-wrapping (lambda ()
+ (toggle-truncate-lines -1)
+ (visual-line-mode))))
-#+begin_src emacs-lisp
- (defun za/keybinds-org-mode ()
- "Function to set org-mode keybindings, run via org-mode-hook."
- (define-key org-mode-map (kbd "C-M-<return>") #'org-insert-todo-heading)
- (define-key org-mode-map (kbd "C-c M-y") #'org-yank-link-url)
- (define-key org-mode-map (kbd "C-c N") #'org-noter)
- (define-key org-mode-map (kbd "C-M-i") #'completion-at-point)
- (define-key org-mode-map (kbd "C-c q t") #'org-timestone-set-org-current-time-effective))
+ ;; If za/wrapping is not locally set, infer its values from the enabled modes
+ (unless (boundp 'za/wrapping)
+ (setq za/wrapping (and visual-line-mode
+ (not truncate-lines))))
- (add-hook 'org-mode-hook #'za/keybinds-org-mode)
+ ;; Toggle wrapping based on current value
+ (cond (za/wrapping
+ (funcall disable-wrapping)
+ (setq za/wrapping nil)
+ (message "Wrapping disabled."))
+ (t
+ (funcall enable-wrapping)
+ (setq za/wrapping t)
+ (message "Wrapping enabled.")))))
#+end_src
-Easier link following. Actual enter is still possible with ~C-q C-j~.
-[[https://google.com][Goggles]]
-#+begin_src emacs-lisp
- (setq org-return-follows-link t)
-#+end_src
-*** Nicer bullets
-In org mode, I want to use bullets instead of stars, so I also install ~org-superstar~.
+And a keybinding to toggle wrapping:
#+begin_src emacs-lisp
- (use-package org-superstar
- :config
- (setq org-superstar-leading-bullet ?\s))
+ (bind-key "C-c q w" #'za/toggle-wrap)
#+end_src
-*** More languages
+** Pager toggle
+M-x view-mode enables pager behavior.
+I want read-only files to automatically use pager mode:
+
#+begin_src emacs-lisp
-(use-package inf-ruby)
+ (setq view-read-only t)
#+end_src
+** Prefer newer file loading
#+begin_src emacs-lisp
- (org-babel-do-load-languages
- 'org-babel-load-languages
- '((emacs-lisp . t)
- (R . t)
- (python . t)
- (ruby . t)
- (shell . t)))
-
- (setq org-babel-python-command "python3")
- (setq org-confirm-babel-evaluate nil)
+ (setq load-prefer-newer t)
#+end_src
-*** Org mode settings
-Furthermore, tags were getting cut off, so I manually set the best column to display them.
+
+** Automatically find tags file
+When opening a file in a git repo, try to discover the etags file:
#+begin_src emacs-lisp
- (defun za/settings-org-mode ()
- "My settings for org mode"
- (org-superstar-mode 1)
- (za/toggle-wrap t)
- (org-indent-mode)
- (setq org-tags-column (- 10 (window-total-width)))
- (setq org-src-tab-acts-natively t) ; a tab in a code block indents the code as it should
- ;; Realign tags
- (org-set-tags-command '(4)))
+ (defun current-tags-file ()
+ "Get current tags file"
+ (let* ((tagspath ".git/etags")
+ (git-root (locate-dominating-file (buffer-file-name) tagspath)))
+ (if git-root
+ (expand-file-name tagspath git-root))))
- (add-hook 'org-mode-hook #'za/settings-org-mode)
+ (setq default-tags-table-function #'current-tags-file)
#+end_src
-Link settings:
+There's probably a better way to write this. I need to ask Reddit for feedback at some point.
+
+** End sentences with one space
+Emacs uses the rather old-fashioned convention of treating a period followed by double spaces as end of sentence. However, it is more common these days to end sentences with a period followed by a single space.
+
+Let a period followed by a single space be treated as end of sentence:
#+begin_src emacs-lisp
- (setq org-link-elisp-confirm-function #'y-or-n-p
- org-link-elisp-skip-confirm-regexp "^org-noter$")
+ (setq sentence-end-double-space nil)
#+end_src
-
-Clock sound:
+* Keybindings
+** Expansion/completion
+Use hippie expand instead of dabbrev-expand:
#+begin_src emacs-lisp
- (setq org-clock-sound (concat user-emacs-directory "notification.wav"))
+ (bind-key "M-/" #'hippie-expand)
#+end_src
-*** Enable linking to email via notmuch
-To be able to link to emails via notmuch, I use ol-notmuch:
+** Zap up to char
+It's more useful for me to be able to delete up to a character instead of to and including a character:
#+begin_src emacs-lisp
- (use-package ol-notmuch :quelpa (:upgrade t))
+ (bind-key "M-z" #'zap-up-to-char)
#+end_src
-*** Agenda & GTD
-**** Agenda mode settings
-Fix tag display by dynamically calculating the column.
-#+begin_src emacs-lisp
- (defun za/settings-org-agenda-mode ()
- "My settings for org agenda mode"
- (setq org-agenda-tags-column (- 10 (window-total-width))))
+** Forward-word and forward-to-word
+Change M-f to stop at the start of the word:
- (add-hook 'org-agenda-mode-hook #'za/settings-org-agenda-mode)
+#+begin_src emacs-lisp
+ (bind-key "M-f" #'forward-to-word)
#+end_src
-Binding to show current agenda thing as narrowed subtree
+Bind ESC M-f to the old functionality of M-f (stop at end of word)
#+begin_src emacs-lisp
- (defun za/org-agenda-goto-narrowed-subtree ()
- "Jump to current agenda item and narrow to its subtree."
- (interactive)
- (delete-other-windows)
- (org-agenda-goto)
- (org-narrow-to-subtree)
- (other-window 1))
- (define-key org-agenda-mode-map (kbd "C-c TAB") #'za/org-agenda-goto-narrowed-subtree)
+ (bind-key "ESC M-f" #'forward-word)
#+end_src
-**** Set file locations
-Which files should be included in the agenda (I have to use ~list~ to evaluate the variables, because org-agenda-files expects strings):
+** Rectangle insert string
#+begin_src emacs-lisp
- (setq org-agenda-files (list za/org-life-main
- za/org-life-inbox
- za/org-life-tickler))
+ (bind-key "C-x r I" #'string-insert-rectangle)
+ (bind-key "C-x r R" #'replace-rectangle)
#+end_src
-I want to search all Org files in the life directory:
+** Toggle auto-revert-mode
+Sometimes I want to toggle auto reverting (or autoread) of buffer:
#+begin_src emacs-lisp
- (setq org-agenda-text-search-extra-files (directory-files za/org-life-dir t (rx bol (not ?.) (* anything) ".org")))
+ (bind-key "C-c q a" #'auto-revert-mode)
#+end_src
-
-Convenience functions to make opening the main file faster:
+** Fast access to view-mode (pager)
+I want to bind view-mode to a key for easy access:
#+begin_src emacs-lisp
- (defun gtd () "GTD: main file" (interactive) (find-file za/org-life-main))
- (defun gtd-inbox () "GTD: inbox" (interactive) (find-file za/org-life-inbox))
- (defun gtd-archive () "GTD: archive" (interactive) (find-file za/org-life-archive))
- (defun gtd-someday () "GTD: someday" (interactive) (find-file za/org-life-someday))
- (defun gtd-tickler () "GTD: tickler" (interactive) (find-file za/org-life-tickler))
+ (bind-key "C-c q r" 'view-mode)
#+end_src
-Bind keys to those functions:
+** Kill this buffer
+I like to be able to kill a buffer instantly:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "M-g t i") #'gtd-inbox)
- (za/global-set-key (kbd "M-g t g") #'gtd)
- (za/global-set-key (kbd "M-g t a") #'gtd-archive)
- (za/global-set-key (kbd "M-g t s") #'gtd-someday)
- (za/global-set-key (kbd "M-g t t") #'gtd-tickler)
+ (bind-key "s-<backspace>" 'kill-current-buffer)
#+end_src
-**** Refiling & archiving
-Where I want to be able to move subtrees (doesn't include inbox because I never refile to that, and the archive has its own keybining):
-
+** Delete this file (and kill the buffer)
#+begin_src emacs-lisp
- (setq org-refile-targets `((,za/org-life-main :maxlevel . 3)
- (,za/org-life-someday :level . 1)
- (,za/org-life-tickler :maxlevel . 2)))
+ (defun za/delete-this-file ()
+ "Kill the current buffer and delete its associated file."
+ (interactive)
+ (let ((fname (buffer-file-name))
+ (buf (current-buffer)))
+ (unless (and fname (file-exists-p fname))
+ (user-error (format "Buffer has no associated file.")))
+
+ (unless (y-or-n-p (format "Really delete %s and its buffer?" fname))
+ (user-error "User cancelled."))
+
+ (delete-file fname 'trash-if-enabled)
+ (kill-buffer buf)
+ (message "Deleted %s and killed its buffer." fname)))
+
+ (bind-key "C-c s-<backspace>" #'za/delete-this-file)
#+end_src
-I want to archive to a specific file, in a date tree:
+** Toggle fullscreen
+I'll use the keybinding that's standard on macOS:
#+begin_src emacs-lisp
- (setq org-archive-location (concat za/org-life-archive "::datetree/"))
+ (bind-key "C-s-f" #'toggle-frame-fullscreen)
#+end_src
-Include the destination file as an element in the path to a heading, and to use the full paths as completion targets rather than just the heading text itself:
+** Sexp manipulation
+When I write lisp, sometimes I want to switch two sexps (e.g. ~(one) (two)~ → ~(two) (one)~), so a key binding is nice for that:
#+begin_src emacs-lisp
- (setq org-refile-use-outline-path 'file)
+ (bind-key "C-S-t" #'transpose-sexps)
#+end_src
-Tell Org that I don’t want to complete in steps; I want Org to generate all of the possible completions and present them at once (necessary for Helm/Ivy):
+Also, to raise a sexp (e.g. ~(one (two))~ → ~(two)~):
#+begin_src emacs-lisp
- (setq org-outline-path-complete-in-steps nil)
+ (bind-key "C-S-u" #'raise-sexp)
#+end_src
-Allow me to tack new heading names onto the end of my outline path, and if I am asking to create new ones, make me confirm it:
+** Dedicated windows
+Sometimes I want to avoid Emacs overriding a window's contents.
+So I create a keybinding to toggle dedicated on a window:
#+begin_src emacs-lisp
- (setq org-refile-allow-creating-parent-nodes 'confirm)
-#+end_src
+ (defun za/toggle-window-dedicated-p ()
+ "Toggle set-window-dedicated-p on current window"
+ (interactive)
+ (cond ((window-dedicated-p (selected-window))
+ (set-window-dedicated-p (selected-window) nil)
+ (message "Window no longer dedicated"))
+ (t
+ (set-window-dedicated-p (selected-window) t)
+ (message "Window marked as dedicated"))))
-**** Quick capture
-Quick capture lets me send something to my inbox very quickly, without thinking about where it should go.
-The inbox is processed later.
+ (bind-key "C-x 9" #'za/toggle-window-dedicated-p)
-Templates for quick capture:
+#+end_src
+** Rotate windows horizontal ↔ vertical
#+begin_src emacs-lisp
- (setq org-capture-templates `(("t" "Todo [inbox]" entry
- (file ,za/org-life-inbox)
- "* TODO %i%?")
+ (defun za/rotate-windows ()
+ (interactive)
+ (if (= (count-windows) 2)
+ (let* ((this-win-buffer (window-buffer))
+ (next-win-buffer (window-buffer (next-window)))
+ (this-win-edges (window-edges (selected-window)))
+ (next-win-edges (window-edges (next-window)))
+ (this-win-2nd (not (and (<= (car this-win-edges)
+ (car next-win-edges))
+ (<= (cadr this-win-edges)
+ (cadr next-win-edges)))))
+ (splitter
+ (if (= (car this-win-edges)
+ (car (window-edges (next-window))))
+ 'split-window-horizontally
+ 'split-window-vertically)))
+ (delete-other-windows)
+ (let ((first-win (selected-window)))
+ (funcall splitter)
+ (if this-win-2nd (other-window 1))
+ (set-window-buffer (selected-window) this-win-buffer)
+ (set-window-buffer (next-window) next-win-buffer)
+ (select-window first-win)
+ (if this-win-2nd (other-window 1))))))
+#+end_src
- ("s" "Save for read/watch/listen" entry
- (file+headline ,za/org-life-tickler "Read/watch/listen")
- "* TODO %?[[%^{link}][%^{description}]] %^G")))
+#+begin_src emacs-lisp
+ (bind-key "C-x 7" #'za/rotate-windows)
#+end_src
-**** Todo & custom agenda views
-Todo keywords based on the GTD system (pipe separates incomplete from complete).
-Apart from the logging-on-done configured [[*Logging][below]], I also want to log a note & timestamp when I start waiting on something.
-In ~org-todo-keywords~, ~@~ means note+timestamp, ~!~ means timestamp, ~@/!~ means note+timestamp on state entry and timestamp on leave.
+** Open line like in Vim
+I prefer to open-line the way o/O works in Vim:
#+begin_src emacs-lisp
- (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w@)" "STARTED(s)" "|" "DONE(d)" "CANCELLED(c)"))
- org-todo-keyword-faces '(("TODO" . org-todo)
- ("NEXT" . org-todo)
- ("WAITING" . org-todo)
- ("STARTED" . org-todo)
- ("DONE" . org-done)
- ("CANCELLED" . org-done)))
+ ;; Autoindent open-*-lines
+ (defvar za/open-line-newline-and-indent t
+ "Modify the behavior of the open-*-line functions to cause them to autoindent.")
+
+ (defun za/open-line (prefix)
+ "Open line like `o`/`O` in Vim. Negative prefix for line above, positive for below."
+ (interactive "p")
+ (cond ((< prefix 0)
+ (beginning-of-line)
+ (open-line (abs prefix)))
+ (t
+ (end-of-line)
+ (open-line prefix)
+ (forward-line 1)))
+ (when za/open-line-newline-and-indent
+ (indent-according-to-mode)))
+
+ (defun za/open-line-keep-point (prefix)
+ "Open line like `o`/`O` in Vim but don't move point. Negative prefix for line above, positive for below."
+ (interactive "p")
+ (save-mark-and-excursion (za/open-line prefix)))
#+end_src
-I decided that projects will not be TODO items, but their progress will be tracked with a progress cookie ([x/y]). This function converts an item to a project: it adds a PROJECT tag, sets the progress indicator to count all checkboxes in sub-items (only TODO items), and removes any existing TODO keywords. Finally, PROJECT tags shouldn't be inherited (i.e. subtasks shouldn't be marked as projects).
+And keybindings:
#+begin_src emacs-lisp
- (defun za/mark-as-project ()
- "This function makes sure that the current heading has
- (1) the tag PROJECT
- (2) the property COOKIE_DATA set to \"todo recursive\"
- (3) a leading progress indicator"
- (interactive)
- (org-set-property "TODO" "")
- (org-toggle-tag "PROJECT" 'on)
- (org-set-property "COOKIE_DATA" "todo recursive")
- (org-back-to-heading t)
- (forward-whitespace 1)
- (insert "[/] ")
- (org-update-statistics-cookies nil))
+ (bind-key "C-o" #'za/open-line)
+ (bind-key "C-M-o" #'za/open-line-keep-point)
#+end_src
-Only the top-level project headlines should be tagged as projects, so disable inheritance of that tag:
+** Unfill region/paragraph
+Taken from here: https://www.emacswiki.org/emacs/UnfillParagraph
#+begin_src emacs-lisp
- (setq org-tags-exclude-from-inheritance '("PROJECT"))
+ (defun za/unfill-paragraph (&optional region)
+ "Takes a multi-line paragraph and makes it into a single line of text."
+ (interactive (progn (barf-if-buffer-read-only) '(t)))
+ (let ((fill-column (point-max))
+ ;; This would override `fill-column' if it's an integer.
+ (emacs-lisp-docstring-fill-column t))
+ (fill-paragraph nil region)))
+
+ (bind-key "M-Q" #'za/unfill-paragraph)
#+end_src
+** Easily edit my config
+Bind a keyboard shortcut to open my config.
+The "(interactive)" means that it can be called from a keybinding or from M-x.
-Define a function to skip items if they're part of a project (i.e. one of their parents has a "PROJECT" tag).
-The problem is, the "PROJECT" tag isn't inherited. So, we temporarily disable excluding from inheritance, just for the ~org-get-tags~ call. Then check if "PROJECT" is one of the tags.
+#+begin_src emacs-lisp
+ (defun za/edit-config-org ()
+ "Edit my config.org file"
+ (interactive)
+ (find-file (expand-file-name "config.org" user-emacs-directory)))
+#+end_src
#+begin_src emacs-lisp
- (defun za/skip-if-in-project ()
- "Skip items that are part of a project"
- (let ((subtree-end (save-excursion (org-end-of-subtree t)))
- (item-tags (let ((org-tags-exclude-from-inheritance nil)) (org-get-tags))))
- (if (member "PROJECT" item-tags)
- subtree-end
- nil)))
+ (bind-key "C-c E" 'za/edit-config-org)
+#+end_src
+* Custom functions
+** Make region readonly or writable
+#+begin_src emacs-lisp
+ (defun za/set-region-read-only (begin end)
+ "Sets the read-only text property on the marked region.
+ Use `set-region-writeable' to remove this property."
+ ;; See https://stackoverflow.com/questions/7410125
+ (interactive "r")
+ (with-silent-modifications
+ (put-text-property begin end 'read-only t)))
+ (defun za/set-region-writeable (begin end)
+ "Removes the read-only text property from the marked region.
+ Use `set-region-read-only' to set this property."
+ ;; See https://stackoverflow.com/questions/7410125
+ (interactive "r")
+ (with-silent-modifications
+ (remove-text-properties begin end '(read-only t))))
#+end_src
+** Insert macro as Lisp
+From here: https://www.masteringemacs.org/article/keyboard-macros-are-misunderstood
-Also, define a function to skip tasks (trees) that are not habits (i.e. don't have the STYLE property ~habit~):
+#+begin_src emacs-lisp
+ (use-package kmacro
+ :ensure nil ; included with Emacs
+ :bind (:map kmacro-keymap
+ ("I" . kmacro-insert-macro))
+ :config
+ (defalias 'kmacro-insert-macro 'insert-kbd-macro)
+ ;; Add advice to ignore errors on `kmacro-keyboard-macro-p`, it was
+ ;; messing up because of some entry in `obarray`
+ (advice-add #'kmacro-keyboard-macro-p :around (lambda (fun sym) "Ignore errors." (ignore-errors (funcall fun sym)))))
+#+end_src
+** Show local help at point when idling
#+begin_src emacs-lisp
- (defun za/skip-unless-habit ()
- "Skip trees that are not habits"
- (let ((subtree-end (save-excursion (org-end-of-subtree t))))
- (if (string= (org-entry-get nil "STYLE") "habit")
- nil
- subtree-end)))
+ (defun za/echo-area-tooltips ()
+ "Show tooltips in the echo area automatically for current buffer."
+ (setq-local help-at-pt-display-when-idle t
+ help-at-pt-timer-delay 0)
+ (help-at-pt-cancel-timer)
+ (help-at-pt-set-timer))
#+end_src
-And one to skip tasks that /are/ habits:
+** Info manual functions
+For some reason, these things don't show up in the index:
#+begin_src emacs-lisp
- (defun za/skip-if-habit ()
- "Skip trees that are not habits"
- (let ((subtree-end (save-excursion (org-end-of-subtree t))))
- (if (string= (org-entry-get nil "STYLE") "habit")
- subtree-end
- nil)))
+ (defun elisp-info (&optional node)
+ "Read documentation for Elisp in the info system.
+ With optional NODE, go directly to that node."
+ (interactive)
+ (info (format "(elisp)%s" (or node ""))))
#+end_src
+Though I can also just use ~info-display-manual~.
-And another function, to skip tasks that are blocked:
+** Radio
+Just a wrapper function to my radio script:
#+begin_src emacs-lisp
- (defun za/skip-if-blocked ()
- "Skip trees that are blocked by previous tasks"
- (let ((subtree-end (save-excursion (org-end-of-subtree t))))
- (if (org-entry-blocked-p)
- subtree-end
- nil)))
+ (defun radio ()
+ "Play an internet radio"
+ (interactive)
+ (ansi-term "radio" "*radio*"))
#+end_src
-Create custom agenda view based on those keywords.
-Agenda views are made up of blocks, appearing in the order that you declare them.
-The first two strings are what shows up in the agenda dispatcher (the key to press and the description).
+** no-op
+#+begin_src emacs-lisp
+ (defun za/no-op (&rest args))
+#+end_src
+** Syncthing
+Some functions to start/stop syncthing.
#+begin_src emacs-lisp
- (setq org-agenda-custom-commands
- '(("n" "Next actions"
- todo "NEXT" ((org-agenda-overriding-header "Next actions:")
- (org-agenda-sorting-strategy '(priority-down alpha-up))))
- ("W" "Waiting"
- ((todo "WAITING" ((org-agenda-overriding-header "Waiting:")))))
- ("S" . "Saved for later...")
- ("Sw" "Saved to watch"
- ((tags-todo "WATCH" ((org-agenda-overriding-header "To watch:")))))
- ("Sr" "Saved to read"
- ((tags-todo "READ" ((org-agenda-overriding-header "To read:")))))
- ("Sl" "Saved to listen"
- ((tags-todo "LISTEN" ((org-agenda-overriding-header "To listen:")))))
-
- ("a" . "Agenda with schedule only...")
- ("aw" "This week"
- ((agenda "" ((org-agenda-span 'week)))))
- ("ad" "Today"
- ((agenda "" ((org-agenda-span 'day)))))
- ("at" "Tomorrow"
- ((agenda "" ((org-agenda-span 'day)
- (org-agenda-start-day "+1d")))))
-
- ("w" "Week Agenda + Next Actions"
- ((agenda "" ((org-agenda-overriding-header "Week agenda:")))
- (todo "NEXT" ((org-agenda-overriding-header "Next actions:")))))
-
- ("o" "Month agenda"
- ((agenda "" ((org-agenda-overriding-header "Month agenda:")
- (org-agenda-span 'month)))))
-
- ("d" "Day Agenda + Next Actions + Habits"
- ((agenda "" ((org-agenda-overriding-header "Day:")
- (org-agenda-span 'day)
- (org-habit-show-habits nil)))
- (todo "NEXT" ((org-agenda-overriding-header "Next actions:")))
- (agenda "" ((org-agenda-overriding-header "Habits:")
- (org-agenda-span 'day)
- (org-agenda-use-time-grid nil)
- (org-agenda-skip-function 'za/skip-unless-habit)
- (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil)
- (org-habit-show-all-today t)))
- (todo "WAITING" ((org-agenda-overriding-header "Waiting:")))))
-
- ("k" "Kanban view"
- ((todo "DONE" ((org-agenda-overriding-header "Done:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
- (todo "STARTED" ((org-agenda-overriding-header "In progress:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
- (todo "NEXT" ((org-agenda-overriding-header "To do:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))))
-
- ("p" "Projects"
- ((tags "PROJECT" ((org-agenda-overriding-header "Projects:")
- (org-agenda-prefix-format '((tags . " %i %-22(let ((deadline (org-entry-get nil \"DEADLINE\"))) (if deadline deadline \"\"))")))
- (org-agenda-sorting-strategy '((tags deadline-up alpha-down)))))))
-
- ("f" "Finished tasks that aren't in a project"
- ((tags "TODO=\"DONE\"|TODO=\"CANCELLED\"" ((org-agenda-overriding-header "Finished tasks:")
- (org-agenda-skip-function 'za/skip-if-in-project)))))
-
- ;; Useful thread for opening calfw: https://github.com/kiwanami/emacs-calfw/issues/18
- ("c" "Calendar view" (lambda (&rest _)
- (interactive)
- (let ((org-agenda-skip-function 'za/skip-if-habit))
- (cfw:open-org-calendar))))))
-#+end_src
-
-In calfw, I don't want to show habits:
-
-#+begin_src emacs-lisp
- (add-hook 'cfw:calendar-mode-hook (setq-local org-agenda-skip-function 'za/skip-if-habit))
-#+end_src
-
-**** Logging for tasks
-I want to log into the LOGBOOK drawer (useful when I want to take quick notes):
-
-#+begin_src emacs-lisp
- (setq org-log-into-drawer "LOGBOOK")
-#+end_src
-
-I also want to log when I finish a task (useful for archiving).
-Furthermore, when I'm done, I want to add a note (any important
-workarounds/tips). And when I reschedule, I want to know the reason.
-I can disable logging on state change for a specific task by adding ~:LOGGING: nil~ to the ~:PROPERTIES:~ drawer.
-
-#+begin_src emacs-lisp
- (setq org-log-done 'note
- org-log-reschedule 'note)
-#+end_src
-
-I want to hide drawers on startup. This variable has options:
-- 'overview': Top-level headlines only.
-- 'content': All headlines.
-- 'showall': No folding on any entry.
-- 'show2levels: Headline levels 1-2.
-- 'show3levels: Headline levels 1-3.
-- 'show4levels: Headline levels 1-4.
-- 'show5levels: Headline levels 1-5.
-- 'showeverything: Show even drawer contents.
-
-#+begin_src emacs-lisp
- (setq org-startup-folded 'content)
-#+end_src
-
-**** Task ordering
-Some tasks should be ordered, i.e. they should be done in steps.
-Those have the ~:ORDERED: t~ setting in ~:PROPERTIES:~, and it should be enforced:
-
-#+begin_src emacs-lisp
- (setq org-enforce-todo-dependencies t)
-#+end_src
-
-Furthermore, tasks that are ordered and can't be done yet because of previous steps should be dimmed in the agenda:
-
-#+begin_src emacs-lisp
- (setq org-agenda-dim-blocked-tasks t)
-#+end_src
-
-I might also want to set ~org-enforce-todo-checkbox-dependencies~, but not convinced on that one yet.
-
-**** Time tracking & effort
-Time tracking should be done in its own drawer:
-
-#+begin_src emacs-lisp
- (setq org-clock-into-drawer "CLOCK")
-#+end_src
-
-And to customize how clock tables work:
-
-#+begin_src emacs-lisp
- (setq org-clocktable-defaults '(:lang "en" :scope agenda-with-archives :wstart 1 :mstart 1 :compact t :maxlevel nil))
- (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel nil))
-#+end_src
-
-I want to set effort in hours:minutes:
-
-#+begin_src emacs-lisp
- (add-to-list 'org-global-properties '("Effort_ALL" . "0:05 0:10 0:15 0:20 0:30 0:45 1:00 1:30 2:00 4:00 6:00 8:00"))
-#+end_src
-
-I want column view to look like this:
-
-| To do | Task | Tags | Sum of time elapsed | Sum of time estimated (effort) |
-|--------------+-----------+------+---------------------+--------------------------------|
-| todo keyword | task name | tags | sum of clock | sum of estimated time |
-| ... | ... | ... | ... | ... |
-
-#+begin_src emacs-lisp
- (setq org-columns-default-format "%7TODO (To Do) %32ITEM(Task) %TAGS(Tags) %11CLOCKSUM_T(Clock) %10Difficulty(Difficulty) %8Effort(Effort){:}")
-#+end_src
-
-Fix column alignment in agenda.
-
-#+begin_src emacs-lisp
- (set-face-attribute 'org-column nil
- :height (face-attribute 'default :height)
- :family (face-attribute 'default :family))
- (set-face-attribute 'org-agenda-date-today nil
- :height (face-attribute 'default :height))
-#+end_src
-
-**** Calculate time since timestamp
-#+begin_src emacs-lisp
- (defun za/org-time-since ()
- "Print the amount of time between the timestamp at point and the current date and time."
- (interactive)
- (unless (org-at-timestamp-p 'lax)
- (user-error "Not at timestamp"))
-
- (when (org-at-timestamp-p 'lax)
- (let ((timestamp (match-string 0)))
- (with-temp-buffer
- (insert timestamp
- "--"
- (org-time-stamp '(16)))
- (org-evaluate-time-range)))))
-#+end_src
-
-*** Priorities: how important something is
-I usually have a lot of 'next' actions, so I prefer 4 priority levels instead of the default 3: A (urgent, ASAP), B (important), C (not super important), D (do in free time):
-
-#+begin_src emacs-lisp
- (setq org-priority-highest ?A
- org-priority-lowest ?D
- org-priority-default ?C)
-#+end_src
-
-Faces for priorities in agenda:
-
-#+begin_src emacs-lisp
- (setq org-priority-faces `((?A . (:foreground ,(face-foreground 'error)))
- (?B . (:foreground ,(face-foreground 'org-todo)))
- (?C . (:foreground ,(face-foreground 'font-lock-constant-face) :weight semi-light))
- (?D . (:foreground ,(face-foreground 'font-lock-string-face) :slant italic :weight light))))
-#+end_src
-
-*** Energy requirement: how difficult something is
-#+begin_src emacs-lisp
- (add-to-list 'org-global-properties '("Difficulty_ALL" . "low medium high"))
-#+end_src
-*** Custom functions
-**** Get number of headlines in a file
-#+begin_src emacs-lisp
- (defun za/org-count-headlines-in-file (level filename)
- "Count number of level LEVEL headlines in FILENAME. If LEVEL is 0, count all."
- (let ((headline-str (cond ((zerop level) "^\*+")
- (t (format "^%s " (apply 'concat (make-list level "\\*")))))))
- (save-mark-and-excursion
- (with-temp-buffer
- (insert-file-contents filename)
- (count-matches headline-str (point-min) (point-max))))))
-#+end_src
-
-**** Yank URL
-#+begin_src emacs-lisp
- (defun org-yank-link-url ()
- (interactive)
- (kill-new (org-element-property :raw-link (org-element-context)))
- (message "Link copied to clipboard"))
-#+end_src
-**** Manipulating time
-#+begin_src emacs-lisp
- (use-package org-timestone
- :quelpa (org-timestone :repo "thezeroalpha/org-timestone.el" :fetcher github)
- :ensure nil)
-#+end_src
-*** Tempo expansions
-
-#+begin_src emacs-lisp
- (add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp"))
- (add-to-list 'org-structure-template-alist '("sb" . "src bibtex"))
- (add-to-list 'org-structure-template-alist '("ss" . "src sh"))
-#+end_src
-
-*** Exporting
-#+begin_src emacs-lisp
-(custom-set-variables '(org-export-backends '(ascii html icalendar latex md odt org)))
-#+end_src
-
-*** Catch invisible edits
-Sometimes when text is folded away, I might accidentally edit text inside of it.
-This option prevents that.
-I wanted to do 'smart', but that has a 'fixme' so it might change in the future...
-Instead, show what's being edited, but don't perform the edit.
-
-#+begin_src emacs-lisp
- (setq org-catch-invisible-edits 'show-and-error)
-#+end_src
-
-*** Notification
-macOS version might not be compiled with dbus support; in that case you can use e.g. terminal-notifier.
-If you use the ~sender~ option, notifications don't show
-unless the app is in the background. [[https://github.com/julienXX/terminal-notifier/issues/68][See this Github issue.]]
-
-#+begin_src emacs-lisp
- ;; on mac without dbus:
- ;; (setq org-show-notification-handler
- ;; (lambda (str) (start-process "terminal-notifier" nil (executable-find "terminal-notifier")
- ;; "-title" "Timer done"
- ;; "-message" str
- ;; "-group" "org.gnu.Emacs"
- ;; "-ignoreDnD"
- ;; "-activate" "org.gnu.Emacs"))))
-#+end_src
-
-*** org-caldav
-This lets me sync my Org agenda to my CalDAV server.
-The main reason is because Orgzly doesn't have a calendar view and can't (yet) search for events on a specific day, so if someone asks "are you free on that day", it's a bit hard for me to answer if I don't have my computer with me.
-This way, I can just check my calendar.
-
-#+begin_src emacs-lisp
- (use-package org-caldav)
-#+end_src
-
-A lot of these variables are from my secret.el file, they're not something I can share publicly.
-I use ~/.authinfo.gpg to store authorization info for the server.
-
-#+begin_src emacs-lisp
- (setq org-caldav-url za/caldav-url
- org-caldav-calendar-id za/caldav-org-calendar-id
- za/org-life-calendar-inbox (concat za/org-life-dir "calendar-inbox.org")
- org-caldav-inbox za/org-life-calendar-inbox
- org-caldav-files (cons (car (split-string org-archive-location "::")) org-agenda-files)
- org-icalendar-include-todo 'all
- org-icalendar-use-deadline '(event-if-todo event-if-not-todo todo-due)
- org-icalendar-use-scheduled '(todo-start event-if-todo event-if-not-todo))
-
-#+end_src
-
-I don't want to export habits, because those will just clutter up my calendar.
-The calendar is supposed to be for one-off stuff, or rarely repeating stuff.
-Yes, I have to manually add the "HABIT" tag to every habit.
-Perhaps nicer would be to exclude based on the property ~:STYLE: habit~, but I haven't figured that one out yet.
-
-#+begin_src emacs-lisp
- (setq org-caldav-exclude-tags '("HABIT"))
-#+end_src
-
-Maybe check [[https://old.reddit.com/r/orgmode/comments/8rl8ep/making_orgcaldav_useable/e0sb5j0/][this]] for a way to sync on save.
-
-** org-contrib
-#+begin_src emacs-lisp
- (use-package org-contrib
- :config
- (require 'org-checklist))
-#+end_src
-** org-ref
-#+begin_src emacs-lisp
- (use-package org-ref)
-#+end_src
-** org-roam
-#+begin_src emacs-lisp
- (use-package org-roam
- :custom
- (org-roam-directory za/org-roam-dir)
- (org-roam-completion-everywhere t)
-
- :config
- (org-roam-setup))
- (require 'org-roam-export)
-#+end_src
-
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c w n") #'org-roam-capture)
- (za/global-set-key (kbd "C-c w f") #'org-roam-node-find)
- (za/global-set-key (kbd "C-c w w") #'org-roam-buffer-toggle)
- (za/global-set-key (kbd "C-c w i") #'org-roam-node-insert)
-#+end_src
-** org-roam-ui
-#+begin_src emacs-lisp
- (use-package org-roam-ui)
-#+end_src
-** org-download
-Drag-and-drop images to Emacs Org mode.
-
-#+begin_src emacs-lisp
- (use-package org-download
- :config
- (setq org-download-method 'attach
- org-download-backend 'curl))
-#+end_src
-** org-sticky-header
-Displays in the header-line the Org heading for the node that’s at the top of the window.
-
-#+begin_src emacs-lisp
- (use-package org-sticky-header)
-#+end_src
-** org publishing
-I decided, after trying many different things, to settle on org-publish.
-
-#+begin_src emacs-lisp
- (defconst za/org-roam-top-name "Top" "The name of the top-level Org-roam node.")
- (defun za/org-roam-sitemap-function (title list)
- "Customized function to generate sitemap for org-roam, almost the same as `org-publish-sitemap-default`."
- (concat "#+TITLE: " title "\n\n"
- (format "[[file:%s][%s]]\n\n"
- (file-name-nondirectory (org-roam-node-file
- (org-roam-node-from-title-or-alias za/org-roam-top-name)))
- "Click here for entrypoint.")))
- ;; (org-list-to-org list))) <-- this is taken care of by Zola
-
-#+end_src
-
-To make this work with Zola, I need to export Github-flavored markdown (fenced code blocks with language):
-
-#+begin_src emacs-lisp
- (require 'ox-publish)
- (require 'ox-md)
-
- ;; See here: https://github.com/larstvei/ox-gfm/issues/36
- (use-package ox-gfm
- :defer 1
- :after org)
-#+end_src
-
-First difficulty: Zola needs front matter with ~+++...+++~.
-The default Markdown backend doesn't provide that, so need to customize it by advising the default ~org-md-template~.
-
-#+begin_src emacs-lisp
- (defun za/org-md-template-zola (contents info)
- "Markdown template compatible with Zola (generates the necessary front matter from CONTENTS and INFO)."
- (let ((title (org-md-plain-text (org-element-interpret-data (plist-get info :title)) info)))
- (concat "+++\n"
- (format "title = \"%s\"" (string-replace "\"" "'" title))
- "\n+++\n"
- (format "# %s\n" title)
- contents)))
-#+end_src
-
-Second difficulty: links need to be reformatted and changed for static data (like images).
-This function filters the return value of ~org-md-link~.
-
-#+begin_src emacs-lisp
- (defun za/org-md-link-zola (linkstr)
- "A filter function for the return value of
- `org-md-link` (LINKSTR) to generate a link compatible with Zola."
- (cond ((string-match-p (rx ".md") linkstr)
- (string-replace "](" "](@/org-roam/" linkstr))
- ((string-match-p (rx "](/") linkstr)
- (replace-regexp-in-string (rx "](/" (* any) "/org-roam/data") "](/org-roam-data" linkstr))
- (t linkstr)))
-#+end_src
-
-And here's the custom publish function that adds/removes the necessary advice:
-
-#+begin_src emacs-lisp
- (defun za/org-gfm-publish-to-gfm-zola (plist filename pub-dir)
- "Run `org-gfm-publish-to-gfm`, advising the necessary
- functions to generate Zola-compatible markdown."
- (let ((advice '((org-md-template :override za/org-md-template-zola)
- (org-md-link :filter-return za/org-md-link-zola)
- (org-gfm-table :override org-md--convert-to-html)))) ; Zola uses CommonMark, so doesn't support Markdown tables
-
- (dolist (orig-type-new advice) (apply #'advice-add orig-type-new))
- (org-gfm-publish-to-gfm plist filename pub-dir)
- (dolist (orig-type-new advice)
- (advice-remove (nth 0 orig-type-new)
- (nth 2 orig-type-new)))))
-#+end_src
-
-Finally, the list of things we can publish with their respective publishin functions:
-
-#+begin_src emacs-lisp
- (setq org-publish-project-alist
- `(
- ("org-notes"
- :base-directory ,za/org-roam-dir
- :base-extension "org"
- :publishing-directory ,(concat za/my-website-dir "content/org-roam/")
- :publishing-function za/org-gfm-publish-to-gfm-zola
- :recursive t
- :sitemap-filename "_index.md"
- :sitemap-title "Org Roam"
- :sitemap-function za/org-roam-sitemap-function
- :auto-sitemap t)
-
- ("org-notes-data"
- :base-directory ,(concat za/org-roam-dir "/data")
- :base-extension any
- :publishing-directory ,(concat za/my-website-dir "static/org-roam-data/")
- :recursive t
- :publishing-function org-publish-attachment)
-
- ("org-roam" :components ("org-notes" "org-notes-data"))
- ))
-#+end_src
-
-And a function to rsync to my VPS:
-
-#+begin_src emacs-lisp
- (defun za/publish-upload-to-website ()
- "Upload my website to my VPS"
+ (defconst za/st-buffer-name "*syncthing*" "Buffer name for the syncthing process.")
+ (defun za/st ()
+ "Start syncthing"
(interactive)
- (async-shell-command (format "cd %s && zola build && yes|publish" za/my-website-dir) "*Async Shell publish*"))
-#+end_src
-*** TODO the path for org-roam export and data export should be configurable, not hard-coded
-** calfw
-Basically provides a way to show the org agenda as a standard GUI calendar app would.
-
-#+begin_src emacs-lisp
- (use-package calfw
- :config
- (use-package calfw-org)
- (setq cfw:org-overwrite-default-keybinding t)
- (setq calendar-week-start-day 1))
-#+end_src
-
-** vanish: hide parts of the file
-#+begin_src emacs-lisp
- (use-package vanish
- :quelpa (vanish :fetcher github :repo "thezeroalpha/vanish.el" :branch "develop")
- :ensure nil)
-#+end_src
-
-Customize the prefix:
-
-#+begin_src emacs-lisp
- (define-key vanish-mode-map (kbd "C-c q h") #'vanish-toggle)
-#+end_src
-** lean-mode
-Specifically for the Lean prover.
-I also install company-lean and helm-lean, which are suggested on the [[https://github.com/leanprover/lean-mode][Github page]].
-Then I map company-complete only for lean-mode.
-
-#+begin_src emacs-lisp
- (use-package lean-mode
- :config
- (use-package company-lean)
- :hook
- (lean-mode . (lambda () )))
-#+end_src
-
-#+begin_src emacs-lisp
- (defun za/keybinds-lean-mode ()
- "Function to set lean-mode keybindings, run via lean-mode-hook."
- (define-key lean-mode-map (kbd "S-SPC") #'company-complete))
-
- (add-hook 'lean-mode-hook #'za/keybinds-lean-mode)
-#+end_src
-
-** magit
-#+begin_src emacs-lisp
- (use-package magit)
-#+end_src
-
-** vterm
-Emacs has a bunch of built-in terminal emulators.
-And they all suck.
-(OK not really, eshell is alright, but not for interactive terminal programs like newsboat/neomutt)
-
-Also use emacsclient inside vterm as an editor, because that'll open documents in the existing Emacs session.
-And I'm not gonna be a heretic and open Vim inside of Emacs.
-
-#+begin_src emacs-lisp
- (use-package vterm
- :hook
- (vterm-mode . (lambda () (unless server-process (server-start)))))
-#+end_src
-
-I'll bind a key to start a vterm or switch to the running vterm:
-
-#+begin_src emacs-lisp
- (defun switch-to-vterm () "Switch to a running vterm, or start one and switch to it."
- (interactive)
- (if (get-buffer vterm-buffer-name)
- (switch-to-buffer vterm-buffer-name)
- (vterm)))
- (za/global-set-key (kbd "C-c t") 'switch-to-vterm)
-#+end_src
-
-** sr-speedbar
-Make speed bar show in the current frame.
-
-#+begin_src emacs-lisp
- (use-package sr-speedbar
- :config
- (setq sr-speedbar-right-side nil))
-#+end_src
-
-#+begin_src emacs-lisp
- (defun za/keybinds-speedbar-mode ()
- "Function to set speedbar-mode keybindings, run via speedbar-mode-hook."
- (define-key speedbar-mode-map (kbd "q") 'sr-speedbar-close))
-
- (add-hook 'speedbar-mode-hook #'za/keybinds-speedbar-mode)
-#+end_src
-
-Jump to speedbar. sr-speedbar-exist-p can be void, so I check if it's bound first.
-If it's not bound, or if it's false, first open the speedbar.
-Then, select it.
+ (if (get-buffer-process za/st-buffer-name)
+ (user-error "Syncthing is already running."))
+ (async-shell-command "syncthing serve --no-browser" za/st-buffer-name))
-#+begin_src emacs-lisp
- (defun za/jump-to-speedbar-or-open ()
- "Open a speedbar or jump to it if already open."
+ (defun za/st-kill ()
+ "Stop syncthing"
(interactive)
- (if (or (not (boundp 'sr-speedbar-exist-p))
- (not (sr-speedbar-exist-p)))
- (sr-speedbar-open))
- (sr-speedbar-select-window))
-#+end_src
-
-
-
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c F") 'za/jump-to-speedbar-or-open)
-#+end_src
-** expand-region
-Expand the selected region semantically.
-
-#+begin_src emacs-lisp
- (use-package expand-region
- :bind ("C-=" . er/expand-region))
-#+end_src
-
-** flycheck
-Install flycheck, and enable it by default in certain major modes:
-
-#+begin_src emacs-lisp
- (use-package flycheck
- :hook (sh-mode . flycheck-mode))
-#+end_src
-
-** anki-editor
-Some extra keybindings that are not set up by default.
-anki-editor doesn't provide a keymap so I have to set one up here:
-
-#+begin_src emacs-lisp
- (use-package anki-editor
- :config
- (defvar anki-editor-mode-map (make-sparse-keymap))
- (add-to-list 'minor-mode-map-alist (cons 'anki-editor-mode
- anki-editor-mode-map))
-
- (setq anki-editor-use-math-jax t)
-
- :hook
- (anki-editor-mode . (lambda ()
- (define-key anki-editor-mode-map (kbd "C-c t") #'org-property-next-allowed-value)
- (define-key anki-editor-mode-map (kbd "C-c i") #'anki-editor-insert-note)
- (define-key anki-editor-mode-map (kbd "C-c p") #'anki-editor-push-notes)
- (define-key anki-editor-mode-map (kbd "C-c c") #'anki-editor-cloze-dwim))))
-#+end_src
-
-** rainbow-mode
-'rainbow-mode' lets you visualise hex colors:
-
-#+begin_src emacs-lisp
- (use-package rainbow-mode)
-#+end_src
-
-** pdf-tools
-A better replacement for DocView:
-
-#+begin_src emacs-lisp
- (use-package pdf-tools
- :config
- (setq-default pdf-annot-default-annotation-properties '((t
- (label . "Alex Balgavy"))
- (text
- (icon . "Note")
- (color . "#0088ff"))
- (highlight
- (color . "yellow"))
- (squiggly
- (color . "orange"))
- (strike-out
- (color . "red"))
- (underline
- (color . "blue"))))
- :hook
- (pdf-annot-list-mode . pdf-annot-list-follow-minor-mode)
- (pdf-annot-edit-contents-minor-mode . org-mode)
- (pdf-view-mode . (lambda () (display-line-numbers-mode 0)))
- (pdf-view-mode . (lambda () (define-key pdf-isearch-minor-mode-map (kbd "C-s") #'isearch-forward))))
- (pdf-tools-install)
-#+end_src
-
-Save position and jump back:
-
-#+begin_src emacs-lisp
- (define-key pdf-view-mode-map (kbd "C-SPC")
- (lambda () (interactive) (message "Position saved") (pdf-view-position-to-register ?x)))
- (define-key pdf-view-mode-map (kbd "C-u C-SPC")
- (lambda () (interactive) (pdf-view-jump-to-register ?x)))
-#+end_src
-
-*** TODO this clobbers register x. Find a way to not clobber a register
-
-*** Fix up arrow
-The arrow tooltip does not show properly when jumping to a location.
-Maybe this is a Mac-only thing. See here: https://github.com/politza/pdf-tools/issues/145
-
-This ~:override~ advice fixes it, color is customized via ~tooltip~ face:
-
-#+begin_src emacs-lisp
- (defun za/pdf-util-tooltip-arrow (image-top &optional timeout)
- "Fix up `pdf-util-tooltip-arrow`, the original doesn't show the arrow."
- (pdf-util-assert-pdf-window)
- (when (floatp image-top)
- (setq image-top
- (round (* image-top (cdr (pdf-view-image-size))))))
- (let* (x-gtk-use-system-tooltips ;allow for display property in tooltip
- (dx (+ (or (car (window-margins)) 0)
- (car (window-fringes))))
- (dy image-top)
- (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
- (vscroll
- (pdf-util-required-vscroll pos))
- (tooltip-frame-parameters
- `((border-width . 0)
- (internal-border-width . 0)
- ,@tooltip-frame-parameters))
- (tooltip-hide-delay (or timeout 3)))
- (when vscroll
- (image-set-window-vscroll vscroll))
- (setq dy (max 0 (- dy
- (cdr (pdf-view-image-offset))
- (window-vscroll nil t)
- (frame-char-height))))
- (when (overlay-get (pdf-view-current-overlay) 'before-string)
- (let* ((e (window-inside-pixel-edges))
- (xw (pdf-util-with-edges (e) e-width)))
- (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2))))
- (pdf-util-tooltip-in-window "\u2192" dx dy)))
- (advice-add #'pdf-util-tooltip-arrow :override #'za/pdf-util-tooltip-arrow)
-#+end_src
-
-** virtualenvwrapper
-Like virtualenvwrapper.sh, but for Emacs.
-
-#+begin_src emacs-lisp
- (use-package virtualenvwrapper
- :config
- (venv-initialize-interactive-shells)
- (venv-initialize-eshell)
- (setq venv-location "~/.config/virtualenvs"))
-#+end_src
-
-** org-noter
-#+begin_src emacs-lisp
- (use-package org-noter)
-#+end_src
-
-Fix disabling of line wrap by no-opping set-notes-scroll:
-
-#+begin_src emacs-lisp
- (defun za/no-op (&rest args))
- (advice-add 'org-noter--set-notes-scroll :override 'za/no-op)
-#+end_src
-
-** hl-todo
-I want to highlight TODO keywords in comments:
-
-#+begin_src emacs-lisp
- (use-package hl-todo
- :custom-face
- (hl-todo ((t (:inherit hl-todo :underline t))))
- :config
- (setq hl-todo-keyword-faces
- '(("TODO" . "#ff7060")
- ("FIXME" . "#caa000")))
- (global-hl-todo-mode t))
+ (unless (get-buffer-process za/st-buffer-name)
+ (user-error "Syncthing is not running."))
+ (async-shell-command "syncthing cli operations shutdown"))
#+end_src
-** undo-tree
-Sometimes it's better to look at undo history as a tree:
+* Interface
+** Theme
+Icons required for some parts of the doom theme:
#+begin_src emacs-lisp
- (use-package undo-tree
- :config
- (global-undo-tree-mode))
+ (use-package all-the-icons)
#+end_src
-Save undo files into ~/.emacs.d/undo-tree
-
-#+begin_src emacs-lisp
- (let ((undo-tree-dir (concat user-emacs-directory "undo-tree/")))
- (unless (file-directory-p undo-tree-dir) (make-directory undo-tree-dir))
- (setq undo-tree-history-directory-alist `(("." . ,undo-tree-dir))))
-#+end_src
+Load Doom Emacs themes:
-** ledger
#+begin_src emacs-lisp
- (use-package ledger-mode
- :mode ("\\.ledger\\'")
+ (use-package doom-themes
:config
- (setq ledger-clear-whole-transactions t
- ledger-reconcile-default-commodity "eur"))
-#+end_src
-
-org-capture lets me add transactions from anywhere in Emacs:
-
-#+begin_src emacs-lisp
- (add-to-list 'org-capture-templates `("$" "Ledger entry" plain
- (file ,za/ledger-file)
- "%(ledger-read-date \"Date\") %^{Payee}
- %^{Account} %^{Amount}
- Assets:ABN Checking%?
- "
- :empty-lines-before 1))
-
+ ;; Global settings (defaults)
+ (setq doom-themes-enable-bold t ; if nil, bold is universally disabled
+ doom-themes-enable-italic t) ; if nil, italics is universally disabled
-#+end_src
+ ;; Enable flashing mode-line on errors
+ (doom-themes-visual-bell-config)
-Budget throws an error when there's multiple commodities involved.
-See discussion here: https://github.com/ledger/ledger/issues/1450#issuecomment-390067165
-#+begin_src emacs-lisp
- (defconst za/ledger-budget-fix-string
- "-X eur -F '%(justify(scrub(get_at(display_total, 0)), 20, -1, true, false)) %(justify(get_at(display_total, 1) ? -scrub(get_at(display_total, 1)) : 0.0, 20, 20 + 1 + 20, true, false)) %(justify(get_at(display_total, 1) ? (get_at(display_total, 0) ? -(scrub(get_at(display_total, 1) + get_at(display_total, 0))) : -(scrub(get_at(display_total, 1)))) : -(scrub(get_at(display_total, 0))), 20, 20 + 1 + 20 + 1 + 20, true, false))%(get_at(display_total, 1) and (abs(quantity(scrub(get_at(display_total, 0))) / quantity(scrub(get_at(display_total, 1)))) >= 1) ? \" \" : \" \")%(justify((get_at(display_total, 1) ? (100% * (get_at(display_total, 0) ? scrub(get_at(display_total, 0)) : 0.0)) / -scrub(get_at(display_total, 1)) : \"na\"), 5, -1, true, false)) %(!options.flat ? depth_spacer : \"\")%-(partial_account(options.flat))\n%/%$2 %$3 %$4 %$6\n%/%(prepend_width ? \" \" * int(prepend_width) : \"\") ---------------- ---------------- ---------------- -----\n'"
- "Append this to a ledger budget to fix errors with multiple commodities.")
+ ;; Corrects (and improves) org-mode's native fontification.
+ (doom-themes-org-config))
#+end_src
-Custom reports:
+Define the themes I want:
#+begin_src emacs-lisp
- (custom-set-variables
- '(ledger-reports
- '(("budget-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" budget ^expenses")
- ("budget-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" budget ^expenses")
- ("expenses-this-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^expenses --budget")
- ("expenses-last-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" --period-sort \"(amount)\" bal ^expenses --budget")
- ("expenses-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur")
- ("expenses-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur")
- ("expenses-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur")
- ("expenses-vs-income-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur")
- ("expenses-vs-income-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"last month\" --period-sort \"(amount)\" bal ^expenses ^income -X eur")
- ("bal-assets-czk" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X czk")
- ("bal-assets" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities")
- ("bal" "%(binary) -f %(ledger-file) --start-of-week=1 bal -B")
- ("bal-assets-eur" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X eur")
- ("monthly-balance-abn-checking" "%(binary) -f %(ledger-file) --start-of-week=1 --effective reg --monthly 'Assets:ABN Checking'")
- ("monthly-expenses" "%(binary) -f %(ledger-file) --monthly register ^expenses --collapse -X eur")
- ("reg" "%(binary) -f %(ledger-file) --start-of-week=1 reg")
- ("payee" "%(binary) -f %(ledger-file) --start-of-week=1 reg @%(payee)")
- ("account" "%(binary) -f %(ledger-file) --start-of-week=1 reg %(account)"))))
-#+end_src
-** osm
-#+begin_src emacs-lisp
- (use-package osm
- :bind (("C-c M h" . osm-home)
- ("C-c M s" . osm-search)
- ("C-c M v" . osm-server)
- ("C-c M t" . osm-goto)
- ("C-c M x" . osm-gpx-show)
- ("C-c M j" . osm-bookmark-jump))
+ (defconst za/dark-theme-name 'doom-one "A symbol representing the name of the dark theme I use.")
+ (defconst za/light-theme-name 'jokull "A symbol representing the name of the light theme I use.")
+ ;; I used to use doom-acario-light before writing my own theme
- :custom
- ;; Take a look at the customization group `osm' for more options.
- (osm-server 'default) ;; Configure the tile server
- (osm-copyright nil) ;; Display the copyright information
+ (defun za/dark-theme ()
+ "Switch to dark theme"
+ (interactive)
+ (mapc #'disable-theme custom-enabled-themes)
+ (load-theme za/dark-theme-name t)
+ (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
- :init
- ;; Load Org link support
- (with-eval-after-load 'org
- (require 'osm-ol)))
-#+end_src
-** ess
-#+begin_src emacs-lisp
- (use-package ess)
+ (defun za/light-theme ()
+ "Switch to light theme"
+ (interactive)
+ (mapc #'disable-theme custom-enabled-themes)
+ (load-theme za/light-theme-name t)
+ (remove-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
#+end_src
-** eglot
-A good LSP plugin.
+
+Change theme depending on the current system theme.
+The way I check for dark mode is defined in 'dark-mode-p'; currently I use the presence of the ~/.config/dark-theme file to indicate when dark theme is set.
+I quote the call to ~file-exists-p~ because I want to evaluate it on-demand, not immediately.
+A function ending in '-p' is a predicate, i.e. returns true or false.
+If calling a function that's in a variable, you have to use 'funcall'.
+To evaluate a quoted form, use 'eval'.
#+begin_src emacs-lisp
- (use-package eglot)
+ (defun za/auto-select-theme (&rest _)
+ "Automatically select dark/light theme based on presence of ~/.config/dark-theme"
+ (let ((dark-mode-p '(file-exists-p "~/.config/dark-theme")))
+ (if (eval dark-mode-p)
+ (za/dark-theme)
+ (za/light-theme))))
+
+ (za/auto-select-theme)
#+end_src
-** crdt
-Collaborative editing in Emacs:
+** Font
+I want Menlo, size 14:
#+begin_src emacs-lisp
- (use-package crdt)
+ (add-to-list 'default-frame-alist '(font . "Menlo-14"))
#+end_src
-** git gutter
-General git gutter:
+** Cursor
+The default box cursor isn't really accurate, because the cursor is actually between letters, not on a letter.
+So, I want a bar instead of a box:
#+begin_src emacs-lisp
- (use-package git-gutter
- :config
- (global-git-gutter-mode 1))
+ (setq-default cursor-type '(bar . 4)
+ cursor-in-non-selected-windows 'hollow)
#+end_src
-Some bindings:
+(I use ~setq-default~ here because cursor-type is automatically buffer-local when it's set)
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c d n") #'git-gutter:next-hunk)
- (za/global-set-key (kbd "C-c d p") #'git-gutter:previous-hunk)
+** Matching parentheses
+Don't add a delay to show matching parenthesis.
+Must come before show-paren-mode enable.
+#+begin_src emacs-lisp
+ (setq show-paren-delay 0)
#+end_src
-** keycast
-In case I want to show what keys I'm pressing.
+Show matching parentheses:
#+begin_src emacs-lisp
-(use-package keycast)
+ (show-paren-mode t)
#+end_src
-
-** helpful
-An alternative to the built-in Emacs help that provides much more contextual information.
+** Line numbers
+Relative line numbers:
#+begin_src emacs-lisp
- (use-package helpful)
+ (setq display-line-numbers-type 'relative)
+ (global-display-line-numbers-mode)
#+end_src
-I use counsel, so I use the keybindings in [[*counsel + ivy + swiper]].
-I just augment the functions counsel uses:
+Function to hide them:
#+begin_src emacs-lisp
- (setq counsel-describe-symbol-function #'helpful-symbol
- counsel-describe-function-function #'helpful-callable
- counsel-describe-variable-function #'helpful-variable)
+ (defun za/hide-line-numbers ()
+ "Hide line numbers"
+ (display-line-numbers-mode 0))
#+end_src
-
-Also, counsel doesn't provide some keybindings that I can get from helpful:
+Don't display them in specific modes. For each of the modes in
+'mode-hooks', add a function to hide line numbers when the mode
+activates (which triggers the 'mode'-hook).
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-h k") #'helpful-key)
- (za/global-set-key (kbd "C-h C") #'helpful-command)
+ (let ((mode-hooks '(doc-view-mode-hook vterm-mode-hook mpc-status-mode-hook mpc-tagbrowser-mode-hook)))
+ (mapc
+ (lambda (mode-name)
+ (add-hook mode-name #'za/hide-line-numbers))
+ mode-hooks))
#+end_src
-
-Then, a way to jump forward and backward in the window:
+** Modeline
+I want to show the time and date in the modeline:
#+begin_src emacs-lisp
- (defvar za/helpful-buffer-ring-size 20
- "How many buffers are stored for use with `helpful-next'.")
-
- (defvar za/helpful--buffer-ring (make-ring za/helpful-buffer-ring-size)
- "Ring that stores the current Helpful buffer history.")
-
- (defun za/helpful--buffer-index (&optional buffer)
- "If BUFFER is a Helpful buffer, return it’s index in the buffer ring."
- (let ((buf (or buffer (current-buffer))))
- (and (eq (buffer-local-value 'major-mode buf) 'helpful-mode)
- (seq-position (ring-elements za/helpful--buffer-ring) buf #'eq))))
-
- (defun za/helpful--new-buffer-a (help-buf)
- "Update the buffer ring according to the current buffer and HELP-BUF."
- :filter-return #'helpful--buffer
- (let ((buf-ring za/helpful--buffer-ring))
- (let ((newer-buffers (or (za/helpful--buffer-index) 0)))
- (dotimes (_ newer-buffers) (ring-remove buf-ring 0)))
- (when (/= (ring-size buf-ring) za/helpful-buffer-ring-size)
- (ring-resize buf-ring za/helpful-buffer-ring-size))
- (ring-insert buf-ring help-buf)))
-
- (advice-add #'helpful--buffer :filter-return #'za/helpful--new-buffer-a)
-
- (defun za/helpful--next (&optional buffer)
- "Return the next live Helpful buffer relative to BUFFER."
- (let ((buf-ring za/helpful--buffer-ring)
- (index (or (za/helpful--buffer-index buffer) -1)))
- (cl-block nil
- (while (> index 0)
- (cl-decf index)
- (let ((buf (ring-ref buf-ring index)))
- (if (buffer-live-p buf) (cl-return buf)))
- (ring-remove buf-ring index)))))
-
-
- (defun za/helpful--previous (&optional buffer)
- "Return the previous live Helpful buffer relative to BUFFER."
- (let ((buf-ring za/helpful--buffer-ring)
- (index (1+ (or (za/helpful--buffer-index buffer) -1))))
- (cl-block nil
- (while (< index (ring-length buf-ring))
- (let ((buf (ring-ref buf-ring index)))
- (if (buffer-live-p buf) (cl-return buf)))
- (ring-remove buf-ring index)))))
-
- (defun za/helpful-next ()
- "Go to the next Helpful buffer."
- (interactive)
- (when-let (buf (za/helpful--next))
- (funcall helpful-switch-buffer-function buf)))
-
- (defun za/helpful-previous ()
- "Go to the previous Helpful buffer."
- (interactive)
- (when-let (buf (za/helpful--previous))
- (funcall helpful-switch-buffer-function buf)))
+ (setq display-time-day-and-date t ; also the date
+ display-time-default-load-average nil ; don't show load average
+ display-time-format "%I:%M%p %e %b (%a)") ; "HR:MIN(AM/PM) day-of-month Month (Day)"
+ (display-time-mode 1) ; enable time mode
#+end_src
-** ace-window
-Window switching with ~other-window~ sucks when I have more than 2 windows open. Too much cognitive load.
-This lets me select a window to jump to using a single key, sort of like ~avy~.
+And to set the modeline format:
#+begin_src emacs-lisp
- (use-package ace-window)
- (za/global-set-key (kbd "M-o") #'ace-window)
+ (setq-default mode-line-format '("%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
+ (vc-mode vc-mode)
+ " " mode-line-modes mode-line-misc-info mode-line-end-spaces))
#+end_src
-I prefer using home-row keys instead of numbers:
+I want to hide certain modes from the modeline, they're always on:
#+begin_src emacs-lisp
- (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
+ (use-package diminish
+ :config
+ (let ((modes-to-hide '(ivy-mode counsel-mode which-key-mode hl-todo-mode undo-tree-mode ivy-posframe-mode git-gutter-mode)))
+ (mapc #'diminish modes-to-hide))
+ (diminish 'view-mode " 👓"))
#+end_src
-
-Also, I want something a little more contrasty:
-
+** Transparent title bar
#+begin_src emacs-lisp
- (custom-set-faces
- '(aw-leading-char-face
- ((t (:inherit font-lock-keyword-face :height 2.0)))))
+ (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
#+end_src
+** Tab bar
+Only show tab bar if there's more than 1 tab:
-** decide-mode for dice rolling
#+begin_src emacs-lisp
- (use-package decide
- :quelpa (decide :repo "lifelike/decide-mode" :fetcher github)
- :ensure nil)
+ (setq tab-bar-show 1)
#+end_src
+** Buffer displaying
-I want a custom keybinding to easily toggle:
+So, this is a bit hard to grok. But basically the alist contains a
+regular expression to match a buffer name, then a list of functions to
+use in order for displaying the list, and then options for those functions (each of which is an alist).
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c q ?") #'decide-mode)
+ (setq
+ ;; Maximum number of side-windows to create on (left top right bottom)
+ window-sides-slots '(0 ;; left
+ 1 ;; top
+ 3 ;; right
+ 1 ) ;; bottom
+
+ display-buffer-alist `(
+ ;; Right side
+ (,(rx (or "*Help*" (seq "*helpful " (* anything) "*")))
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . right)
+ (slot . -1)
+ (inhibit-same-window . t))
+ (,(rx "*Async Shell " (* anything) "*")
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . right)
+ (slot . 0)
+ (inhibit-same-window . t))
+ (,(rx "magit-process: " (* anything))
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . right)
+ (slot . 0)
+ (inhibit-same-window . t))
+
+ ;; Top side
+ (,(rx "*Info*")
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . top)
+ (slot . 0))
+ (,(rx "*Man " (* anything) "*")
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . top)
+ (slot . 0))
+
+ ;; Bottom
+ (,(rx "*Flycheck errors*")
+ (display-buffer-reuse-window display-buffer-in-side-window)
+ (side . bottom)
+ (slot . 0))))
#+end_src
-** try: try out different packages
+And a way to toggle those side windows:
+
#+begin_src emacs-lisp
- (use-package try)
+ (bind-key "C-c W" #'window-toggle-side-windows)
#+end_src
-** dumb-jump
-"jump to definition" package, minimal configuration with no stored indexes.
-Uses The Silver Searcher ag, ripgrep rg, or grep to find potential definitions of a function or variable under point.
+** Eldoc
+When editing Elisp and other supported major-modes, Eldoc will display useful information about the construct at point in the echo area.
#+begin_src emacs-lisp
- (use-package dumb-jump)
+ (global-eldoc-mode 1)
#+end_src
-Enable xref backend:
+** Pulse line
+When you switch windows, Emacs can flash the cursor briefly to guide your eyes; I like that.
+Set some options for pulsing:
#+begin_src emacs-lisp
- (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
- (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
+ (setq pulse-iterations 10)
+ (setq pulse-delay 0.05)
#+end_src
-** (disabled) command-log-mode
-Simple real-time logger of commands.
+Define the pulse function:
-#+begin_src emacs-lisp :tangle no
- (use-package command-log-mode)
+#+begin_src emacs-lisp
+ (defun pulse-line (&rest _)
+ "Pulse the current line."
+ (pulse-momentary-highlight-one-line (point)))
#+end_src
-** package-lint
-Linter for the metadata in Emacs Lisp files which are intended to be packages.
+Run it in certain cases: scrolling up/down, recentering, switching windows.
+'dolist' binds 'command' to each value in the list in turn, and runs the body.
+'advice-add' makes the pulse-line function run after 'command'.
#+begin_src emacs-lisp
- (use-package package-lint)
- (use-package flycheck-package)
- (eval-after-load 'flycheck
- '(flycheck-package-setup))
+ (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window))
+ (advice-add command :after #'pulse-line))
#+end_src
-** prism
-Prism changes the color of text depending on their depth. Makes it easier to see where something is at a glance.
+And set the pulse color:
#+begin_src emacs-lisp
-(use-package prism)
+ (custom-set-faces '(pulse-highlight-start-face ((t (:background "CadetBlue2")))))
#+end_src
-** olivetti: distraction-free writing
+
+** Enable all commands
+By default, Emacs disables some commands.
+I want to have these enabled so I don't get a prompt whenever I try to use a disabled command.
+
#+begin_src emacs-lisp
- (use-package olivetti)
+ (setq disabled-command-function nil)
#+end_src
-** nov.el: EPUB support
+** More extensive apropos
#+begin_src emacs-lisp
- (use-package nov)
- (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
+ (setq apropos-do-all t)
#+end_src
-* Interface
-** Start debugger on error
+** Enable recursive minibuffers
#+begin_src emacs-lisp
- ;; (toggle-debug-on-error t)
+ (setq enable-recursive-minibuffers t
+ minibuffer-depth-indicate-mode t)
#+end_src
-
-** Messages
-Hide some messages I don't need, and add a list of recent files.
+** View webp and other formats
+Emacs handles common image formats internally, but for stuff like webp, you need an external converter:
#+begin_src emacs-lisp
- (recentf-mode)
- (setq inhibit-startup-message t
- initial-major-mode #'org-mode
- initial-scratch-message
- (concat "Welcome to Emacs\n\n"
- "Recent:\n"
- (mapconcat
- (lambda (x) (format "- [[%s]]" x)) recentf-list "\n")
- "\n\nELISP Evaluation area:\n#+begin_src emacs-lisp\n\n#+end_src"))
-
+ (setq image-use-external-converter t)
#+end_src
-*** TODO maybe look into fancy-startup-text and fancy-splash-image
-** Appearance
-*** Cursor line
-Highlight the current line:
+You also need imagemagick installed.
+** Repeat mode: easy repeating of commands
#+begin_src emacs-lisp
- (global-hl-line-mode)
+ (repeat-mode 1)
#+end_src
-*** Matching parentheses
-Don't add a delay to show matching parenthesis.
-Must come before show-paren-mode enable.
+
+** Messages
+Hide some messages I don't need, and add a list of recent files.
#+begin_src emacs-lisp
- (setq show-paren-delay 0)
+ (recentf-mode)
+ (setq inhibit-startup-message t
+ initial-major-mode #'org-mode
+ initial-scratch-message
+ (concat "Welcome to Emacs\n\n"
+ "Recent:\n"
+ (mapconcat
+ (lambda (x) (format "- [[%s]]" x)) recentf-list "\n")
+ "\n\nELISP Evaluation area:\n#+begin_src emacs-lisp\n\n#+end_src"))
+
#+end_src
-Show matching parentheses:
+* General packages
+** which-key
+Minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.
-#+begin_src emacs-lisp
- (show-paren-mode t)
+#+BEGIN_SRC emacs-lisp
+ (use-package which-key
+ :config
+ (which-key-mode))
#+end_src
-*** Cursor
-The default box cursor isn't really accurate, because the cursor is actually between letters, not on a letter.
-So, I want a bar instead of a box:
+** counsel + ivy + swiper + prescient
+Better incremental completion and selection narrowing.
+And a bunch more.
+Generally makes for nicer interactivity, like ido mode on steroids.
+Switched to this from Helm, it's more lightweight.
+*** ivy: generic completion mechanism
#+begin_src emacs-lisp
- (setq-default cursor-type '(bar . 4)
- cursor-in-non-selected-windows 'hollow)
-#+end_src
+ (use-package ivy
+ :custom
+ (ivy-use-virtual-buffers t "extend searching to bookmarks")
+ (ivy-height 20 "set height of the ivy window")
+ (ivy-count-format "(%d/%d) " "count format, from the ivy help page")
+ (ivy-display-style 'fancy)
+ (ivy-format-function 'ivy-format-function-line)
+ (ivy-use-selectable-prompt t "to let me select exactly what I'm typing as a candidate")
+
+ :bind (("C-x b" . ivy-switch-buffer)
+ ("C-c v" . ivy-push-view)
+ ("C-c V" . ivy-pop-view)
+
+ ;; accidentally pressing shift-space deletes input, because
+ ;; by default, shift-space is bound to
+ ;; ~ivy-restrict-to-matches~ in the ivy minibuffer.
+ :map ivy-minibuffer-map
+ ("S-SPC" . (lambda () (interactive) (insert ?\s)))
+ ("<backtab>" . ivy-restrict-to-matches))
+ :config
+ (ivy-add-actions
+ 'counsel-dired
+ '(("f" (lambda (dir) (counsel-fzf nil dir)) "Fzf in directory")
+ ("g" (lambda (dir) (counsel-ag nil dir)) "Ag in directory")))
+ (ivy-add-actions
+ 'dired
+ '(("f" (lambda (dir) (ivy-exit-with-action (counsel-fzf nil dir))) "Fzf in directory")
+ ("g" (lambda (dir) (ivy-exit-with-action (counsel-ag nil dir))) "Ag in directory")))
+ (ivy-mode)
-(I use ~setq-default~ here because cursor-type is automatically buffer-local when it's set)
+ (defun edit-script ()
+ "Edit a file in ~/.scripts/"
+ (interactive)
+ (let ((input (ivy--input)))
+ (ivy-quit-and-run (counsel-file-jump nil "~/.scripts/"))))
-*** Line numbers
-Relative line numbers:
+ (defun edit-config ()
+ "Edit a file in ~/.dotfiles/"
+ (interactive)
+ (let ((input (ivy--input)))
+ (ivy-quit-and-run (counsel-file-jump nil "~/.dotfiles/")))))
+#+end_src
+*** counsel: collection of common Emacs commands enhanced using ivy
#+begin_src emacs-lisp
- (setq display-line-numbers-type 'relative)
- (global-display-line-numbers-mode)
+ (use-package counsel
+ :demand
+ :config
+ (counsel-mode)
+ :bind (("M-x" . counsel-M-x)
+ ("C-x C-f" . counsel-find-file)
+ ("M-y" . counsel-yank-pop)
+ ("C-c c" . counsel-compile)
+ ("M-s g" . counsel-ag)
+ ("M-s f" . counsel-fzf)
+ ("C-c b" . counsel-bookmark)
+ ("C-c p" . counsel-recentf)
+ ("C-c o" . counsel-outline)
+ ("C-h f" . counsel-describe-function)
+ ("C-h v" . counsel-describe-variable)
+ ("C-h o" . counsel-describe-symbol)))
+#+end_src
+*** swiper: search enhanced using ivy
+#+begin_src emacs-lisp
+ (use-package swiper
+ :bind (("C-s" . swiper-isearch)
+ ("C-r" . swiper-isearch-backward)))
+#+end_src
+*** prescient: scoring system for M-x
+#+begin_src emacs-lisp
+ (use-package prescient
+ :config (prescient-persist-mode))
+
+ (use-package ivy-prescient
+ :after counsel
+ :custom (ivy-prescient-retain-classic-highlighting t)
+ :config (ivy-prescient-mode))
#+end_src
-Function to hide them:
+*** ivy-posframe: ivy in a popup
+I like having ivy in a popup.
+Problem: posframe does not work if emacs is too old and on macos.
+See here: https://github.com/tumashu/posframe/issues/30
+On Mac, ~brew install --HEAD emacs~ doesn't work either.
+Solution: ~brew tap daviderestivo/emacs-head && brew install emacs-head@28 --with-cocoa~
#+begin_src emacs-lisp
- (defun za/hide-line-numbers ()
- "Hide line numbers"
- (display-line-numbers-mode 0))
+ (if (and (version< emacs-version "28") (equal system-type 'darwin))
+ (message "ivy-posframe won't work properly, run `brew install daviderestivo/emacs-head/emacs-head@28 --with-cocoa`")
+ (use-package ivy-posframe
+ :custom
+ (ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-center)))
+ (ivy-posframe-parameters
+ '((left-fringe . 8)
+ (right-fringe . 8)))
+ (ivy-posframe-border-width 3)
+ :custom-face
+ (ivy-posframe-border ((t (:inherit mode-line-inactive))))
+ :config
+ (ivy-posframe-mode 1)))
#+end_src
-Don't display them in specific modes. For each of the modes in
-'mode-hooks', add a function to hide line numbers when the mode
-activates (which triggers the 'mode'-hook).
+** company: completion mechanism
#+begin_src emacs-lisp
- (let ((mode-hooks '(doc-view-mode-hook vterm-mode-hook mpc-status-mode-hook mpc-tagbrowser-mode-hook)))
- (mapc
- (lambda (mode-name)
- (add-hook mode-name #'za/hide-line-numbers))
- mode-hooks))
+ (use-package company)
#+end_src
-*** Modeline
-I want to show the time and date in the modeline:
+** wgrep: writable grep
#+begin_src emacs-lisp
- (setq display-time-day-and-date t ; also the date
- display-time-default-load-average nil ; don't show load average
- display-time-format "%I:%M%p %e %b (%a)") ; "HR:MIN(AM/PM) day-of-month Month (Day)"
- (display-time-mode 1) ; enable time mode
+ (use-package wgrep)
#+end_src
-
-And to set the modeline format:
+** avy: jump to any position
+This lets me jump to any position in Emacs rather quickly, sometimes it's useful.
+~avy-goto-char-timer~ lets me type a part of the text before avy kicks in.
#+begin_src emacs-lisp
- (setq-default mode-line-format '("%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
- (vc-mode vc-mode)
- " " mode-line-modes mode-line-misc-info mode-line-end-spaces))
+ (use-package avy
+ :bind
+ (("C-:" . avy-goto-char-timer)))
#+end_src
-I want to hide certain modes from the modeline, they're always on:
+** DISABLED calfw: graphical calendar
+Basically provides a way to show the org agenda as a standard GUI calendar app would.
-#+begin_src emacs-lisp
- (use-package diminish
+#+begin_src emacs-lisp :tangle no
+ (use-package calfw
:config
- (let ((modes-to-hide '(ivy-mode counsel-mode which-key-mode hl-todo-mode undo-tree-mode ivy-posframe-mode git-gutter-mode)))
- (mapc #'diminish modes-to-hide))
- (diminish 'view-mode " 👓"))
+ (use-package calfw-org)
+ :custom
+ (cfw:org-overwrite-default-keybinding t)
+ (calendar-week-start-day 1))
#+end_src
-*** Transparent title bar
+
+** vanish: hide parts of the file
#+begin_src emacs-lisp
- (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
+ (use-package vanish
+ :quelpa (vanish :fetcher github :repo "thezeroalpha/vanish.el" :branch "develop")
+ :ensure nil
+ :bind (:map vanish-mode-map
+ ("C-c q h" . vanish-toggle)))
#+end_src
-*** Tab bar
-Only show tab bar if there's more than 1 tab:
-
+** magit
#+begin_src emacs-lisp
- (setq tab-bar-show 1)
+ (use-package magit)
#+end_src
+** vterm
+Emacs has a bunch of built-in terminal emulators.
+And they all suck.
+(OK not really, eshell is alright, but not for interactive terminal programs like newsboat/neomutt)
-** Buffer displaying
-
-So, this is a bit hard to grok. But basically the alist contains a
-regular expression to match a buffer name, then a list of functions to
-use in order for displaying the list, and then options for those functions (each of which is an alist).
+Also use emacsclient inside vterm as an editor, because that'll open documents in the existing Emacs session.
+And I'm not gonna be a heretic and open Vim inside of Emacs.
#+begin_src emacs-lisp
- (setq
- ;; Maximum number of side-windows to create on (left top right bottom)
- window-sides-slots '(0 ;; left
- 1 ;; top
- 3 ;; right
- 1 ) ;; bottom
-
- display-buffer-alist `(
- ;; Right side
- (,(rx (or "*Help*" (seq "*helpful " (* anything) "*")))
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . right)
- (slot . -1)
- (inhibit-same-window . t))
- (,(rx "*Async Shell " (* anything) "*")
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . right)
- (slot . 0)
- (inhibit-same-window . t))
- (,(rx "magit-process: " (* anything))
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . right)
- (slot . 0)
- (inhibit-same-window . t))
+ (use-package vterm
+ :hook
+ (vterm-mode . (lambda () (unless server-process (server-start))))
+ :bind (("C-c t" . switch-to-vterm))
+ :config
+ (defun switch-to-vterm ()
+ "Switch to a running vterm, or start one and switch to it."
+ (interactive)
+ (if (get-buffer vterm-buffer-name)
+ (switch-to-buffer vterm-buffer-name)
+ (vterm))))
+#+end_src
+** sr-speedbar
+Make speed bar show in the current frame.
- ;; Top side
- (,(rx "*Info*")
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . top)
- (slot . 0))
- (,(rx "*Man " (* anything) "*")
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . top)
- (slot . 0))
+#+begin_src emacs-lisp
+ (use-package sr-speedbar
+ :bind (("C-c F" . za/jump-to-speedbar-or-open)
+ :map speedbar-mode-map
+ ("q" . sr-speedbar-close))
+ :custom
+ (sr-speedbar-right-side nil)
- ;; Bottom
- (,(rx "*Flycheck errors*")
- (display-buffer-reuse-window display-buffer-in-side-window)
- (side . bottom)
- (slot . 0))))
+ :config
+ (defun za/jump-to-speedbar-or-open ()
+ "Open a speedbar or jump to it if already open."
+ (interactive)
+ (if (or (not (boundp 'sr-speedbar-exist-p))
+ (not (sr-speedbar-exist-p)))
+ (sr-speedbar-open))
+ (sr-speedbar-select-window)))
#+end_src
-
-And a way to toggle those side windows:
+** expand-region
+Expand the selected region semantically.
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c W") #'window-toggle-side-windows)
+ (use-package expand-region
+ :bind ("C-=" . er/expand-region))
#+end_src
-** Eldoc
-When editing Elisp and other supported major-modes, Eldoc will display useful information about the construct at point in the echo area.
+** flycheck
+Install flycheck:
#+begin_src emacs-lisp
- (global-eldoc-mode 1)
+ (use-package flycheck)
#+end_src
-
-* Editor
-** Overwrite selection on typing
-Normally, when I select something and start typing, Emacs clears the selection, i.e. it deselects and inserts text after the cursor.
-I want to replace the selection.
+** rainbow-mode: visualise hex colors
+'rainbow-mode' lets you visualise hex colors:
#+begin_src emacs-lisp
- (delete-selection-mode t)
+ (use-package rainbow-mode)
#+end_src
-
-** Strip trailing whitespace
-You can show trailing whitespace by setting show-trailing-whitespace to 't'.
-But I want to automatically strip trailing whitespace.
-Luckily there's already a function for that, I just need to call it in a hook:
+** hl-todo: highlight TODO keywords
+I want to highlight TODO keywords in comments:
#+begin_src emacs-lisp
- (add-hook 'before-save-hook #'delete-trailing-whitespace)
+ (use-package hl-todo
+ :custom-face
+ (hl-todo ((t (:inherit hl-todo :underline t))))
+ :custom
+ (hl-todo-keyword-faces '(("TODO" . "#ff7060")
+ ("FIXME" . "#caa000")))
+ :config
+ (global-hl-todo-mode t))
#+end_src
-** Formatting & indentation
-
-Show a tab as 8 spaces:
+** undo-tree
+Sometimes it's better to look at undo history as a tree:
#+begin_src emacs-lisp
- (setq-default tab-width 8)
+ (use-package undo-tree
+ :custom
+ (undo-tree-history-directory-alist
+ (progn (let ((undo-tree-dir (concat user-emacs-directory "undo-tree/")))
+ (unless (file-directory-p undo-tree-dir) (make-directory undo-tree-dir))
+ `(("." . ,undo-tree-dir)))))
+
+ :config
+ (global-undo-tree-mode))
#+end_src
-Never insert tabs with indentation by default:
+*** TODO undo tree dir should be configurable
+** eglot
+A good LSP plugin.
#+begin_src emacs-lisp
- (setq-default indent-tabs-mode nil)
+ (use-package eglot)
#+end_src
-
-Allow switching between the two easily:
+** crdt
+Collaborative editing in Emacs:
#+begin_src emacs-lisp
- (defun indent-tabs ()
- (interactive)
- (setq indent-tabs-mode t))
- (defun indent-spaces ()
- (interactive)
- (setq indent-tabs-mode nil))
+ (use-package crdt)
#+end_src
-
-Indentation for various modes:
+** git gutter
+General git gutter:
#+begin_src emacs-lisp
- (setq-default sh-basic-offset 2
- c-basic-offset 4)
+ (use-package git-gutter
+ :bind (("C-c d n" . git-gutter:next-hunk)
+ ("C-c d p" . git-gutter:previous-hunk))
+ :config
+ (global-git-gutter-mode 1))
#+end_src
-
-** Wrapping
-
-A function to toggle wrapping:
+** keycast
+In case I want to show what keys I'm pressing.
#+begin_src emacs-lisp
- (defvar-local za/wrapping nil "Wrapping changes per buffer.")
-
- (defun za/toggle-wrap (&optional enable)
- "Toggle line wrapping settings. With ENABLE a positive number, enable wrapping. If ENABLE is negative or zero, disable wrapping."
- (interactive "P") ; prefix arg in raw form
+ (use-package keycast)
+#+end_src
+** ace-window: better window switching
+Window switching with ~other-window~ sucks when I have more than 2 windows open. Too much cognitive load.
+This lets me select a window to jump to using a single key, sort of like ~avy~.
- ;; If an argument is provided, prefix or otherwise
- (if enable
- (let ((enable (cond ((numberp enable)
- enable)
- ((booleanp enable)
- (if enable 1 0))
- ((or (listp enable) (string= "-" enable))
- (prefix-numeric-value enable)))))
- ;; If zero or negative, we want to disable wrapping, so pretend it's currently enabled.
- ;; And vice versa.
- (cond ((<= enable 0) (setq za/wrapping t))
- ((> enable 0) (setq za/wrapping nil)))))
+#+begin_src emacs-lisp
+ (use-package ace-window
+ :custom
+ (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) "I prefer using home-row keys instead of numbers")
+ :custom-face
+ ;; I want something a little more contrasty
+ (aw-leading-char-face ((t (:inherit font-lock-keyword-face :height 2.0))))
- (let ((disable-wrapping (lambda ()
- (visual-line-mode -1)
- (toggle-truncate-lines t)))
- (enable-wrapping (lambda ()
- (toggle-truncate-lines -1)
- (visual-line-mode))))
+ :bind ("M-o" . ace-window))
+#+end_src
+** decide-mode for dice rolling
+#+begin_src emacs-lisp
+ (use-package decide
+ :quelpa (decide :repo "lifelike/decide-mode" :fetcher github)
+ :ensure nil)
+#+end_src
- ;; If za/wrapping is not locally set, infer its values from the enabled modes
- (unless (boundp 'za/wrapping)
- (setq za/wrapping (and visual-line-mode
- (not truncate-lines))))
+I want a custom keybinding to easily toggle:
- ;; Toggle wrapping based on current value
- (cond (za/wrapping
- (funcall disable-wrapping)
- (setq za/wrapping nil)
- (message "Wrapping disabled."))
- (t
- (funcall enable-wrapping)
- (setq za/wrapping t)
- (message "Wrapping enabled.")))))
+#+begin_src emacs-lisp
+ (bind-key "C-c q ?" #'decide-mode)
+#+end_src
+** try: try out different packages
+#+begin_src emacs-lisp
+ (use-package try)
+#+end_src
+** dumb-jump
+"jump to definition" package, minimal configuration with no stored indexes.
+Uses The Silver Searcher ag, ripgrep rg, or grep to find potential definitions of a function or variable under point.
+
+#+begin_src emacs-lisp
+ (use-package dumb-jump)
#+end_src
-And a keybinding to toggle wrapping:
+Enable xref backend:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c q w") #'za/toggle-wrap)
+ (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
+ (setq xref-show-definitions-function #'xref-show-definitions-completing-read)
#+end_src
+** DISABLED command-log-mode
+Simple real-time logger of commands.
-I want to wrap text at window boundary for some modes:
+#+begin_src emacs-lisp :tangle no
+ (use-package command-log-mode)
+#+end_src
+** package-lint
+Linter for the metadata in Emacs Lisp files which are intended to be packages.
#+begin_src emacs-lisp
- (defun za/settings-help-mode ()
- "Help mode settings"
- (za/toggle-wrap t))
- (add-hook 'help-mode-hook #'za/settings-help-mode)
+ (use-package package-lint)
+ (use-package flycheck-package)
+ (eval-after-load 'flycheck
+ '(flycheck-package-setup))
#+end_src
+** prism: change color of text depending on depth
+Prism changes the color of text depending on their depth. Makes it easier to see where something is at a glance.
#+begin_src emacs-lisp
- (defun za/settings-helpful-mode ()
- "Helpful mode settings"
- (za/toggle-wrap t)
- (define-key helpful-mode-map (kbd "l") #'za/helpful-previous)
- (define-key helpful-mode-map (kbd "r") #'za/helpful-next))
- (add-hook 'helpful-mode-hook #'za/settings-helpful-mode)
+ (use-package prism)
#+end_src
-
-** Pulse line
-When you switch windows, Emacs can flash the cursor briefly to guide your eyes; I like that.
-Set some options for pulsing:
-
+** olivetti: distraction-free writing
#+begin_src emacs-lisp
- (setq pulse-iterations 10)
- (setq pulse-delay 0.05)
+ (use-package olivetti)
#+end_src
-
-Define the pulse function:
-
+** nov.el: EPUB support
#+begin_src emacs-lisp
- (defun pulse-line (&rest _)
- "Pulse the current line."
- (pulse-momentary-highlight-one-line (point)))
+ (use-package nov)
+ (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
#+end_src
-
-Run it in certain cases: scrolling up/down, recentering, switching windows.
-'dolist' binds 'command' to each value in the list in turn, and runs the body.
-'advice-add' makes the pulse-line function run after 'command'.
+* Mode/language specific packages
+** Org
+*** Installation
+Install Org and require additional components that I use.
#+begin_src emacs-lisp
- (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window))
- (advice-add command :after #'pulse-line))
+ (use-package org
+ :custom
+ (org-return-follows-link t "Easier link following. Actual enter is still possible with ~C-q C-j~.")
+ (org-babel-python-command "python3")
+ (org-confirm-babel-evaluate nil)
+ (org-link-elisp-confirm-function #'y-or-n-p)
+ (org-link-elisp-skip-confirm-regexp "^org-noter$")
+ (org-clock-sound (concat user-emacs-directory "notification.wav"))
+ (org-export-backends '(ascii html icalendar latex md odt org))
+ (org-catch-invisible-edits 'show-and-error
+ "Sometimes when text is folded away, I might accidentally edit text inside of it. This option prevents that. I wanted to do 'smart', but that has a 'fixme' so it might change in the future...Instead, show what's being edited, but don't perform the edit.")
+ (org-src-tab-acts-natively t "a tab in a code block indents the code as it should")
+
+ :bind (("C-c a" . org-agenda)
+ ("C-c n" . org-capture)
+ ("C-c l" . org-store-link)
+ :map org-mode-map
+ ("C-M-<return>" . org-insert-todo-heading)
+ ("C-c M-y" . org-yank-link-url)
+ ("C-c N" . org-noter)
+ ("C-M-i" . completion-at-point)
+ ("C-c q t" . org-timestone-set-org-current-time-effective))
+ :hook ((org-mode . abbrev-mode)
+ (org-mode . za/echo-area-tooltips)
+ (org-mode . org-superstar-mode)
+ (org-mode . org-indent-mode)
+ (org-mode . za/settings-on-org-mode))
+ :config
+ (defun za/settings-on-org-mode ()
+ "Settings on enabling org mode"
+ (za/toggle-wrap t)
+ (setq org-tags-column (- 10 (window-total-width)))
+ ;; Realign tags
+ (org-set-tags-command '(4)))
+
+ (require 'org-tempo)
+ (require 'org-habit)
+ (require 'org-id)
+ (org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((emacs-lisp . t)
+ (R . t)
+ (python . t)
+ (ruby . t)
+ (shell . t)))
+ (use-package inf-ruby)
+ (use-package org-superstar
+ :custom
+ (org-superstar-leading-bullet ?\s))
+
+ ;; Linking to emails via notmuch
+ (use-package ol-notmuch :quelpa)
+
+ ;; Tempo expansions
+ (add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp"))
+ (add-to-list 'org-structure-template-alist '("sb" . "src bibtex"))
+ (add-to-list 'org-structure-template-alist '("ss" . "src sh")))
#+end_src
-And set the pulse color:
-
+*** Agenda & GTD
+**** Agenda mode settings
#+begin_src emacs-lisp
- (custom-set-faces '(pulse-highlight-start-face ((t (:background "CadetBlue2")))))
-#+end_src
+ (use-package org-agenda
+ :ensure org
+ :bind (:map org-agenda-mode-map
+ ("C-c TAB" . za/org-agenda-goto-narrowed-subtree))
+ :custom
+ (org-agenda-files (list za/org-life-main
+ za/org-life-inbox
+ za/org-life-tickler))
+ (org-agenda-text-search-extra-files
+ (directory-files za/org-life-dir t (rx bol (not ?.) (* anything) ".org"))
+ "I want to search all Org files in the life directory")
-** Pager toggle keybinding
-M-x view-mode enables pager behavior.
-I want read-only files to automatically use pager mode:
+ :config
+ (defun za/org-agenda-goto-narrowed-subtree ()
+ "Jump to current agenda item and narrow to its subtree."
+ (interactive)
+ (delete-other-windows)
+ (org-agenda-goto)
+ (org-narrow-to-subtree)
+ (other-window 1)))
-#+begin_src emacs-lisp
- (setq view-read-only t)
#+end_src
-** Mail mode for neomutt
-When editing a message from neomutt, I want to use mail mode.
-Even though I won't be sending the email from there, I like the syntax highlighting :)
+
+Fix tag display by dynamically calculating the column.
#+begin_src emacs-lisp
- (add-to-list 'auto-mode-alist '("/neomutt-" . mail-mode))
+ (defun za/settings-org-agenda-mode ()
+ "My settings for org agenda mode"
+ (setq org-agenda-tags-column (- 10 (window-total-width))))
+ (add-hook 'org-agenda-mode-hook #'za/settings-org-agenda-mode)
#+end_src
-** Zap up to char
-It's more useful for me to be able to delete up to a character instead of to and including a character:
+
+**** Opening files
+Convenience functions to make opening the main file faster:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "M-z") 'zap-up-to-char)
+ (defun gtd () "GTD: main file" (interactive) (find-file za/org-life-main))
+ (defun gtd-inbox () "GTD: inbox" (interactive) (find-file za/org-life-inbox))
+ (defun gtd-archive () "GTD: archive" (interactive) (find-file za/org-life-archive))
+ (defun gtd-someday () "GTD: someday" (interactive) (find-file za/org-life-someday))
+ (defun gtd-tickler () "GTD: tickler" (interactive) (find-file za/org-life-tickler))
#+end_src
-** Expansion/completion
-Use hippie expand instead of dabbrev-expand:
+
+Bind keys to those functions:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "M-/") 'hippie-expand)
+ (bind-keys :prefix "M-g t"
+ :prefix-map za/gtd-files-map
+ :prefix-docstring "Visit GTD file"
+ ("i" . gtd-inbox)
+ ("g" . gtd)
+ ("a" . gtd-archive)
+ ("s" . gtd-someday)
+ ("t" . gtd-tickler))
#+end_src
-** Prefer newer file loading
+*** Refiling & archiving
#+begin_src emacs-lisp
- (setq load-prefer-newer t)
-#+end_src
+ (use-package org-refile
+ :ensure org
+ :custom
+ (org-refile-targets `((,za/org-life-main :maxlevel . 3)
+ (,za/org-life-someday :level . 1)
+ (,za/org-life-tickler :maxlevel . 2))
+ "Where I want to be able to move subtrees (doesn't include inbox because I never refile to that, and the archive has its own keybining)")
+ (org-archive-location (concat za/org-life-archive "::datetree/")
+ "I want to archive to a specific file, in a date tree")
+ (org-refile-use-outline-path 'file
+ "Include the destination file as an element in the path to a heading, and to use the full paths as completion targets rather than just the heading text itself")
+ (org-outline-path-complete-in-steps nil
+ "Tell Org that I don’t want to complete in steps; I want Org to generate all of the possible completions and present them at once (necessary for Helm/Ivy)")
+ (org-refile-allow-creating-parent-nodes 'confirm
+ "Allow me to tack new heading names onto the end of my outline path, and if I am asking to create new ones, make me confirm it"))
+#+end_src
+
+*** Quick capture
+Quick capture lets me send something to my inbox very quickly, without thinking about where it should go.
+The inbox is processed later.
-** Automatically find tags file
-When opening a file in a git repo, try to discover the etags file:
+Templates for quick capture:
#+begin_src emacs-lisp
- (defun current-tags-file ()
- "Get current tags file"
- (let* ((tagspath ".git/etags")
- (git-root (locate-dominating-file (buffer-file-name) tagspath)))
- (if git-root
- (expand-file-name tagspath git-root))))
+ (use-package org-capture
+ :ensure org
+ :custom
+ (org-capture-templates `(("t" "Todo [inbox]" entry
+ (file ,za/org-life-inbox)
+ "* TODO %i%?")
- (setq default-tags-table-function #'current-tags-file)
+ ("s" "Save for read/watch/listen" entry
+ (file+headline ,za/org-life-tickler "Read/watch/listen")
+ "* TODO %?[[%^{link}][%^{description}]] %^G"))))
#+end_src
-There's probably a better way to write this. I need to ask Reddit for feedback at some point.
+*** Todo & custom agenda views
+Todo keywords based on the GTD system (pipe separates incomplete from complete).
+Apart from the logging-on-done configured [[*Logging][below]], I also want to log a note & timestamp when I start waiting on something.
+In ~org-todo-keywords~, ~@~ means note+timestamp, ~!~ means timestamp, ~@/!~ means note+timestamp on state entry and timestamp on leave.
-** Semantic mode
-Disabled for now, don't use it much.
+#+begin_src emacs-lisp
+ (custom-set-variables '(org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w@)" "STARTED(s)" "|" "DONE(d)" "CANCELLED(c)")))
+ '(org-todo-keyword-faces '(("TODO" . org-todo)
+ ("NEXT" . org-todo)
+ ("WAITING" . org-todo)
+ ("STARTED" . org-todo)
+ ("DONE" . org-done)
+ ("CANCELLED" . org-done))))
+#+end_src
-Set default submodes:
+I decided that projects will not be TODO items, but their progress will be tracked with a progress cookie ([x/y]). This function converts an item to a project: it adds a PROJECT tag, sets the progress indicator to count all checkboxes in sub-items (only TODO items), and removes any existing TODO keywords. Finally, PROJECT tags shouldn't be inherited (i.e. subtasks shouldn't be marked as projects).
-#+begin_src emacs-lisp :tangle no
- (setq semantic-default-submodes '(global-semantic-idle-scheduler-mode ; reparse buffer when idle
- global-semanticdb-minor-mode ; maintain database
- global-semantic-idle-summary-mode)) ; show information (e.g. types) about tag at point
- global-semantic-stickyfunc-mode)) ; show current func in header line
+#+begin_src emacs-lisp
+ (defun za/mark-as-project ()
+ "This function makes sure that the current heading has
+ (1) the tag PROJECT
+ (2) the property COOKIE_DATA set to \"todo recursive\"
+ (3) a leading progress indicator"
+ (interactive)
+ (org-set-property "TODO" "")
+ (org-toggle-tag "PROJECT" 'on)
+ (org-set-property "COOKIE_DATA" "todo recursive")
+ (org-back-to-heading t)
+ (forward-whitespace 1)
+ (insert "[/] ")
+ (org-update-statistics-cookies nil))
#+end_src
-Add some keybindings:
+Only the top-level project headlines should be tagged as projects, so disable inheritance of that tag:
-#+begin_src emacs-lisp :tangle no
- (with-eval-after-load 'semantic
- (define-key semantic-mode-map (kbd "C-c , .") #'semantic-ia-show-summary))
+#+begin_src emacs-lisp
+ (custom-set-variables '(org-tags-exclude-from-inheritance '("PROJECT")))
#+end_src
-SemanticDB is written into ~/.emacs.d/semanticdb/.
-
-Enable semantic mode for major modes:
+Define a function to skip items if they're part of a project (i.e. one of their parents has a "PROJECT" tag).
+The problem is, the "PROJECT" tag isn't inherited. So, we temporarily disable excluding from inheritance, just for the ~org-get-tags~ call. Then check if "PROJECT" is one of the tags.
-#+begin_src emacs-lisp :tangle no
- (defun za/settings-c-mode ()
- "C mode settings"
- (semantic-mode 1))
-#+end_src
+#+begin_src emacs-lisp
+ (defun za/skip-if-in-project ()
+ "Skip items that are part of a project"
+ (let ((subtree-end (save-excursion (org-end-of-subtree t)))
+ (item-tags (let ((org-tags-exclude-from-inheritance nil)) (org-get-tags))))
+ (if (member "PROJECT" item-tags)
+ subtree-end
+ nil)))
-#+begin_src emacs-lisp :tangle no
- (let ((mode-hooks '(c-mode-common-hook)))
- (dolist (mode-name mode-hooks)
- (add-hook mode-name #'za/settings-c-mode)))
#+end_src
-** Forward-word and forward-to-word
-Change M-f to stop at the start of the word:
+Also, define a function to skip tasks (trees) that are not habits (i.e. don't have the STYLE property ~habit~):
#+begin_src emacs-lisp
- (za/global-set-key (kbd "M-f") 'forward-to-word)
+ (defun za/skip-unless-habit ()
+ "Skip trees that are not habits"
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (string= (org-entry-get nil "STYLE") "habit")
+ nil
+ subtree-end)))
#+end_src
-Bind ESC M-f to the old functionality of M-f (stop at end of word)
+And one to skip tasks that /are/ habits:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "ESC M-f") 'forward-word)
+ (defun za/skip-if-habit ()
+ "Skip trees that are not habits"
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (string= (org-entry-get nil "STYLE") "habit")
+ subtree-end
+ nil)))
#+end_src
-** Rectangle insert string
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-x r I") 'string-insert-rectangle)
- (za/global-set-key (kbd "C-x r R") 'replace-rectangle)
-#+end_src
-** End sentences with one space
-Emacs uses the rather old-fashioned convention of treating a period followed by double spaces as end of sentence. However, it is more common these days to end sentences with a period followed by a single space.
-Let a period followed by a single space be treated as end of sentence:
+And another function, to skip tasks that are blocked:
#+begin_src emacs-lisp
- (setq sentence-end-double-space nil)
+ (defun za/skip-if-blocked ()
+ "Skip trees that are blocked by previous tasks"
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (org-entry-blocked-p)
+ subtree-end
+ nil)))
#+end_src
-** Make region readonly or writable
-#+begin_src emacs-lisp
- (defun za/set-region-read-only (begin end)
- "Sets the read-only text property on the marked region.
- Use `set-region-writeable' to remove this property."
- ;; See https://stackoverflow.com/questions/7410125
- (interactive "r")
- (with-silent-modifications
- (put-text-property begin end 'read-only t)))
- (defun za/set-region-writeable (begin end)
- "Removes the read-only text property from the marked region.
- Use `set-region-read-only' to set this property."
- ;; See https://stackoverflow.com/questions/7410125
- (interactive "r")
- (with-silent-modifications
- (remove-text-properties begin end '(read-only t))))
-#+end_src
-** Open line like in Vim
-I prefer to open-line the way o/O works in Vim:
+Create custom agenda view based on those keywords.
+Agenda views are made up of blocks, appearing in the order that you declare them.
+The first two strings are what shows up in the agenda dispatcher (the key to press and the description).
#+begin_src emacs-lisp
- ;; Autoindent open-*-lines
- (defvar za/open-line-newline-and-indent t
- "Modify the behavior of the open-*-line functions to cause them to autoindent.")
+ (setq org-agenda-custom-commands
+ '(("n" "Next actions"
+ todo "NEXT" ((org-agenda-overriding-header "Next actions:")
+ (org-agenda-sorting-strategy '(priority-down alpha-up))))
+ ("W" "Waiting"
+ ((todo "WAITING" ((org-agenda-overriding-header "Waiting:")))))
+ ("S" . "Saved for later...")
+ ("Sw" "Saved to watch"
+ ((tags-todo "WATCH" ((org-agenda-overriding-header "To watch:")))))
+ ("Sr" "Saved to read"
+ ((tags-todo "READ" ((org-agenda-overriding-header "To read:")))))
+ ("Sl" "Saved to listen"
+ ((tags-todo "LISTEN" ((org-agenda-overriding-header "To listen:")))))
- (defun za/open-line (prefix)
- "Open line like `o`/`O` in Vim. Negative prefix for line above, positive for below."
- (interactive "p")
- (cond ((< prefix 0)
- (beginning-of-line)
- (open-line (abs prefix)))
- (t
- (end-of-line)
- (open-line prefix)
- (forward-line 1)))
- (when za/open-line-newline-and-indent
- (indent-according-to-mode)))
+ ("a" . "Agenda with schedule only...")
+ ("aw" "This week"
+ ((agenda "" ((org-agenda-span 'week)))))
+ ("ad" "Today"
+ ((agenda "" ((org-agenda-span 'day)))))
+ ("at" "Tomorrow"
+ ((agenda "" ((org-agenda-span 'day)
+ (org-agenda-start-day "+1d")))))
+
+ ("w" "Week Agenda + Next Actions"
+ ((agenda "" ((org-agenda-overriding-header "Week agenda:")))
+ (todo "NEXT" ((org-agenda-overriding-header "Next actions:")))))
+
+ ("o" "Month agenda"
+ ((agenda "" ((org-agenda-overriding-header "Month agenda:")
+ (org-agenda-span 'month)))))
+
+ ("d" "Day Agenda + Next Actions + Habits"
+ ((agenda "" ((org-agenda-overriding-header "Day:")
+ (org-agenda-span 'day)
+ (org-habit-show-habits nil)))
+ (todo "NEXT" ((org-agenda-overriding-header "Next actions:")))
+ (agenda "" ((org-agenda-overriding-header "Habits:")
+ (org-agenda-span 'day)
+ (org-agenda-use-time-grid nil)
+ (org-agenda-skip-function 'za/skip-unless-habit)
+ (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil)
+ (org-habit-show-all-today t)))
+ (todo "WAITING" ((org-agenda-overriding-header "Waiting:")))))
+
+ ("k" "Kanban view"
+ ((todo "DONE" ((org-agenda-overriding-header "Done:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
+ (todo "STARTED" ((org-agenda-overriding-header "In progress:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))
+ (todo "NEXT" ((org-agenda-overriding-header "To do:") (org-agenda-sorting-strategy '(deadline-up priority-down alpha-up))))))
- (defun za/open-line-keep-point (prefix)
- "Open line like `o`/`O` in Vim but don't move point. Negative prefix for line above, positive for below."
- (interactive "p")
- (save-mark-and-excursion (za/open-line prefix)))
-#+end_src
+ ("p" "Projects"
+ ((tags "PROJECT" ((org-agenda-overriding-header "Projects:")
+ (org-agenda-prefix-format '((tags . " %i %-22(let ((deadline (org-entry-get nil \"DEADLINE\"))) (if deadline deadline \"\"))")))
+ (org-agenda-sorting-strategy '((tags deadline-up alpha-down)))))))
-And keybindings:
+ ("f" "Finished tasks that aren't in a project"
+ ((tags "TODO=\"DONE\"|TODO=\"CANCELLED\"" ((org-agenda-overriding-header "Finished tasks:")
+ (org-agenda-skip-function 'za/skip-if-in-project)))))
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-o") #'za/open-line)
- (za/global-set-key (kbd "C-M-o") #'za/open-line-keep-point)
+ ;; Useful thread for opening calfw: https://github.com/kiwanami/emacs-calfw/issues/18
+ ("c" "Calendar view" (lambda (&rest _)
+ (interactive)
+ (let ((org-agenda-skip-function 'za/skip-if-habit))
+ (cfw:open-org-calendar))))))
#+end_src
-** Repeat mode: easy repeating of commans
+
+In calfw, I don't want to show habits:
+
#+begin_src emacs-lisp
- (repeat-mode 1)
+ (add-hook 'cfw:calendar-mode-hook (setq-local org-agenda-skip-function 'za/skip-if-habit))
#+end_src
-** Toggle auto-revert-mode
-Sometimes I want to toggle auto reverting (or autoread) of buffer:
+
+*** Logging for tasks
+I want to log into the LOGBOOK drawer (useful when I want to take quick notes):
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c q a") #'auto-revert-mode)
+ (setq org-log-into-drawer "LOGBOOK")
#+end_src
-** Unfill region/paragraph
-Taken from here: https://www.emacswiki.org/emacs/UnfillParagraph
-#+begin_src emacs-lisp
- (defun za/unfill-paragraph (&optional region)
- "Takes a multi-line paragraph and makes it into a single line of text."
- (interactive (progn (barf-if-buffer-read-only) '(t)))
- (let ((fill-column (point-max))
- ;; This would override `fill-column' if it's an integer.
- (emacs-lisp-docstring-fill-column t))
- (fill-paragraph nil region)))
+I also want to log when I finish a task (useful for archiving).
+Furthermore, when I'm done, I want to add a note (any important
+workarounds/tips). And when I reschedule, I want to know the reason.
+I can disable logging on state change for a specific task by adding ~:LOGGING: nil~ to the ~:PROPERTIES:~ drawer.
- (za/global-set-key (kbd "M-Q") #'za/unfill-paragraph)
+#+begin_src emacs-lisp
+ (setq org-log-done 'note
+ org-log-reschedule 'note)
#+end_src
-** Insert macro as Lisp
-From here: https://www.masteringemacs.org/article/keyboard-macros-are-misunderstood
+
+I want to hide drawers on startup. This variable has options:
+- 'overview': Top-level headlines only.
+- 'content': All headlines.
+- 'showall': No folding on any entry.
+- 'show2levels: Headline levels 1-2.
+- 'show3levels: Headline levels 1-3.
+- 'show4levels: Headline levels 1-4.
+- 'show5levels: Headline levels 1-5.
+- 'showeverything: Show even drawer contents.
#+begin_src emacs-lisp
- (require 'kmacro)
- (defalias 'kmacro-insert-macro 'insert-kbd-macro)
- ;; Add advice to ignore errors on `kmacro-keyboard-macro-p`, it was messing up because of some entry in `obarray`
- (advice-add #'kmacro-keyboard-macro-p :around (lambda (fun sym) "Ignore errors." (ignore-errors (funcall fun sym))))
- (define-key kmacro-keymap (kbd "I") #'kmacro-insert-macro)
+ (setq org-startup-folded 'content)
#+end_src
-** Show local help at point when idling
+
+*** Task ordering
+Some tasks should be ordered, i.e. they should be done in steps.
+Those have the ~:ORDERED: t~ setting in ~:PROPERTIES:~, and it should be enforced:
+
#+begin_src emacs-lisp
- (defun za/echo-area-tooltips ()
- "Show tooltips in the echo area automatically for current buffer."
- (setq-local help-at-pt-display-when-idle t
- help-at-pt-timer-delay 0)
- (help-at-pt-cancel-timer)
- (help-at-pt-set-timer))
+ (setq org-enforce-todo-dependencies t)
#+end_src
-* Markdown
-Markdown mode settings.
+Furthermore, tasks that are ordered and can't be done yet because of previous steps should be dimmed in the agenda:
#+begin_src emacs-lisp
- (defun za/settings-markdown-mode ()
- "My settings for markdown mode"
- (auto-fill-mode 0)
- (flyspell-mode 1)
- (za/toggle-wrap t))
-
- (add-hook 'markdown-mode-hook #'za-settings-markdown-mode)
+ (setq org-agenda-dim-blocked-tasks t)
#+end_src
-* Bib(la)tex
+I might also want to set ~org-enforce-todo-checkbox-dependencies~, but not convinced on that one yet.
+
+*** Time tracking & effort
+Time tracking should be done in its own drawer:
+
#+begin_src emacs-lisp
- (defun za/settings-bibtex-mode ()
- "My settings for bibtex mode"
- (bibtex-set-dialect "biblatex"))
+ (setq org-clock-into-drawer "CLOCK")
#+end_src
+And to customize how clock tables work:
+
#+begin_src emacs-lisp
- (add-hook 'bibtex-mode-hook #'za/settings-bibtex-mode)
+ (setq org-clocktable-defaults '(:lang "en" :scope agenda-with-archives :wstart 1 :mstart 1 :compact t :maxlevel nil))
+ (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel nil))
#+end_src
-* Python
-In Python, I want to enable flycheck and semantic mode:
+I want to set effort in hours:minutes:
#+begin_src emacs-lisp
- (add-hook 'python-mode-hook #'flycheck-mode)
- ;;(add-hook 'python-mode-hook #'semantic-mode)
+ (add-to-list 'org-global-properties '("Effort_ALL" . "0:05 0:10 0:15 0:20 0:30 0:45 1:00 1:30 2:00 4:00 6:00 8:00"))
#+end_src
-* Misc settings
-** Enable all commands
-By default, Emacs disables some commands.
-I want to have these enabled so I don't get a prompt whenever I try to use a disabled command.
+I want column view to look like this:
+
+| To do | Task | Tags | Sum of time elapsed | Sum of time estimated (effort) |
+|--------------+-----------+------+---------------------+--------------------------------|
+| todo keyword | task name | tags | sum of clock | sum of estimated time |
+| ... | ... | ... | ... | ... |
#+begin_src emacs-lisp
- (setq disabled-command-function nil)
+ (setq org-columns-default-format "%7TODO (To Do) %32ITEM(Task) %TAGS(Tags) %11CLOCKSUM_T(Clock) %10Difficulty(Difficulty) %8Effort(Effort){:}")
#+end_src
-** More extensive apropos
+Fix column alignment in agenda.
+
#+begin_src emacs-lisp
- (setq apropos-do-all t)
+ (set-face-attribute 'org-column nil
+ :height (face-attribute 'default :height)
+ :family (face-attribute 'default :family))
+ (set-face-attribute 'org-agenda-date-today nil
+ :height (face-attribute 'default :height))
#+end_src
-** Easily edit my config
-Bind a keyboard shortcut to open my config.
-The "(interactive)" means that it can be called from a keybinding or from M-x.
+*** Calculate time since timestamp
#+begin_src emacs-lisp
- (defun za/edit-config-org ()
- "Edit my config.org file"
+ (defun za/org-time-since ()
+ "Print the amount of time between the timestamp at point and the current date and time."
(interactive)
- (find-file (expand-file-name "config.org" user-emacs-directory)))
+ (unless (org-at-timestamp-p 'lax)
+ (user-error "Not at timestamp"))
+
+ (when (org-at-timestamp-p 'lax)
+ (let ((timestamp (match-string 0)))
+ (with-temp-buffer
+ (insert timestamp
+ "--"
+ (org-time-stamp '(16)))
+ (org-evaluate-time-range)))))
#+end_src
+*** Priorities: how important something is
+I usually have a lot of 'next' actions, so I prefer 4 priority levels instead of the default 3: A (urgent, ASAP), B (important), C (not super important), D (do in free time):
+
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c E") 'za/edit-config-org)
+ (setq org-priority-highest ?A
+ org-priority-lowest ?D
+ org-priority-default ?C)
#+end_src
-(describe-key (kbd "C-c E"))
-** Fast access to view-mode (pager)
-I want to bind view-mode to a key for easy access:
+
+Faces for priorities in agenda:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c q r") 'view-mode)
+ (setq org-priority-faces `((?A . (:foreground ,(face-foreground 'error)))
+ (?B . (:foreground ,(face-foreground 'org-todo)))
+ (?C . (:foreground ,(face-foreground 'font-lock-constant-face) :weight semi-light))
+ (?D . (:foreground ,(face-foreground 'font-lock-string-face) :slant italic :weight light))))
#+end_src
-** Kill this buffer
-I like to be able to kill a buffer instantly:
-
+*** Energy requirement: how difficult something is
+#+begin_src emacs-lisp
+ (add-to-list 'org-global-properties '("Difficulty_ALL" . "low medium high"))
+#+end_src
+*** Custom functions
+**** Get number of headlines in a file
#+begin_src emacs-lisp
- (za/global-set-key (kbd "s-<backspace>") 'kill-current-buffer)
+ (defun za/org-count-headlines-in-file (level filename)
+ "Count number of level LEVEL headlines in FILENAME. If LEVEL is 0, count all."
+ (let ((headline-str (cond ((zerop level) "^\*+")
+ (t (format "^%s " (apply 'concat (make-list level "\\*")))))))
+ (save-mark-and-excursion
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (count-matches headline-str (point-min) (point-max))))))
#+end_src
-** Delete this file (and kill the buffer)
+**** Yank URL
#+begin_src emacs-lisp
- (defun za/delete-this-file ()
- "Kill the current buffer and delete its associated file."
+ (defun org-yank-link-url ()
(interactive)
- (let ((fname (buffer-file-name))
- (buf (current-buffer)))
- (unless (and fname (file-exists-p fname))
- (user-error (format "Buffer has no associated file.")))
+ (kill-new (org-element-property :raw-link (org-element-context)))
+ (message "Link copied to clipboard"))
+#+end_src
+*** org publishing
+I decided, after trying many different things, to settle on org-publish.
- (unless (y-or-n-p (format "Really delete %s and its buffer?" fname))
- (user-error "User cancelled."))
+#+begin_src emacs-lisp
+ (defconst za/org-roam-top-name "Top" "The name of the top-level Org-roam node.")
+ (defun za/org-roam-sitemap-function (title list)
+ "Customized function to generate sitemap for org-roam, almost the same as `org-publish-sitemap-default`."
+ (concat "#+TITLE: " title "\n\n"
+ (format "[[file:%s][%s]]\n\n"
+ (file-name-nondirectory (org-roam-node-file
+ (org-roam-node-from-title-or-alias za/org-roam-top-name)))
+ "Click here for entrypoint.")))
+ ;; (org-list-to-org list))) <-- this is taken care of by Zola
- (delete-file fname 'trash-if-enabled)
- (kill-buffer buf)
- (message "Deleted %s and killed its buffer." fname)))
+#+end_src
+
+To make this work with Zola, I need to export Github-flavored markdown (fenced code blocks with language):
+
+#+begin_src emacs-lisp
+ (require 'ox-publish)
+ (require 'ox-md)
- (za/global-set-key (kbd "C-c s-<backspace>") #'za/delete-this-file)
+ (use-package ox-gfm
+ :init
+ (with-eval-after-load 'org (require 'ox-gfm)))
#+end_src
-** Toggle fullscreen
-I'll use the keybinding that's standard on macOS:
+First difficulty: Zola needs front matter with ~+++...+++~.
+The default Markdown backend doesn't provide that, so need to customize it by advising the default ~org-md-template~.
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-s-f") #'toggle-frame-fullscreen)
+ (defun za/org-md-template-zola (contents info)
+ "Markdown template compatible with Zola (generates the necessary front matter from CONTENTS and INFO)."
+ (let ((title (org-md-plain-text (org-element-interpret-data (plist-get info :title)) info)))
+ (concat "+++\n"
+ (format "title = \"%s\"" (string-replace "\"" "'" title))
+ "\n+++\n"
+ (format "# %s\n" title)
+ contents)))
#+end_src
-** Enable recursive minibuffers
+Second difficulty: links need to be reformatted and changed for static data (like images).
+This function filters the return value of ~org-md-link~.
+
#+begin_src emacs-lisp
- (setq enable-recursive-minibuffers t
- minibuffer-depth-indicate-mode t)
+ (defun za/org-md-link-zola (linkstr)
+ "A filter function for the return value of
+ `org-md-link` (LINKSTR) to generate a link compatible with Zola."
+ (cond ((string-match-p (rx ".md") linkstr)
+ (string-replace "](" "](@/org-roam/" linkstr))
+ ((string-match-p (rx "](/") linkstr)
+ (replace-regexp-in-string (rx "](/" (* any) "/org-roam/data") "](/org-roam-data" linkstr))
+ (t linkstr)))
#+end_src
-** Sexp manipulation
-When I write lisp, sometimes I want to switch two sexps (e.g. ~(one) (two)~ → ~(two) (one)~), so a key binding is nice for that:
+And here's the custom publish function that adds/removes the necessary advice:
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-S-t") #'transpose-sexps)
+ (defun za/org-gfm-publish-to-gfm-zola (plist filename pub-dir)
+ "Run `org-gfm-publish-to-gfm`, advising the necessary
+ functions to generate Zola-compatible markdown."
+ (let ((advice '((org-md-template :override za/org-md-template-zola)
+ (org-md-link :filter-return za/org-md-link-zola)
+ (org-gfm-table :override org-md--convert-to-html)))) ; Zola uses CommonMark, so doesn't support Markdown tables
+
+ (dolist (orig-type-new advice) (apply #'advice-add orig-type-new))
+ (org-gfm-publish-to-gfm plist filename pub-dir)
+ (dolist (orig-type-new advice)
+ (advice-remove (nth 0 orig-type-new)
+ (nth 2 orig-type-new)))))
#+end_src
-Also, to raise a sexp (e.g. ~(one (two))~ → ~(two)~):
+Finally, the list of things we can publish with their respective publishin functions:
+
+#+begin_src emacs-lisp
+ (setq org-publish-project-alist
+ `(
+ ("org-notes"
+ :base-directory ,za/org-roam-dir
+ :base-extension "org"
+ :publishing-directory ,(concat za/my-website-dir "content/org-roam/")
+ :publishing-function za/org-gfm-publish-to-gfm-zola
+ :recursive t
+ :sitemap-filename "_index.md"
+ :sitemap-title "Org Roam"
+ :sitemap-function za/org-roam-sitemap-function
+ :auto-sitemap t)
+
+ ("org-notes-data"
+ :base-directory ,(concat za/org-roam-dir "/data")
+ :base-extension any
+ :publishing-directory ,(concat za/my-website-dir "static/org-roam-data/")
+ :recursive t
+ :publishing-function org-publish-attachment)
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-S-u") #'raise-sexp)
+ ("org-roam" :components ("org-notes" "org-notes-data"))
+ ))
#+end_src
-** Dedicated windows
-Sometimes I want to avoid Emacs overriding a window's contents.
-So I create a keybinding to toggle dedicated on a window:
+And a function to rsync to my VPS:
#+begin_src emacs-lisp
- (defun za/toggle-window-dedicated-p ()
- "Toggle set-window-dedicated-p on current window"
+ (defun za/publish-upload-to-website ()
+ "Upload my website to my VPS"
(interactive)
- (cond ((window-dedicated-p (selected-window))
- (set-window-dedicated-p (selected-window) nil)
- (message "Window no longer dedicated"))
- (t
- (set-window-dedicated-p (selected-window) t)
- (message "Window marked as dedicated"))))
-
- (za/global-set-key (kbd "C-x 9") #'za/toggle-window-dedicated-p)
-
-#+end_src
-
-** Rebuild org cache
-#+begin_src emacs-lisp
-(defun za/force-org-rebuild-cache ()
- "Rebuild the `org-mode' and `org-roam' cache."
- (interactive)
- (org-id-update-id-locations)
- ;; Note: you may need `org-roam-db-clear-all'
- ;; followed by `org-roam-db-sync'
- (org-roam-db-sync)
- (org-roam-update-org-id-locations))
+ (async-shell-command (format "cd %s && zola build && yes|publish" za/my-website-dir) "*Async Shell publish*"))
#+end_src
-
-** Info manual functions
-For some reason, these things don't show up in the index:
-
+*** Rebuild org cache
#+begin_src emacs-lisp
- (defun elisp-info (&optional node)
- "Read documentation for Elisp in the info system.
- With optional NODE, go directly to that node."
+ (defun za/force-org-rebuild-cache ()
+ "Rebuild the `org-mode' and `org-roam' cache."
(interactive)
- (info (format "(elisp)%s" (or node ""))))
+ (org-id-update-id-locations)
+ ;; Note: you may need `org-roam-db-clear-all'
+ ;; followed by `org-roam-db-sync'
+ (org-roam-db-sync)
+ (org-roam-update-org-id-locations))
#+end_src
-Though I can also just use ~info-display-manual~.
-
-** View webp and other formats
-Emacs handles common image formats internally, but for stuff like webp, you need an external converter:
+*** org-caldav
+This lets me sync my Org agenda to my CalDAV server.
+The main reason is because Orgzly doesn't have a calendar view and can't (yet) search for events on a specific day, so if someone asks "are you free on that day", it's a bit hard for me to answer if I don't have my computer with me.
+This way, I can just check my calendar.
#+begin_src emacs-lisp
- (setq image-use-external-converter t)
+ (use-package org-caldav
+ :init
+ (defconst za/org-life-calendar-inbox (concat za/org-life-dir "calendar-inbox.org"))
+ :custom
+ (org-caldav-url za/caldav-url)
+ (org-caldav-calendar-id za/caldav-org-calendar-id)
+ (org-caldav-inbox za/org-life-calendar-inbox)
+ (org-caldav-files (cons (car (split-string org-archive-location "::")) org-agenda-files))
+ (org-icalendar-include-todo 'all)
+ (org-icalendar-use-deadline '(event-if-todo event-if-not-todo todo-due))
+ (org-icalendar-use-scheduled '(todo-start event-if-todo event-if-not-todo))
+ (org-caldav-exclude-tags '("HABIT")
+ "I don't want to export habits, because those will just clutter up my calendar. The calendar is supposed to be for one-off stuff, or rarely repeating stuff. Yes, I have to manually add the HABIT tag to every habit. Perhaps nicer would be to exclude based on the property ~:STYLE: habit~, but I haven't figured that one out yet."))
#+end_src
-You also need imagemagick installed.
-
-** Rotate windows horizontal ↔ vertical
-#+begin_src emacs-lisp
- (defun za/rotate-windows ()
- (interactive)
- (if (= (count-windows) 2)
- (let* ((this-win-buffer (window-buffer))
- (next-win-buffer (window-buffer (next-window)))
- (this-win-edges (window-edges (selected-window)))
- (next-win-edges (window-edges (next-window)))
- (this-win-2nd (not (and (<= (car this-win-edges)
- (car next-win-edges))
- (<= (cadr this-win-edges)
- (cadr next-win-edges)))))
- (splitter
- (if (= (car this-win-edges)
- (car (window-edges (next-window))))
- 'split-window-horizontally
- 'split-window-vertically)))
- (delete-other-windows)
- (let ((first-win (selected-window)))
- (funcall splitter)
- (if this-win-2nd (other-window 1))
- (set-window-buffer (selected-window) this-win-buffer)
- (set-window-buffer (next-window) next-win-buffer)
- (select-window first-win)
- (if this-win-2nd (other-window 1))))))
-#+end_src
+Maybe check [[https://old.reddit.com/r/orgmode/comments/8rl8ep/making_orgcaldav_useable/e0sb5j0/][this]] for a way to sync on save.
+*** org-contrib
#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-x 7") #'za/rotate-windows)
+ (use-package org-contrib
+ :config
+ (require 'org-checklist))
#+end_src
-
-* Language-specific
+*** org-ref
#+begin_src emacs-lisp
- (let ((modes '((emacs-lisp-mode-hook . (flycheck-mode
- rainbow-mode
- outline-minor-mode
- company-mode))
- (org-mode-hook . (abbrev-mode
- za/echo-area-tooltips)))))
- (dolist (major-minorlist modes)
- (let ((major (car major-minorlist))
- (minor-modes (cdr major-minorlist)))
- (dolist (minor minor-modes) (add-hook major minor)))))
+ (use-package org-ref)
#+end_src
-
-* Sound support
-On macOS, you can use afplay:
-
+*** org-roam
#+begin_src emacs-lisp
- (defun za/play-sound-file-macos (file &optional volume device)
- "Play sound using `afplay` on macOS"
- (unless (file-readable-p file)
- (user-error "File %s not readable." file))
+ (use-package org-roam
+ :custom
+ (org-roam-directory za/org-roam-dir)
+ (org-roam-completion-everywhere t)
- ;; the `apply` is required here because I need to build a list of arguments
- (apply 'start-process `("afplay" nil
- "afplay"
- ,@(if volume (list "-v" (int-to-string volume)))
- ,file)))
+ :config
+ (org-roam-setup)
+ (bind-keys :prefix "C-c w"
+ :prefix-map za/org-roam-map
+ :prefix-docstring "Org roam"
+ ("n" . org-roam-capture)
+ ("f" . org-roam-node-find)
+ ("w" . org-roam-buffer-toggle)
+ ("i" . org-roam-node-insert))
+ (require 'org-roam-export))
#+end_src
-Then redefine the play-sound-file function where needed:
-
+*** org-roam-ui
#+begin_src emacs-lisp
- (cond ((and (not (fboundp 'play-sound-internal))
- (eq system-type 'darwin))
- (advice-add 'play-sound-file :override #'za/play-sound-file-macos)))
+ (use-package org-roam-ui)
#+end_src
-
-* Daemon
-I want to have a way to kill the Emacs daemon.
-So, define a function that kills the frame, and with a prefix kills emacs.
+*** org-download
+Drag-and-drop images to Emacs Org mode.
#+begin_src emacs-lisp
- (defun za/emacsclient-c-x-c-c (&optional arg)
- "If running in emacsclient, make C-x C-c exit frame, and C-u C-x C-c exit Emacs."
- (interactive "P") ; prefix arg in raw form
- (if arg
- (save-buffers-kill-emacs)
- (save-buffers-kill-terminal)))
+ (use-package org-download
+ :custom
+ (org-download-method 'attach)
+ (org-download-backend 'curl))
#+end_src
-Then, if I'm in an emacsclient, I want to bind C-x C-c to that function (if not, I just want the default keybinding):
+*** org-sticky-header
+Displays in the header-line the Org heading for the node that’s at the top of the window.
#+begin_src emacs-lisp
- ;; If not running in emacsclient, use the default bindings
- (if (daemonp)
- (za/global-set-key (kbd "C-x C-c") #'za/emacsclient-c-x-c-c))
+ (use-package org-sticky-header)
#+end_src
-
-Furthermore, I want to set the theme correctly whenever I connect with 'emacsclient':
-
+*** org-timestone
#+begin_src emacs-lisp
- (if (daemonp)
- (add-hook 'after-make-frame-functions #'za/auto-select-theme))
+ (use-package org-timestone
+ :quelpa (org-timestone :repo "thezeroalpha/org-timestone.el" :fetcher github)
+ :after org
+ :ensure nil)
#+end_src
-* Notmuch
-Define some saved searches (i.e. mailboxes):
-
+*** org-noter
#+begin_src emacs-lisp
- (setq notmuch-saved-searches
- `((:name "inbox: personal" :query ,(format "folder:/%s/ tag:inbox" za/email-personal) :key ,(kbd "ip") :search-type 'tree)
- (:name "inbox: school" :query ,(format "folder:/%s/ tag:inbox" za/email-vu) :key ,(kbd "is") :search-type 'tree)
- (:name "archive: personal" :query ,(format "folder:/%s/ tag:archive" za/email-personal) :key ,(kbd "ap") :search-type 'tree)
- (:name "archive: school" :query ,(format "folder:/%s/ tag:archive" za/email-vu) :key ,(kbd "as") :search-type 'tree)))
+ (use-package org-noter
+ :config
+ ;; Fix disabling of line wrap by no-opping set-notes-scroll
+ (advice-add 'org-noter--set-notes-scroll :override 'za/no-op))
#+end_src
-Define the main screen sections:
+*** TODO the path for org-roam export and data export should be configurable, not hard-coded
+** Mail mode for neomutt
+When editing a message from neomutt, I want to use mail mode.
+Even though I won't be sending the email from there, I like the syntax highlighting :)
#+begin_src emacs-lisp
- (setq notmuch-hello-sections
- '(notmuch-hello-insert-header
- notmuch-hello-insert-saved-searches
- notmuch-hello-insert-search
- notmuch-hello-insert-alltags
- notmuch-hello-insert-footer))
+ (add-to-list 'auto-mode-alist '("/neomutt-" . mail-mode))
#+end_src
+** DISABLED Semantic mode
+Disabled for now, don't use it much.
+SemanticDB is written into ~/.emacs.d/semanticdb/.
+
+#+begin_src emacs-lisp :tangle no
+ (use-package semantic
+ :bind (:map semantic-mode-map
+ ("C-c , ." . semantic-ia-show-summary))
+ :custom
+ (semantic-default-submodes '(global-semantic-idle-scheduler-mode ; reparse buffer when idle
+ global-semanticdb-minor-mode ; maintain database
+ global-semantic-idle-summary-mode ; show information (e.g. types) about tag at point
+ global-semantic-stickyfunc-mode))) ; show current func in header line
-Global keybindings:
-#+begin_src emacs-lisp
- (za/global-set-key (kbd "C-c m") #'notmuch)
#+end_src
-Show newest mail first:
-
+** Bib(la)tex
#+begin_src emacs-lisp
- (setq notmuch-search-oldest-first nil)
+ (use-package bibtex
+ :config
+ (bibtex-set-dialect "biblatex"))
#+end_src
-Set tags:
+** Python
+In Python, I want to enable flycheck and semantic mode:
#+begin_src emacs-lisp
- (setq notmuch-archive-tags '("-inbox" "+archive"))
- (setq notmuch-show-mark-unread-tags '("+unread"))
- (setq notmuch-delete-tags '("-inbox" "+trash"))
-
- (setq notmuch-tagging-keys '(("a" notmuch-archive-tags "Archive")
- ("r" notmuch-show-mark-read-tags "Mark read")
- ("u" notmuch-show-mark-unread-tags "Mark unread")
- ("d" notmuch-delete-tags "Delete")))
+ (add-hook 'python-mode-hook #'flycheck-mode)
+ ;;(add-hook 'python-mode-hook #'semantic-mode)
#+end_src
-Run notmuch-hook script on hello refresh, to move messages to folders according to their tags:
-
+** Elisp
#+begin_src emacs-lisp
- (defun za/notmuch-hook-tags2folders ()
- "Run notmuch-hook to organise email in folders based on tags."
- (start-process "notmuch-hook" nil "notmuch-hook" "tags2folders"))
+ (use-package emacs-lisp
+ :ensure nil ; preinstalled
+ :hook ((emacs-lisp-mode . flycheck-mode)
+ (emacs-lisp-mode . rainbow-mode)
+ (emacs-lisp-mode . outline-minor-mode)
+ (emacs-lisp-mode . company-mode)))
#+end_src
+** lean-mode
+Specifically for the Lean prover.
+I also install company-lean and helm-lean, which are suggested on the [[https://github.com/leanprover/lean-mode][Github page]].
+Then I map company-complete only for lean-mode.
#+begin_src emacs-lisp
- (add-hook 'notmuch-hello-refresh-hook #'za/notmuch-hook-tags2folders)
+ (use-package lean-mode
+ :config
+ (use-package company-lean)
+ :bind (:map lean-mode-map
+ ("S-SPC" . company-complete)))
#+end_src
-Sort with newest first:
-
-#+begin_src emacs-lisp
- (setq-default notmuch-search-oldest-first nil)
+** sh-mode
+#+begin_src emacs-lisp :results value
+ (use-package sh-script
+ :hook (sh-mode . flycheck-mode))
#+end_src
-* MPC
-Set the windows I want to show:
+** anki-editor
+Some extra keybindings that are not set up by default.
+anki-editor doesn't provide a keymap so I have to set one up here:
#+begin_src emacs-lisp
- (setq mpc-browser-tags '(AlbumArtist Album Genre Playlist))
-#+end_src
+ (use-package anki-editor
+ :init
+ (defvar anki-editor-mode-map (make-sparse-keymap))
+ (add-to-list 'minor-mode-map-alist (cons 'anki-editor-mode
+ anki-editor-mode-map))
+ :custom
+ (anki-editor-use-math-jax t)
-Define some functions:
+ :bind (:map anki-editor-mode-map
+ ("C-c t" . org-property-next-allowed-value)
+ ("C-c i" . anki-editor-insert-note)
+ ("C-c p" . anki-editor-push-notes)
+ ("C-c c" . anki-editor-cloze-dwim)))
+#+end_src
+** pdf-tools
+A better replacement for DocView:
#+begin_src emacs-lisp
- (defun za/mpc-seek-forward-20-seconds ()
- "Seek forward 20 seconds"
- (interactive)
- (mpc-seek-current "+20"))
+ (use-package pdf-tools
+ :custom
+ (pdf-annot-default-annotation-properties '((t
+ (label . "Alex Balgavy"))
+ (text
+ (icon . "Note")
+ (color . "#0088ff"))
+ (highlight
+ (color . "yellow"))
+ (squiggly
+ (color . "orange"))
+ (strike-out
+ (color . "red"))
+ (underline
+ (color . "blue"))))
+ :bind (:map pdf-isearch-minor-mode-map
+ ("C-s" . isearch-forward)
+ :map pdf-view-mode-map
+ ;; Save position & jump back
+ ("C-SPC" . (lambda () (interactive) (message "Position saved") (pdf-view-position-to-register ?x)))
+ ("C-u C-SPC" . (lambda () (interactive) (pdf-view-jump-to-register ?x))))
+ :hook
+ (pdf-annot-list-mode . pdf-annot-list-follow-minor-mode)
+ (pdf-annot-edit-contents-minor-mode . org-mode)
+ (pdf-view-mode . (lambda () (display-line-numbers-mode 0)))
+
+ :config
+ (pdf-tools-install)
+
+ ;; The arrow tooltip does not show properly when jumping to a
+ ;; location. Maybe this is a Mac-only thing. See here:
+ ;; https://github.com/politza/pdf-tools/issues/145
+ ;; This ~:override~ advice fixes it, color is customized via ~tooltip~ face
+ (advice-add #'pdf-util-tooltip-arrow :override #'za/pdf-util-tooltip-arrow)
+ (defun za/pdf-util-tooltip-arrow (image-top &optional timeout)
+ "Fix up `pdf-util-tooltip-arrow`, the original doesn't show the arrow."
+ (pdf-util-assert-pdf-window)
+ (when (floatp image-top)
+ (setq image-top
+ (round (* image-top (cdr (pdf-view-image-size))))))
+ (let* (x-gtk-use-system-tooltips ;allow for display property in tooltip
+ (dx (+ (or (car (window-margins)) 0)
+ (car (window-fringes))))
+ (dy image-top)
+ (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
+ (vscroll
+ (pdf-util-required-vscroll pos))
+ (tooltip-frame-parameters
+ `((border-width . 0)
+ (internal-border-width . 0)
+ ,@tooltip-frame-parameters))
+ (tooltip-hide-delay (or timeout 3)))
+ (when vscroll
+ (image-set-window-vscroll vscroll))
+ (setq dy (max 0 (- dy
+ (cdr (pdf-view-image-offset))
+ (window-vscroll nil t)
+ (frame-char-height))))
+ (when (overlay-get (pdf-view-current-overlay) 'before-string)
+ (let* ((e (window-inside-pixel-edges))
+ (xw (pdf-util-with-edges (e) e-width)))
+ (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2))))
+ (pdf-util-tooltip-in-window "\u2192" dx dy))))
- (defun za/mpc-seek-backward-20-seconds ()
- "Seek backward 20 seconds"
- (interactive)
- (mpc-seek-current "-20"))
#+end_src
-Define some keybindings:
+*** TODO this clobbers register x. Find a way to not clobber a register
+** virtualenvwrapper
+Like virtualenvwrapper.sh, but for Emacs.
#+begin_src emacs-lisp
- (defun za/mpc-mode-settings ()
- "MPC mode settings"
- (define-key mpc-mode-map "a" #'mpc-playlist-add)
- (define-key mpc-mode-map "P" #'mpc-playlist)
- (define-key mpc-mode-map "x" #'mpc-playlist-delete)
- (define-key mpc-mode-map "p" #'mpc-toggle-play)
- (define-key mpc-mode-map "t" #'mpc-select-toggle)
- (define-key mpc-mode-map "f" #'za/mpc-seek-forward-20-seconds)
- (define-key mpc-mode-map "b" #'za/mpc-seek-backward-20-seconds))
+ (use-package virtualenvwrapper
+ :custom
+ (venv-location "~/.config/virtualenvs")
+
+ :config
+ (venv-initialize-interactive-shells)
+ (venv-initialize-eshell))
#+end_src
+** ledger
#+begin_src emacs-lisp
- (add-hook 'mpc-mode-hook #'za/mpc-mode-settings)
+ (use-package ledger-mode
+ :mode ("\\.ledger\\'")
+ :hook (ledger-mode . company-mode)
+ :custom
+ (ledger-clear-whole-transactions t)
+ (ledger-reconcile-default-commodity "eur")
+ (ledger-reports
+ '(("budget-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" budget ^expenses")
+ ("budget-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" budget ^expenses")
+ ("expenses-this-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^expenses --budget")
+ ("expenses-last-month-vs-budget" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" --period-sort \"(amount)\" bal ^expenses --budget")
+ ("expenses-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur")
+ ("expenses-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur")
+ ("expenses-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur")
+ ("expenses-vs-income-this-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur")
+ ("expenses-vs-income-last-month" "%(binary) -f %(ledger-file) --start-of-week=1 --effective --period \"last month\" --period-sort \"(amount)\" bal ^expenses ^income -X eur")
+ ("bal-assets-czk" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X czk")
+ ("bal-assets" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities")
+ ("bal" "%(binary) -f %(ledger-file) --start-of-week=1 bal -B")
+ ("bal-assets-eur" "%(binary) -f %(ledger-file) --start-of-week=1 bal Assets Liabilities -X eur")
+ ("monthly-balance-abn-checking" "%(binary) -f %(ledger-file) --start-of-week=1 --effective reg --monthly 'Assets:ABN Checking'")
+ ("monthly-expenses" "%(binary) -f %(ledger-file) --monthly register ^expenses --collapse -X eur")
+ ("reg" "%(binary) -f %(ledger-file) --start-of-week=1 reg")
+ ("payee" "%(binary) -f %(ledger-file) --start-of-week=1 reg @%(payee)")
+ ("account" "%(binary) -f %(ledger-file) --start-of-week=1 reg %(account)")
+ ("degiro-changes" "%(binary) -f %(ledger-file) --start-of-week=1 reg Investments:Degiro -X eur"))))
#+end_src
-Unfortunately the lambda keybindings don't show up documented properly, but oh well. That's a minor problem.
+org-capture lets me add transactions from anywhere in Emacs:
-* Radio
-Just a wrapper function to my radio script:
+Budget throws an error when there's multiple commodities involved.
+See discussion here: https://github.com/ledger/ledger/issues/1450#issuecomment-390067165
#+begin_src emacs-lisp
- (defun radio ()
- "Play an internet radio"
- (interactive)
- (ansi-term "radio" "*radio*"))
+ (defconst za/ledger-budget-fix-string
+ "-X eur -F '%(justify(scrub(get_at(display_total, 0)), 20, -1, true, false)) %(justify(get_at(display_total, 1) ? -scrub(get_at(display_total, 1)) : 0.0, 20, 20 + 1 + 20, true, false)) %(justify(get_at(display_total, 1) ? (get_at(display_total, 0) ? -(scrub(get_at(display_total, 1) + get_at(display_total, 0))) : -(scrub(get_at(display_total, 1)))) : -(scrub(get_at(display_total, 0))), 20, 20 + 1 + 20 + 1 + 20, true, false))%(get_at(display_total, 1) and (abs(quantity(scrub(get_at(display_total, 0))) / quantity(scrub(get_at(display_total, 1)))) >= 1) ? \" \" : \" \")%(justify((get_at(display_total, 1) ? (100% * (get_at(display_total, 0) ? scrub(get_at(display_total, 0)) : 0.0)) / -scrub(get_at(display_total, 1)) : \"na\"), 5, -1, true, false)) %(!options.flat ? depth_spacer : \"\")%-(partial_account(options.flat))\n%/%$2 %$3 %$4 %$6\n%/%(prepend_width ? \" \" * int(prepend_width) : \"\") ---------------- ---------------- ---------------- -----\n'"
+ "Append this to a ledger budget to fix errors with multiple commodities.")
#+end_src
-* Dired
-'i' expands subdirs, so I want to be able to close them too.
-
+** Notmuch
#+begin_src emacs-lisp
- (define-key dired-mode-map (kbd "M-k") #'dired-kill-subdir)
+ (use-package notmuch
+ :custom
+ (notmuch-saved-searches
+ `((:name "inbox: personal" :query ,(format "folder:/%s/ tag:inbox" za/email-personal) :key ,(kbd "ip") :search-type 'tree)
+ (:name "inbox: school" :query ,(format "folder:/%s/ tag:inbox" za/email-vu) :key ,(kbd "is") :search-type 'tree)
+ (:name "archive: personal" :query ,(format "folder:/%s/ tag:archive" za/email-personal) :key ,(kbd "ap") :search-type 'tree)
+ (:name "archive: school" :query ,(format "folder:/%s/ tag:archive" za/email-vu) :key ,(kbd "as") :search-type 'tree))
+ "Define some saved searches (i.e. mailboxes)")
+ (notmuch-hello-sections
+ '(notmuch-hello-insert-header
+ notmuch-hello-insert-saved-searches
+ notmuch-hello-insert-search
+ notmuch-hello-insert-alltags
+ notmuch-hello-insert-footer)
+ "Define the main screen sections")
+ (notmuch-search-oldest-first nil "Show newest mail first")
+ (notmuch-archive-tags '("-inbox" "+archive"))
+ (notmuch-show-mark-unread-tags '("+unread"))
+ (notmuch-delete-tags '("-inbox" "+trash"))
+ (notmuch-tagging-keys '(("a" notmuch-archive-tags "Archive")
+ ("r" notmuch-show-mark-read-tags "Mark read")
+ ("u" notmuch-show-mark-unread-tags "Mark unread")
+ ("d" notmuch-delete-tags "Delete")))
+
+ :bind ("C-c m" . notmuch)
+ ;; Run notmuch-hook script on hello refresh, to move messages to
+ ;; folders according to their tags:
+ :hook (notmuch-hello-refresh . za/notmuch-hook-tags2folders)
+ :config
+ (defun za/notmuch-hook-tags2folders ()
+ "Run notmuch-hook to organise email in folders based on tags."
+ (start-process "notmuch-hook" nil "notmuch-hook" "tags2folders")))
#+end_src
-Set up listing display:
+** MPC
+#+begin_src emacs-lisp
+ (use-package mpc
+ :custom
+ (mpc-browser-tags '(AlbumArtist Album Genre Playlist)
+ "Set the windows I want to show")
+
+ :bind (:map mpc-mode-map
+ ("a" . mpc-playlist-add)
+ ("P" . mpc-playlist)
+ ("x" . mpc-playlist-delete)
+ ("p" . mpc-toggle-play)
+ ("t" . mpc-select-toggle)
+ ("f" . za/mpc-seek-forward-20-seconds)
+ ("b" . za/mpc-seek-backward-20-seconds))
+ :config
+ (defun za/mpc-seek-forward-20-seconds ()
+ "Seek forward 20 seconds"
+ (interactive)
+ (mpc-seek-current "+20"))
+ (defun za/mpc-seek-backward-20-seconds ()
+ "Seek backward 20 seconds"
+ (interactive)
+ (mpc-seek-current "-20")))
+#+end_src
+** Dired
#+begin_src emacs-lisp
- (setq-default dired-listing-switches "-alhv")
+ (use-package dired
+ :ensure nil ; installed with Emacs
+ :bind (:map dired-mode-map
+ ;; 'i' expands subdirs, so I want to be able to close them too.
+ ("M-k" . dired-kill-subdir))
+ :custom
+ (dired-listing-switches "-alhv")
+ (dired-dwim-target t "If I have another dired window open, use that as target")
+ ;; By default, hide details (show again by pressing oparen):
+ :hook (dired-mode . dired-hide-details-mode))
#+end_src
-By default, hide details (show again by pressing oparen):
-
+** ess: statistics (R, SAS...)
#+begin_src emacs-lisp
- (add-hook 'dired-mode-hook #'dired-hide-details-mode)
+ (use-package ess)
#+end_src
-If I have another dired window open, use that as target:
-
+** help mode
#+begin_src emacs-lisp
- (setq dired-dwim-target t)
+ (use-package help-mode
+ :ensure nil ; included with Emacs
+ :hook (help-mode . za/settings-on-help-mode)
+ :config
+ (defun za/settings-on-help-mode ()
+ "Settings on enabling help mode"
+ (za/toggle-wrap t)))
#+end_src
+** helpful
+An alternative to the built-in Emacs help that provides much more contextual information.
+I use counsel, so I use the keybindings in [[*counsel + ivy + swiper]].
+I just augment the functions counsel uses.
+Also, counsel doesn't provide some keybindings that I can get from helpful.
-* Syncthing
-Some functions to start/stop syncthing.
#+begin_src emacs-lisp
- (defconst za/st-buffer-name "*syncthing*" "Buffer name for the syncthing process.")
- (defun za/st ()
- "Start syncthing"
- (interactive)
- (if (get-buffer-process za/st-buffer-name)
- (user-error "Syncthing is already running."))
- (async-shell-command "syncthing serve --no-browser" za/st-buffer-name))
+ (use-package helpful
+ :custom
+ (counsel-describe-symbol-function #'helpful-symbol)
+ (counsel-describe-function-function #'helpful-callable)
+ (counsel-describe-variable-function #'helpful-variable)
- (defun za/st-kill ()
- "Stop syncthing"
- (interactive)
- (unless (get-buffer-process za/st-buffer-name)
- (user-error "Syncthing is not running."))
- (async-shell-command "syncthing cli operations shutdown"))
-#+end_src
+ :bind (("C-h k" . helpful-key)
+ ("C-h C" . helpful-command)
+ :map helpful-mode-map
+ ("l" . za/helpful-previous)
+ ("r" . za/helpful-next))
+
+ :hook (helpful-mode . za/settings-on-helpful-mode)
+ :config
+ (defun za/settings-on-helpful-mode ()
+ "Settings on enabling helpful mode"
+ (za/toggle-wrap t))
+
+ ;; Then, a way to jump forward and backward in the window:
+ (defvar za/helpful-buffer-ring-size 20
+ "How many buffers are stored for use with `helpful-next'.")
+
+ (defvar za/helpful--buffer-ring (make-ring za/helpful-buffer-ring-size)
+ "Ring that stores the current Helpful buffer history.")
+
+ (defun za/helpful--buffer-index (&optional buffer)
+ "If BUFFER is a Helpful buffer, return it’s index in the buffer ring."
+ (let ((buf (or buffer (current-buffer))))
+ (and (eq (buffer-local-value 'major-mode buf) 'helpful-mode)
+ (seq-position (ring-elements za/helpful--buffer-ring) buf #'eq))))
+
+ (defun za/helpful--new-buffer-a (help-buf)
+ "Update the buffer ring according to the current buffer and HELP-BUF."
+ :filter-return #'helpful--buffer
+ (let ((buf-ring za/helpful--buffer-ring))
+ (let ((newer-buffers (or (za/helpful--buffer-index) 0)))
+ (dotimes (_ newer-buffers) (ring-remove buf-ring 0)))
+ (when (/= (ring-size buf-ring) za/helpful-buffer-ring-size)
+ (ring-resize buf-ring za/helpful-buffer-ring-size))
+ (ring-insert buf-ring help-buf)))
+
+ (advice-add #'helpful--buffer :filter-return #'za/helpful--new-buffer-a)
+
+ (defun za/helpful--next (&optional buffer)
+ "Return the next live Helpful buffer relative to BUFFER."
+ (let ((buf-ring za/helpful--buffer-ring)
+ (index (or (za/helpful--buffer-index buffer) -1)))
+ (cl-block nil
+ (while (> index 0)
+ (cl-decf index)
+ (let ((buf (ring-ref buf-ring index)))
+ (if (buffer-live-p buf) (cl-return buf)))
+ (ring-remove buf-ring index)))))
+
+
+ (defun za/helpful--previous (&optional buffer)
+ "Return the previous live Helpful buffer relative to BUFFER."
+ (let ((buf-ring za/helpful--buffer-ring)
+ (index (1+ (or (za/helpful--buffer-index buffer) -1))))
+ (cl-block nil
+ (while (< index (ring-length buf-ring))
+ (let ((buf (ring-ref buf-ring index)))
+ (if (buffer-live-p buf) (cl-return buf)))
+ (ring-remove buf-ring index)))))
+
+ (defun za/helpful-next ()
+ "Go to the next Helpful buffer."
+ (interactive)
+ (when-let (buf (za/helpful--next))
+ (funcall helpful-switch-buffer-function buf)))
+
+ (defun za/helpful-previous ()
+ "Go to the previous Helpful buffer."
+ (interactive)
+ (when-let (buf (za/helpful--previous))
+ (funcall helpful-switch-buffer-function buf))))
+#+end_src
* References
Here's a list of good articles I encountered about configging emacs:
- [[https://karthinks.com/software/batteries-included-with-emacs/][Batteries included with Emacs]]